add the Options feature to control some details

Signed-off-by: Yuhang Zhao <2546789017@qq.com>
This commit is contained in:
Yuhang Zhao 2022-03-20 15:51:05 +08:00
parent 09acbfaf02
commit 12988f2ddf
15 changed files with 170 additions and 94 deletions

View File

@ -29,7 +29,7 @@
FRAMELESSHELPER_USE_NAMESPACE FRAMELESSHELPER_USE_NAMESPACE
MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) : FramelessMainWindow(parent, flags) MainWindow::MainWindow(QWidget *parent, const Qt::WindowFlags flags) : FramelessMainWindow(parent, flags)
{ {
setupUi(); setupUi();
} }

View File

@ -38,7 +38,7 @@ class MainWindow : public FRAMELESSHELPER_PREPEND_NAMESPACE(FramelessMainWindow)
Q_DISABLE_COPY_MOVE(MainWindow) Q_DISABLE_COPY_MOVE(MainWindow)
public: public:
explicit MainWindow(QWidget *parent = nullptr, Qt::WindowFlags flags = {}); explicit MainWindow(QWidget *parent = nullptr, const Qt::WindowFlags flags = {});
~MainWindow() override; ~MainWindow() override;
protected: protected:

View File

@ -30,7 +30,7 @@
FRAMELESSHELPER_USE_NAMESPACE FRAMELESSHELPER_USE_NAMESPACE
Widget::Widget(QWidget *parent) : FramelessWidget(parent, WindowLayout::Standard) Widget::Widget(QWidget *parent) : FramelessWidget(parent, {Option::UseStandardWindowLayout})
{ {
setupUi(); setupUi();
startTimer(500); startTimer(500);

View File

@ -26,6 +26,7 @@
#include <QtCore/qdebug.h> #include <QtCore/qdebug.h>
#include <QtCore/qhash.h> #include <QtCore/qhash.h>
#include <QtCore/qmutex.h> #include <QtCore/qmutex.h>
#include <QtCore/qvariant.h>
#include <QtCore/qcoreapplication.h> #include <QtCore/qcoreapplication.h>
#include <QtGui/qwindow.h> #include <QtGui/qwindow.h>
#include "framelesswindowsmanager.h" #include "framelesswindowsmanager.h"
@ -76,11 +77,16 @@ void FramelessHelperWin::addWindow(QWindow *window)
qApp->installNativeEventFilter(g_win32Helper()->nativeEventFilter.data()); qApp->installNativeEventFilter(g_win32Helper()->nativeEventFilter.data());
} }
g_win32Helper()->mutex.unlock(); g_win32Helper()->mutex.unlock();
Utils::fixupQtInternals(winId); const auto options = qvariant_cast<Options>(window->property(kInternalOptionsFlag));
if (!(options & Option::DontModifyQtInternals)) {
Utils::fixupQtInternals(winId);
}
Utils::updateInternalWindowFrameMargins(window, true); Utils::updateInternalWindowFrameMargins(window, true);
Utils::updateWindowFrameMargins(winId, false); Utils::updateWindowFrameMargins(winId, false);
const bool dark = Utils::shouldAppsUseDarkMode(); if (!(options & Option::DontModifyWindowFrameBorderColor)) {
Utils::updateWindowFrameBorderColor(winId, dark); const bool dark = Utils::shouldAppsUseDarkMode();
Utils::updateWindowFrameBorderColor(winId, dark);
}
} }
void FramelessHelperWin::removeWindow(QWindow *window) void FramelessHelperWin::removeWindow(QWindow *window)
@ -119,7 +125,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
#endif #endif
if (!msg->hwnd) { if (!msg->hwnd) {
// Why sometimes the window handle is null? Is it designed to be like this? // Why sometimes the window handle is null? Is it designed to be like this?
// Anyway, we should skip the entire function in this case. // Anyway, we should skip the entire processing in this case.
return false; return false;
} }
const WId winId = reinterpret_cast<WId>(msg->hwnd); const WId winId = reinterpret_cast<WId>(msg->hwnd);
@ -134,6 +140,22 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
if (!window) { if (!window) {
return false; return false;
} }
auto options = qvariant_cast<Options>(window->property(kInternalOptionsFlag));
if ((options & Option::ForceHideWindowFrameBorder) && (options & Option::ForceShowWindowFrameBorder)) {
qWarning() << "You can't use both \"Option::ForceHideWindowFrameBorder\" and "
"\"Option::ForceShowWindowFrameBorder\" at the same time.";
options &= ~(Option::ForceHideWindowFrameBorder | Option::ForceShowWindowFrameBorder);
window->setProperty(kInternalOptionsFlag, QVariant::fromValue(options));
}
const bool frameBorderVisible = [options]() -> bool {
if (options & Option::ForceShowWindowFrameBorder) {
return true;
}
if (options & Option::ForceHideWindowFrameBorder) {
return false;
}
return Utils::isWindowFrameBorderVisible();
}();
switch (msg->message) { switch (msg->message) {
case WM_NCCALCSIZE: { case WM_NCCALCSIZE: {
// Windows是根据这个消息的返回值来设置窗口的客户区窗口中真正显示的内容 // Windows是根据这个消息的返回值来设置窗口的客户区窗口中真正显示的内容
@ -226,7 +248,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
const auto clientRect = ((static_cast<BOOL>(msg->wParam) == FALSE) const auto clientRect = ((static_cast<BOOL>(msg->wParam) == FALSE)
? reinterpret_cast<LPRECT>(msg->lParam) ? reinterpret_cast<LPRECT>(msg->lParam)
: &(reinterpret_cast<LPNCCALCSIZE_PARAMS>(msg->lParam))->rgrc[0]); : &(reinterpret_cast<LPNCCALCSIZE_PARAMS>(msg->lParam))->rgrc[0]);
if (Utils::isWindowFrameBorderVisible()) { if (frameBorderVisible) {
// Store the original top before the default window proc applies the default frame. // Store the original top before the default window proc applies the default frame.
const LONG originalTop = clientRect->top; const LONG originalTop = clientRect->top;
// Apply the default frame. // Apply the default frame.
@ -252,7 +274,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
// a window when it's maximized unless you restore it). // a window when it's maximized unless you restore it).
const int frameSizeY = Utils::getResizeBorderThickness(winId, false, true); const int frameSizeY = Utils::getResizeBorderThickness(winId, false, true);
clientRect->top += frameSizeY; clientRect->top += frameSizeY;
if (!Utils::isWindowFrameBorderVisible()) { if (!frameBorderVisible) {
clientRect->bottom -= frameSizeY; clientRect->bottom -= frameSizeY;
const int frameSizeX = Utils::getResizeBorderThickness(winId, true, true); const int frameSizeX = Utils::getResizeBorderThickness(winId, true, true);
clientRect->left += frameSizeX; clientRect->left += frameSizeX;
@ -466,7 +488,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
const int frameSizeY = Utils::getResizeBorderThickness(winId, false, true); const int frameSizeY = Utils::getResizeBorderThickness(winId, false, true);
const bool isTop = (localPos.y < frameSizeY); const bool isTop = (localPos.y < frameSizeY);
static constexpr const bool isTitleBar = false; static constexpr const bool isTitleBar = false;
if (Utils::isWindowFrameBorderVisible()) { if (frameBorderVisible) {
// This will handle the left, right and bottom parts of the frame // This will handle the left, right and bottom parts of the frame
// because we didn't change them. // because we didn't change them.
const LRESULT originalRet = DefWindowProcW(msg->hwnd, WM_NCHITTEST, 0, msg->lParam); const LRESULT originalRet = DefWindowProcW(msg->hwnd, WM_NCHITTEST, 0, msg->lParam);
@ -579,7 +601,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
default: default:
break; break;
} }
if (!Utils::isWindowFrameBorderVisible()) { if (!frameBorderVisible) {
switch (msg->message) { switch (msg->message) {
case WM_NCUAHDRAWCAPTION: case WM_NCUAHDRAWCAPTION:
case WM_NCUAHDRAWFRAME: { case WM_NCUAHDRAWFRAME: {
@ -665,8 +687,10 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
return false; return false;
}(); }();
if (themeSettingChanged) { if (themeSettingChanged) {
const bool dark = Utils::shouldAppsUseDarkMode(); if (!(options & Option::DontModifyWindowFrameBorderColor)) {
Utils::updateWindowFrameBorderColor(winId, dark); const bool dark = Utils::shouldAppsUseDarkMode();
Utils::updateWindowFrameBorderColor(winId, dark);
}
} }
if (themeSettingChanged || (msg->message == WM_THEMECHANGED) if (themeSettingChanged || (msg->message == WM_THEMECHANGED)
|| (msg->message == WM_DWMCOLORIZATIONCOLORCHANGED)) { || (msg->message == WM_DWMCOLORIZATIONCOLORCHANGED)) {

View File

@ -108,11 +108,29 @@ Q_NAMESPACE_EXPORT(FRAMELESSHELPER_CORE_API)
[[maybe_unused]] static constexpr const QSize kDefaultSystemButtonSize = {int(qRound(qreal(kDefaultTitleBarHeight) * 1.5)), kDefaultTitleBarHeight}; [[maybe_unused]] static constexpr const QSize kDefaultSystemButtonSize = {int(qRound(qreal(kDefaultTitleBarHeight) * 1.5)), kDefaultTitleBarHeight};
[[maybe_unused]] static constexpr const QSize kDefaultSystemButtonIconSize = {16, 16}; [[maybe_unused]] static constexpr const QSize kDefaultSystemButtonIconSize = {16, 16};
enum class WindowLayout : int [[maybe_unused]] static constexpr const char kInternalOptionsFlag[] = "FRAMELESSHELPER_INTERNAL_OPTIONS";
enum class Option : int
{ {
Standard = 0, Default = 0x00000000, // Default placeholder, have no effect.
Custom = 1 ForceHideWindowFrameBorder = 0x00000001, // Windows only, force hide the window frame border even on Windows 10 and onwards.
ForceShowWindowFrameBorder = 0x00000002, // Windows only, force show the window frame border even on Windows 7 (~ 8.1).
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.
UseStandardWindowLayout = 0x00000040, // The standard window layout is a titlebar always on top and fill the window width.
BeCompatibleWithQtFramelessWindowHint = 0x00000080, // Windows only, make the code compatible with Qt::FramelessWindowHint. Don't use this option unless you really need that flag.
DontModifyQtInternals = 0x00000100, // Windows only, don't touch Qt's internal data.
DontModifyWindowFrameBorderColor = 0x00000200, // Windows only, don't touch the window frame border color.
DontInstallSystemMenuHook = 0x00000400, // Windows only, don't install the system menu hook.
DisableSystemMenu = 0x00000800, // Windows only, don't open the system menu when right clicks the titlebar.
NoDoubleClickMaximizeToggle = 0x00001000 // Don't toggle the maximize state when double clicks the titlebar.
}; };
Q_ENUM_NS(WindowLayout) Q_FLAG_NS(Option)
Q_DECLARE_FLAGS(Options, Option)
Q_DECLARE_OPERATORS_FOR_FLAGS(Options)
FRAMELESSHELPER_END_NAMESPACE FRAMELESSHELPER_END_NAMESPACE
Q_DECLARE_METATYPE(FRAMELESSHELPER_PREPEND_NAMESPACE(Options))

View File

@ -24,6 +24,7 @@
#include "framelesswindowsmanager.h" #include "framelesswindowsmanager.h"
#include "framelesswindowsmanager_p.h" #include "framelesswindowsmanager_p.h"
#include <QtCore/qvariant.h>
#include <QtGui/qscreen.h> #include <QtGui/qscreen.h>
#include <QtGui/qwindow.h> #include <QtGui/qwindow.h>
#include "framelesshelper_qt.h" #include "framelesshelper_qt.h"
@ -182,7 +183,10 @@ void FramelessWindowsManager::addWindow(QWindow *window)
if (!pureQt) { if (!pureQt) {
FramelessHelperWin::addWindow(window); FramelessHelperWin::addWindow(window);
} }
Utils::installSystemMenuHook(winId); const auto options = qvariant_cast<Options>(window->property(kInternalOptionsFlag));
if (!(options & Option::DontInstallSystemMenuHook)) {
Utils::installSystemMenuHook(winId);
}
#endif #endif
} }
@ -208,7 +212,10 @@ void FramelessWindowsManager::removeWindow(QWindow *window)
disconnect(d->win32WorkaroundConnections.value(uuid)); disconnect(d->win32WorkaroundConnections.value(uuid));
d->win32WorkaroundConnections.remove(uuid); d->win32WorkaroundConnections.remove(uuid);
} }
Utils::uninstallSystemMenuHook(winId); const auto options = qvariant_cast<Options>(window->property(kInternalOptionsFlag));
if (!(options & Option::DontInstallSystemMenuHook)) {
Utils::uninstallSystemMenuHook(winId);
}
#endif #endif
static const bool pureQt = d->usePureQtImplementation(); static const bool pureQt = d->usePureQtImplementation();
if (pureQt) { if (pureQt) {

View File

@ -77,6 +77,7 @@ FRAMELESSHELPER_CORE_API void startSystemResize(QWindow *window, const Qt::Edges
[[nodiscard]] FRAMELESSHELPER_CORE_API bool isWindowFixedSize(const QWindow *window); [[nodiscard]] FRAMELESSHELPER_CORE_API bool isWindowFixedSize(const QWindow *window);
[[nodiscard]] FRAMELESSHELPER_CORE_API QVariant getSystemButtonIconResource [[nodiscard]] FRAMELESSHELPER_CORE_API QVariant getSystemButtonIconResource
(const SystemButtonType button, const SystemTheme theme, const ResourceType type); (const SystemButtonType button, const SystemTheme theme, const ResourceType type);
FRAMELESSHELPER_CORE_API void sendMouseReleaseEvent();
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
[[nodiscard]] FRAMELESSHELPER_CORE_API bool isWin8OrGreater(); [[nodiscard]] FRAMELESSHELPER_CORE_API bool isWin8OrGreater();
@ -111,6 +112,7 @@ FRAMELESSHELPER_CORE_API void fixupQtInternals(const WId winId);
[[nodiscard]] FRAMELESSHELPER_CORE_API bool isFrameBorderColorized(); [[nodiscard]] FRAMELESSHELPER_CORE_API bool isFrameBorderColorized();
FRAMELESSHELPER_CORE_API void installSystemMenuHook(const WId winId); FRAMELESSHELPER_CORE_API void installSystemMenuHook(const WId winId);
FRAMELESSHELPER_CORE_API void uninstallSystemMenuHook(const WId winId); FRAMELESSHELPER_CORE_API void uninstallSystemMenuHook(const WId winId);
FRAMELESSHELPER_CORE_API void tryToBeCompatibleWithQtFramelessWindowHint(QWindow *window, const bool enable);
#endif // Q_OS_WINDOWS #endif // Q_OS_WINDOWS
} // namespace Utils } // namespace Utils

View File

@ -913,7 +913,9 @@ void Utils::fixupQtInternals(const WId winId)
SetLastError(ERROR_SUCCESS); SetLastError(ERROR_SUCCESS);
if (SetWindowLongPtrW(hwnd, GWL_STYLE, static_cast<LONG_PTR>(newWindowStyle)) == 0) { if (SetWindowLongPtrW(hwnd, GWL_STYLE, static_cast<LONG_PTR>(newWindowStyle)) == 0) {
qWarning() << getSystemErrorMessage(QStringLiteral("SetWindowLongPtrW")); qWarning() << getSystemErrorMessage(QStringLiteral("SetWindowLongPtrW"));
return;
} }
triggerFrameChange(winId);
} }
void Utils::startSystemMove(QWindow *window) void Utils::startSystemMove(QWindow *window)
@ -925,10 +927,7 @@ void Utils::startSystemMove(QWindow *window)
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
window->startSystemMove(); window->startSystemMove();
#else #else
if (ReleaseCapture() == FALSE) { sendMouseReleaseEvent();
qWarning() << getSystemErrorMessage(QStringLiteral("ReleaseCapture"));
return;
}
const auto hwnd = reinterpret_cast<HWND>(window->winId()); const auto hwnd = reinterpret_cast<HWND>(window->winId());
if (PostMessageW(hwnd, WM_SYSCOMMAND, 0xF012 /*SC_DRAGMOVE*/, 0) == FALSE) { if (PostMessageW(hwnd, WM_SYSCOMMAND, 0xF012 /*SC_DRAGMOVE*/, 0) == FALSE) {
qWarning() << getSystemErrorMessage(QStringLiteral("PostMessageW")); qWarning() << getSystemErrorMessage(QStringLiteral("PostMessageW"));
@ -948,10 +947,7 @@ void Utils::startSystemResize(QWindow *window, const Qt::Edges edges)
if (edges == Qt::Edges{}) { if (edges == Qt::Edges{}) {
return; return;
} }
if (ReleaseCapture() == FALSE) { sendMouseReleaseEvent();
qWarning() << getSystemErrorMessage(QStringLiteral("ReleaseCapture"));
return;
}
const auto hwnd = reinterpret_cast<HWND>(window->winId()); const auto hwnd = reinterpret_cast<HWND>(window->winId());
if (PostMessageW(hwnd, WM_SYSCOMMAND, qtEdgesToWin32Orientation(edges), 0) == FALSE) { if (PostMessageW(hwnd, WM_SYSCOMMAND, qtEdgesToWin32Orientation(edges), 0) == FALSE) {
qWarning() << getSystemErrorMessage(QStringLiteral("PostMessageW")); qWarning() << getSystemErrorMessage(QStringLiteral("PostMessageW"));
@ -1024,6 +1020,7 @@ void Utils::installSystemMenuHook(const WId winId)
return; return;
} }
g_utilsHelper()->qtWindowProcs.insert(hwnd, originalWindowProc); g_utilsHelper()->qtWindowProcs.insert(hwnd, originalWindowProc);
//triggerFrameChange(winId);
} }
void Utils::uninstallSystemMenuHook(const WId winId) void Utils::uninstallSystemMenuHook(const WId winId)
@ -1048,6 +1045,40 @@ void Utils::uninstallSystemMenuHook(const WId winId)
return; return;
} }
g_utilsHelper()->qtWindowProcs.remove(hwnd); g_utilsHelper()->qtWindowProcs.remove(hwnd);
//triggerFrameChange(winId);
}
void Utils::sendMouseReleaseEvent()
{
if (ReleaseCapture() == FALSE) {
qWarning() << getSystemErrorMessage(QStringLiteral("ReleaseCapture"));
}
}
void Utils::tryToBeCompatibleWithQtFramelessWindowHint(QWindow *window, const bool enable)
{
Q_ASSERT(window);
if (!window) {
return;
}
const WId winId = window->winId();
const auto hwnd = reinterpret_cast<HWND>(winId);
SetLastError(ERROR_SUCCESS);
const LONG_PTR originalWindowStyle = GetWindowLongPtrW(hwnd, GWL_STYLE);
if (originalWindowStyle == 0) {
qWarning() << getSystemErrorMessage(QStringLiteral("GetWindowLongPtrW"));
return;
}
const Qt::WindowFlags originalWindowFlags = window->flags();
const Qt::WindowFlags newWindowFlags = (enable ? (originalWindowFlags | Qt::FramelessWindowHint)
: (originalWindowFlags & ~Qt::FramelessWindowHint));
window->setFlags(newWindowFlags);
SetLastError(ERROR_SUCCESS);
if (SetWindowLongPtrW(hwnd, GWL_STYLE, originalWindowStyle) == 0) {
qWarning() << getSystemErrorMessage(QStringLiteral("SetWindowLongPtrW"));
return;
}
triggerFrameChange(winId);
} }
FRAMELESSHELPER_END_NAMESPACE FRAMELESSHELPER_END_NAMESPACE

View File

@ -208,6 +208,7 @@ bool FramelessQuickEventFilter::eventFilter(QObject *object, QEvent *event)
return false; return false;
} }
const bool titleBar = isInTitleBarDraggableArea(window, scenePos); const bool titleBar = isInTitleBarDraggableArea(window, scenePos);
const auto options = qvariant_cast<Options>(window->property(kInternalOptionsFlag));
switch (eventType) { switch (eventType) {
case QEvent::MouseButtonPress: { case QEvent::MouseButtonPress: {
if (button != Qt::LeftButton) { if (button != Qt::LeftButton) {
@ -220,6 +221,9 @@ bool FramelessQuickEventFilter::eventFilter(QObject *object, QEvent *event)
return true; return true;
} }
case QEvent::MouseButtonRelease: { case QEvent::MouseButtonRelease: {
if (options & Option::DisableSystemMenu) {
return false;
}
if (button != Qt::RightButton) { if (button != Qt::RightButton) {
return false; return false;
} }
@ -236,6 +240,9 @@ bool FramelessQuickEventFilter::eventFilter(QObject *object, QEvent *event)
return true; return true;
} }
case QEvent::MouseButtonDblClick: { case QEvent::MouseButtonDblClick: {
if (options & Option::NoDoubleClickMaximizeToggle) {
return false;
}
if (button != Qt::LeftButton) { if (button != Qt::LeftButton) {
return false; return false;
} }

View File

@ -27,9 +27,9 @@
FRAMELESSHELPER_BEGIN_NAMESPACE FRAMELESSHELPER_BEGIN_NAMESPACE
FramelessMainWindow::FramelessMainWindow(QWidget *parent, Qt::WindowFlags flags) : QMainWindow(parent, flags) FramelessMainWindow::FramelessMainWindow(QWidget *parent, const Qt::WindowFlags flags, const Options options) : QMainWindow(parent, flags)
{ {
m_helper.reset(new FramelessWidgetsHelper(this, WindowLayout::Custom)); m_helper.reset(new FramelessWidgetsHelper(this, options));
} }
FramelessMainWindow::~FramelessMainWindow() = default; FramelessMainWindow::~FramelessMainWindow() = default;
@ -64,12 +64,6 @@ void FramelessMainWindow::toggleMaximized()
m_helper->toggleMaximized(); m_helper->toggleMaximized();
} }
void FramelessMainWindow::showEvent(QShowEvent *event)
{
QMainWindow::showEvent(event);
m_helper->showEventHandler(event);
}
void FramelessMainWindow::changeEvent(QEvent *event) void FramelessMainWindow::changeEvent(QEvent *event)
{ {
QMainWindow::changeEvent(event); QMainWindow::changeEvent(event);

View File

@ -38,7 +38,7 @@ class FRAMELESSHELPER_WIDGETS_API FramelessMainWindow : public QMainWindow
Q_PROPERTY(QWidget* titleBarWidget READ titleBarWidget WRITE setTitleBarWidget NOTIFY titleBarWidgetChanged FINAL) Q_PROPERTY(QWidget* titleBarWidget READ titleBarWidget WRITE setTitleBarWidget NOTIFY titleBarWidgetChanged FINAL)
public: public:
explicit FramelessMainWindow(QWidget *parent = nullptr, Qt::WindowFlags flags = {}); explicit FramelessMainWindow(QWidget *parent = nullptr, const Qt::WindowFlags flags = {}, const Options options = {});
~FramelessMainWindow() override; ~FramelessMainWindow() override;
Q_NODISCARD Q_INVOKABLE bool isNormal() const; Q_NODISCARD Q_INVOKABLE bool isNormal() const;
@ -52,7 +52,6 @@ public:
Q_INVOKABLE void toggleMaximized(); Q_INVOKABLE void toggleMaximized();
protected: protected:
void showEvent(QShowEvent *event) override;
void changeEvent(QEvent *event) override; void changeEvent(QEvent *event) override;
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override;

View File

@ -27,9 +27,9 @@
FRAMELESSHELPER_BEGIN_NAMESPACE FRAMELESSHELPER_BEGIN_NAMESPACE
FramelessWidget::FramelessWidget(QWidget *parent, const WindowLayout wl) : QWidget(parent) FramelessWidget::FramelessWidget(QWidget *parent, const Options options) : QWidget(parent)
{ {
m_helper.reset(new FramelessWidgetsHelper(this, wl)); m_helper.reset(new FramelessWidgetsHelper(this, options));
} }
FramelessWidget::~FramelessWidget() = default; FramelessWidget::~FramelessWidget() = default;
@ -74,12 +74,6 @@ void FramelessWidget::toggleMaximized()
m_helper->toggleMaximized(); m_helper->toggleMaximized();
} }
void FramelessWidget::showEvent(QShowEvent *event)
{
QWidget::showEvent(event);
m_helper->showEventHandler(event);
}
void FramelessWidget::changeEvent(QEvent *event) void FramelessWidget::changeEvent(QEvent *event)
{ {
QWidget::changeEvent(event); QWidget::changeEvent(event);

View File

@ -39,7 +39,7 @@ class FRAMELESSHELPER_WIDGETS_API FramelessWidget : public QWidget
Q_PROPERTY(QWidget* contentWidget READ contentWidget WRITE setContentWidget NOTIFY contentWidgetChanged FINAL) Q_PROPERTY(QWidget* contentWidget READ contentWidget WRITE setContentWidget NOTIFY contentWidgetChanged FINAL)
public: public:
explicit FramelessWidget(QWidget *parent = nullptr, const WindowLayout wl = WindowLayout::Standard); explicit FramelessWidget(QWidget *parent = nullptr, const Options options = {});
~FramelessWidget() override; ~FramelessWidget() override;
Q_NODISCARD Q_INVOKABLE bool isNormal() const; Q_NODISCARD Q_INVOKABLE bool isNormal() const;
@ -56,7 +56,6 @@ public:
Q_INVOKABLE void toggleMaximized(); Q_INVOKABLE void toggleMaximized();
protected: protected:
void showEvent(QShowEvent *event) override;
void changeEvent(QEvent *event) override; void changeEvent(QEvent *event) override;
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override;

View File

@ -23,8 +23,10 @@
*/ */
#include "framelesswidgetshelper.h" #include "framelesswidgetshelper.h"
#include <QtCore/qdebug.h>
#include <QtGui/qpainter.h> #include <QtGui/qpainter.h>
#include <QtGui/qevent.h> #include <QtGui/qevent.h>
#include <QtGui/qwindow.h>
#include <QtWidgets/qboxlayout.h> #include <QtWidgets/qboxlayout.h>
#include <QtWidgets/qlabel.h> #include <QtWidgets/qlabel.h>
#include <QtWidgets/qpushbutton.h> #include <QtWidgets/qpushbutton.h>
@ -49,12 +51,12 @@ QPushButton:pressed {
} }
)"); )");
FramelessWidgetsHelper::FramelessWidgetsHelper(QWidget *q, const WindowLayout wl) : QObject(q) FramelessWidgetsHelper::FramelessWidgetsHelper(QWidget *q, const Options options) : QObject(q)
{ {
Q_ASSERT(q); Q_ASSERT(q);
if (q) { if (q) {
this->q = q; this->q = q;
m_windowLayout = wl; m_options = options;
initialize(); initialize();
} }
} }
@ -80,7 +82,7 @@ void FramelessWidgetsHelper::setTitleBarWidget(QWidget *widget)
if (m_userTitleBarWidget == widget) { if (m_userTitleBarWidget == widget) {
return; return;
} }
if (isStandardLayout()) { if (m_options & Option::UseStandardWindowLayout) {
if (m_systemTitleBarWidget && m_systemTitleBarWidget->isVisible()) { if (m_systemTitleBarWidget && m_systemTitleBarWidget->isVisible()) {
m_mainLayout->removeWidget(m_systemTitleBarWidget); m_mainLayout->removeWidget(m_systemTitleBarWidget);
m_systemTitleBarWidget->hide(); m_systemTitleBarWidget->hide();
@ -108,7 +110,7 @@ void FramelessWidgetsHelper::setContentWidget(QWidget *widget)
if (!widget) { if (!widget) {
return; return;
} }
if (isCustomLayout()) { if (!(m_options & Option::UseStandardWindowLayout)) {
return; return;
} }
if (m_userContentWidget == widget) { if (m_userContentWidget == widget) {
@ -143,15 +145,6 @@ void FramelessWidgetsHelper::setHitTestVisible(QWidget *widget, const bool visib
} }
} }
void FramelessWidgetsHelper::showEventHandler(QShowEvent *event)
{
Q_ASSERT(event);
if (!event) {
return;
}
setupFramelessHelperOnce();
}
void FramelessWidgetsHelper::changeEventHandler(QEvent *event) void FramelessWidgetsHelper::changeEventHandler(QEvent *event)
{ {
Q_ASSERT(event); Q_ASSERT(event);
@ -162,8 +155,9 @@ void FramelessWidgetsHelper::changeEventHandler(QEvent *event)
if ((type != QEvent::WindowStateChange) && (type != QEvent::ActivationChange)) { if ((type != QEvent::WindowStateChange) && (type != QEvent::ActivationChange)) {
return; return;
} }
const bool standardLayout = (m_options & Option::UseStandardWindowLayout);
if (type == QEvent::WindowStateChange) { if (type == QEvent::WindowStateChange) {
if (isStandardLayout()) { if (standardLayout) {
if (isZoomed()) { if (isZoomed()) {
m_systemMaximizeButton->setToolTip(tr("Restore")); m_systemMaximizeButton->setToolTip(tr("Restore"));
} else { } else {
@ -173,7 +167,7 @@ void FramelessWidgetsHelper::changeEventHandler(QEvent *event)
} }
updateContentsMargins(); updateContentsMargins();
} }
if (isStandardLayout()) { if (standardLayout) {
updateSystemTitleBarStyleSheet(); updateSystemTitleBarStyleSheet();
} }
q->update(); q->update();
@ -227,6 +221,9 @@ void FramelessWidgetsHelper::mouseReleaseEventHandler(QMouseEvent *event)
if (!event) { if (!event) {
return; return;
} }
if (m_options & Option::DisableSystemMenu) {
return;
}
if (event->button() != Qt::RightButton) { if (event->button() != Qt::RightButton) {
return; return;
} }
@ -258,6 +255,9 @@ void FramelessWidgetsHelper::mouseDoubleClickEventHandler(QMouseEvent *event)
if (!event) { if (!event) {
return; return;
} }
if (m_options & Option::NoDoubleClickMaximizeToggle) {
return;
}
if (event->button() != Qt::LeftButton) { if (event->button() != Qt::LeftButton) {
return; return;
} }
@ -281,21 +281,35 @@ void FramelessWidgetsHelper::initialize()
return; return;
} }
m_initialized = true; m_initialized = true;
// Without this flag, Qt will always create an invisible native parent window
// for any native widgets which will intercept some win32 messages and confuse
// our own native event filter, so to prevent some weired bugs from happening,
// just disable this feature.
q->setAttribute(Qt::WA_DontCreateNativeAncestors); q->setAttribute(Qt::WA_DontCreateNativeAncestors);
// Force the widget become a native window now so that we can deal with its
// win32 events as soon as possible.
q->createWinId(); q->createWinId();
setupInitialUi(); QWindow * const window = q->windowHandle();
} Q_ASSERT(window);
if (!window) {
void FramelessWidgetsHelper::setupFramelessHelperOnce()
{
if (m_framelessHelperSetup) {
return; return;
} }
m_framelessHelperSetup = true; window->setProperty(kInternalOptionsFlag, QVariant::fromValue(m_options));
if (m_options & Option::UseStandardWindowLayout) {
if (q->inherits("QMainWindow")) {
m_options &= ~Options(Option::UseStandardWindowLayout);
qWarning() << "\"Option::UseStandardWindowLayout\" is not compatible with QMainWindow and it's subclasses."
" Enabling this option will mess up with your main window's layout.";
}
}
if (m_options & Option::BeCompatibleWithQtFramelessWindowHint) {
Utils::tryToBeCompatibleWithQtFramelessWindowHint(window, true);
Q_ASSERT(window->flags() & Qt::FramelessWindowHint);
}
FramelessWindowsManager *manager = FramelessWindowsManager::instance(); FramelessWindowsManager *manager = FramelessWindowsManager::instance();
manager->addWindow(q->windowHandle()); manager->addWindow(q->windowHandle());
connect(manager, &FramelessWindowsManager::systemThemeChanged, this, [this](){ connect(manager, &FramelessWindowsManager::systemThemeChanged, this, [this](){
if (isStandardLayout()) { if (m_options & Option::UseStandardWindowLayout) {
updateSystemTitleBarStyleSheet(); updateSystemTitleBarStyleSheet();
updateSystemButtonsIcon(); updateSystemButtonsIcon();
q->update(); q->update();
@ -305,11 +319,12 @@ void FramelessWidgetsHelper::setupFramelessHelperOnce()
connect(manager, &FramelessWindowsManager::systemMenuRequested, this, [this](const QPointF &pos){ connect(manager, &FramelessWindowsManager::systemMenuRequested, this, [this](const QPointF &pos){
QMetaObject::invokeMethod(q, "systemMenuRequested", Q_ARG(QPointF, pos)); QMetaObject::invokeMethod(q, "systemMenuRequested", Q_ARG(QPointF, pos));
}); });
setupInitialUi();
} }
void FramelessWidgetsHelper::createSystemTitleBar() void FramelessWidgetsHelper::createSystemTitleBar()
{ {
if (isCustomLayout()) { if (!(m_options & Option::UseStandardWindowLayout)) {
return; return;
} }
m_systemTitleBarWidget = new QWidget(q); m_systemTitleBarWidget = new QWidget(q);
@ -352,7 +367,7 @@ void FramelessWidgetsHelper::createSystemTitleBar()
void FramelessWidgetsHelper::createUserContentContainer() void FramelessWidgetsHelper::createUserContentContainer()
{ {
if (isCustomLayout()) { if (!(m_options & Option::UseStandardWindowLayout)) {
return; return;
} }
m_userContentContainerWidget = new QWidget(q); m_userContentContainerWidget = new QWidget(q);
@ -365,7 +380,7 @@ void FramelessWidgetsHelper::createUserContentContainer()
void FramelessWidgetsHelper::setupInitialUi() void FramelessWidgetsHelper::setupInitialUi()
{ {
if (isStandardLayout()) { if (m_options & Option::UseStandardWindowLayout) {
createSystemTitleBar(); createSystemTitleBar();
createUserContentContainer(); createUserContentContainer();
m_mainLayout = new QVBoxLayout(q); m_mainLayout = new QVBoxLayout(q);
@ -402,7 +417,7 @@ bool FramelessWidgetsHelper::isInTitleBarDraggableArea(const QPoint &pos) const
} }
return region; return region;
} }
if (isStandardLayout()) { if (m_options & Option::UseStandardWindowLayout) {
QRegion region = mapGeometryToScene(m_systemTitleBarWidget); QRegion region = mapGeometryToScene(m_systemTitleBarWidget);
region -= mapGeometryToScene(m_systemMinimizeButton); region -= mapGeometryToScene(m_systemMinimizeButton);
region -= mapGeometryToScene(m_systemMaximizeButton); region -= mapGeometryToScene(m_systemMaximizeButton);
@ -417,22 +432,13 @@ bool FramelessWidgetsHelper::isInTitleBarDraggableArea(const QPoint &pos) const
bool FramelessWidgetsHelper::shouldDrawFrameBorder() const bool FramelessWidgetsHelper::shouldDrawFrameBorder() const
{ {
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
return (Utils::isWindowFrameBorderVisible() && !Utils::isWin11OrGreater() && isNormal()); return (Utils::isWindowFrameBorderVisible() && !Utils::isWin11OrGreater()
&& isNormal() && !(m_options & Option::DontDrawTopWindowFrameBorder));
#else #else
return false; return false;
#endif #endif
} }
bool FramelessWidgetsHelper::isStandardLayout() const
{
return (m_windowLayout == WindowLayout::Standard);
}
bool FramelessWidgetsHelper::isCustomLayout() const
{
return (m_windowLayout == WindowLayout::Custom);
}
bool FramelessWidgetsHelper::shouldIgnoreMouseEvents(const QPoint &pos) const bool FramelessWidgetsHelper::shouldIgnoreMouseEvents(const QPoint &pos) const
{ {
return (isNormal() && ((pos.x() < kDefaultResizeBorderThickness) return (isNormal() && ((pos.x() < kDefaultResizeBorderThickness)
@ -449,7 +455,7 @@ void FramelessWidgetsHelper::updateContentsMargins()
void FramelessWidgetsHelper::updateSystemTitleBarStyleSheet() void FramelessWidgetsHelper::updateSystemTitleBarStyleSheet()
{ {
if (isCustomLayout()) { if (!(m_options & Option::UseStandardWindowLayout)) {
return; return;
} }
const bool active = q->isActiveWindow(); const bool active = q->isActiveWindow();
@ -484,7 +490,7 @@ void FramelessWidgetsHelper::updateSystemTitleBarStyleSheet()
void FramelessWidgetsHelper::updateSystemButtonsIcon() void FramelessWidgetsHelper::updateSystemButtonsIcon()
{ {
if (isCustomLayout()) { if (!(m_options & Option::UseStandardWindowLayout)) {
return; return;
} }
const SystemTheme theme = ((Utils::shouldAppsUseDarkMode() || Utils::isTitleBarColorized()) ? SystemTheme::Dark : SystemTheme::Light); const SystemTheme theme = ((Utils::shouldAppsUseDarkMode() || Utils::isTitleBarColorized()) ? SystemTheme::Dark : SystemTheme::Light);

View File

@ -45,7 +45,7 @@ class FRAMELESSHELPER_WIDGETS_API FramelessWidgetsHelper : public QObject
Q_DISABLE_COPY_MOVE(FramelessWidgetsHelper) Q_DISABLE_COPY_MOVE(FramelessWidgetsHelper)
public: public:
explicit FramelessWidgetsHelper(QWidget *q, const WindowLayout wl); explicit FramelessWidgetsHelper(QWidget *q, const Options options = {});
~FramelessWidgetsHelper() override; ~FramelessWidgetsHelper() override;
Q_NODISCARD Q_INVOKABLE bool isNormal() const; Q_NODISCARD Q_INVOKABLE bool isNormal() const;
@ -61,7 +61,6 @@ public:
Q_INVOKABLE void toggleMaximized(); Q_INVOKABLE void toggleMaximized();
Q_INVOKABLE void showEventHandler(QShowEvent *event);
Q_INVOKABLE void changeEventHandler(QEvent *event); Q_INVOKABLE void changeEventHandler(QEvent *event);
Q_INVOKABLE void paintEventHandler(QPaintEvent *event); Q_INVOKABLE void paintEventHandler(QPaintEvent *event);
Q_INVOKABLE void mousePressEventHandler(QMouseEvent *event); Q_INVOKABLE void mousePressEventHandler(QMouseEvent *event);
@ -70,14 +69,11 @@ public:
private: private:
void initialize(); void initialize();
void setupFramelessHelperOnce();
void createSystemTitleBar(); void createSystemTitleBar();
void createUserContentContainer(); void createUserContentContainer();
void setupInitialUi(); void setupInitialUi();
Q_NODISCARD bool isInTitleBarDraggableArea(const QPoint &pos) const; Q_NODISCARD bool isInTitleBarDraggableArea(const QPoint &pos) const;
Q_NODISCARD bool shouldDrawFrameBorder() const; Q_NODISCARD bool shouldDrawFrameBorder() const;
Q_NODISCARD bool isStandardLayout() const;
Q_NODISCARD bool isCustomLayout() const;
Q_NODISCARD bool shouldIgnoreMouseEvents(const QPoint &pos) const; Q_NODISCARD bool shouldIgnoreMouseEvents(const QPoint &pos) const;
private Q_SLOTS: private Q_SLOTS:
@ -88,8 +84,7 @@ private Q_SLOTS:
private: private:
QWidget *q = nullptr; QWidget *q = nullptr;
bool m_initialized = false; bool m_initialized = false;
bool m_framelessHelperSetup = false; Options m_options = {};
WindowLayout m_windowLayout = WindowLayout::Standard;
QWidget *m_systemTitleBarWidget = nullptr; QWidget *m_systemTitleBarWidget = nullptr;
QLabel *m_systemWindowTitleLabel = nullptr; QLabel *m_systemWindowTitleLabel = nullptr;
QPushButton *m_systemMinimizeButton = nullptr; QPushButton *m_systemMinimizeButton = nullptr;