Signed-off-by: Yuhang Zhao <2546789017@qq.com>
This commit is contained in:
Yuhang Zhao 2022-03-23 16:38:14 +08:00
parent 059b8d7982
commit 6ffc894213
26 changed files with 417 additions and 241 deletions

View File

@ -79,10 +79,10 @@ void MainWindow::setupUi()
setTitleBarWidget(titleBarWidget);
setHitTestVisible(titleBar->iconButton, true);
setHitTestVisible(titleBar->minimizeButton, true);
setHitTestVisible(titleBar->maximizeButton, true);
setHitTestVisible(titleBar->closeButton, true);
setHitTestVisible(titleBar->iconButton);
setHitTestVisible(titleBar->minimizeButton);
setHitTestVisible(titleBar->maximizeButton);
setHitTestVisible(titleBar->closeButton);
connect(titleBar->minimizeButton, &QPushButton::clicked, this, &MainWindow::showMinimized);
connect(titleBar->maximizeButton, &QPushButton::clicked, this, &MainWindow::toggleMaximized);

View File

@ -65,21 +65,21 @@ FramelessWindow {
title: window.title
minimizeButton {
id: minimizeButton
onClicked: FramelessUtils.showMinimized2(window)
onClicked: window.showMinimized2()
}
maximizeButton {
id: maximizeButton
onClicked: FramelessUtils.toggleMaximize(window)
onClicked: window.toggleMaximize()
}
closeButton {
id: closeButton
onClicked: window.close()
}
Component.onCompleted: {
FramelessHelper.setTitleBarItem(window, titleBar);
FramelessHelper.setHitTestVisible(window, minimizeButton, true);
FramelessHelper.setHitTestVisible(window, maximizeButton, true);
FramelessHelper.setHitTestVisible(window, closeButton, true);
window.setTitleBarItem(titleBar);
window.setHitTestVisible(minimizeButton);
window.setHitTestVisible(maximizeButton);
window.setHitTestVisible(closeButton);
}
}
}

View File

@ -148,7 +148,8 @@ enum class Option : int
DisableResizing = 0x00002000, // Disable resizing of the window.
DisableDragging = 0x00004000, // Disable dragging through the titlebar of the window.
DontTouchCursorShape = 0x00008000, // Don't change the cursor shape while the mouse is hovering above the window.
DontMoveWindowToDesktopCenter = 0x00010000 // Don't move the window to the desktop center before shown.
DontMoveWindowToDesktopCenter = 0x00010000, // Don't move the window to the desktop center before shown.
DontTreatFullScreenAsZoomed = 0x00020000 // Don't treat fullscreen as zoomed (maximized).
};
Q_DECLARE_FLAGS(Options, Option)
Q_FLAG_NS(Options)

View File

@ -46,7 +46,7 @@ public:
Q_INVOKABLE static void addWindow(QQuickWindow *window);
Q_INVOKABLE static void removeWindow(QQuickWindow *window);
Q_INVOKABLE static void setTitleBarItem(QQuickWindow *window, QQuickItem *item);
Q_INVOKABLE static void setHitTestVisible(QQuickWindow *window, QQuickItem *item, const bool visible);
Q_INVOKABLE static void setHitTestVisible(QQuickWindow *window, QQuickItem *item);
protected:
Q_NODISCARD bool eventFilter(QObject *object, QEvent *event) override;

View File

@ -52,7 +52,7 @@ public:
Q_INVOKABLE static void addWindow(QQuickWindow *window);
Q_INVOKABLE static void removeWindow(QQuickWindow *window);
Q_INVOKABLE static void setTitleBarItem(QQuickWindow *window, QQuickItem *item);
Q_INVOKABLE static void setHitTestVisible(QQuickWindow *window, QQuickItem *item, const bool visible);
Q_INVOKABLE static void setHitTestVisible(QQuickWindow *window, QQuickItem *item);
};
FRAMELESSHELPER_END_NAMESPACE

View File

@ -47,11 +47,9 @@ class FRAMELESSHELPER_QUICK_API FramelessQuickUtils : public QObject
Q_PROPERTY(qreal titleBarHeight READ titleBarHeight CONSTANT FINAL)
Q_PROPERTY(bool frameBorderVisible READ frameBorderVisible CONSTANT FINAL)
Q_PROPERTY(qreal frameBorderThickness READ frameBorderThickness CONSTANT FINAL)
Q_PROPERTY(QColor frameBorderActiveColor READ frameBorderActiveColor NOTIFY frameBorderActiveColorChanged FINAL)
Q_PROPERTY(QColor frameBorderInactiveColor READ frameBorderInactiveColor NOTIFY frameBorderInactiveColorChanged FINAL)
Q_PROPERTY(bool darkModeEnabled READ darkModeEnabled NOTIFY darkModeEnabledChanged FINAL)
Q_PROPERTY(QColor systemAccentColor READ systemAccentColor NOTIFY systemAccentColorChanged FINAL)
Q_PROPERTY(bool titleBarColorVisible READ titleBarColorVisible NOTIFY titleBarColorVisibleChanged FINAL)
Q_PROPERTY(bool titleBarColorized READ titleBarColorized NOTIFY titleBarColorizedChanged FINAL)
Q_PROPERTY(QColor defaultSystemLightColor READ defaultSystemLightColor CONSTANT FINAL)
Q_PROPERTY(QColor defaultSystemDarkColor READ defaultSystemDarkColor CONSTANT FINAL)
Q_PROPERTY(QSizeF defaultSystemButtonSize READ defaultSystemButtonSize CONSTANT FINAL)
@ -64,28 +62,18 @@ public:
Q_NODISCARD static qreal titleBarHeight();
Q_NODISCARD static bool frameBorderVisible();
Q_NODISCARD static qreal frameBorderThickness();
Q_NODISCARD static QColor frameBorderActiveColor();
Q_NODISCARD static QColor frameBorderInactiveColor();
Q_NODISCARD static bool darkModeEnabled();
Q_NODISCARD static QColor systemAccentColor();
Q_NODISCARD static bool titleBarColorVisible();
Q_NODISCARD static bool titleBarColorized();
Q_NODISCARD static QColor defaultSystemLightColor();
Q_NODISCARD static QColor defaultSystemDarkColor();
Q_NODISCARD static QSizeF defaultSystemButtonSize();
Q_NODISCARD static QSizeF defaultSystemButtonIconSize();
Q_INVOKABLE static void showMinimized2(QQuickWindow *window);
Q_INVOKABLE static void toggleMaximize(QQuickWindow *window);
Q_INVOKABLE static void showSystemMenu(QQuickWindow *window, const QPoint &pos);
Q_INVOKABLE static void startSystemMove2(QQuickWindow *window);
Q_INVOKABLE static void startSystemResize2(QQuickWindow *window, const Qt::Edges edges);
Q_SIGNALS:
void frameBorderActiveColorChanged();
void frameBorderInactiveColorChanged();
void darkModeEnabledChanged();
void systemAccentColorChanged();
void titleBarColorVisibleChanged();
void titleBarColorizedChanged();
};
FRAMELESSHELPER_END_NAMESPACE

View File

@ -25,6 +25,7 @@
#pragma once
#include "framelesshelperquick_global.h"
#include <QtQuick/qquickitem.h>
#include <QtQuick/qquickwindow.h>
FRAMELESSHELPER_BEGIN_NAMESPACE
@ -34,18 +35,34 @@ class FramelessQuickWindowPrivate;
class FRAMELESSHELPER_QUICK_API FramelessQuickWindow : public QQuickWindow
{
Q_OBJECT
#ifdef QML_NAMED_ELEMENT
QML_NAMED_ELEMENT(FramelessWindow)
#endif
Q_DECLARE_PRIVATE(FramelessQuickWindow)
Q_DISABLE_COPY_MOVE(FramelessQuickWindow)
Q_PROPERTY(bool zoomed READ zoomed NOTIFY zoomedChanged FINAL)
Q_PROPERTY(QColor frameBorderColor READ frameBorderColor NOTIFY frameBorderColorChanged FINAL)
public:
explicit FramelessQuickWindow(QWindow *parent = nullptr);
explicit FramelessQuickWindow(QWindow *parent = nullptr, const Options options = {});
~FramelessQuickWindow() override;
Q_NODISCARD bool zoomed() const;
Q_NODISCARD QColor frameBorderColor() const;
public Q_SLOTS:
void showMinimized2();
void toggleMaximize();
void toggleFullScreen();
void showSystemMenu(const QPoint &pos);
void startSystemMove2();
void startSystemResize2(const Qt::Edges edges);
void setTitleBarItem(QQuickItem *item);
void setHitTestVisible(QQuickItem *item);
Q_SIGNALS:
void zoomedChanged();
void frameBorderColorChanged();
private:
QScopedPointer<FramelessQuickWindowPrivate> d_ptr;

View File

@ -47,9 +47,10 @@ public:
void setTitleBarWidget(QWidget *widget);
Q_NODISCARD QWidget *titleBarWidget() const;
Q_INVOKABLE void setHitTestVisible(QWidget *widget, const bool visible);
Q_INVOKABLE void setHitTestVisible(QWidget *widget);
Q_INVOKABLE void toggleMaximized();
Q_INVOKABLE void toggleFullScreen();
protected:
void changeEvent(QEvent *event) override;

View File

@ -51,9 +51,10 @@ public:
void setContentWidget(QWidget *widget);
Q_NODISCARD QWidget *contentWidget() const;
Q_INVOKABLE void setHitTestVisible(QWidget *widget, const bool visible);
Q_INVOKABLE void setHitTestVisible(QWidget *widget);
Q_INVOKABLE void toggleMaximized();
Q_INVOKABLE void toggleFullScreen();
protected:
void changeEvent(QEvent *event) override;

View File

@ -57,9 +57,10 @@ public:
Q_INVOKABLE void setContentWidget(QWidget *widget);
Q_NODISCARD Q_INVOKABLE QWidget *contentWidget() const;
Q_INVOKABLE void setHitTestVisible(QWidget *widget, const bool visible);
Q_INVOKABLE void setHitTestVisible(QWidget *widget);
Q_INVOKABLE void toggleMaximized();
Q_INVOKABLE void toggleFullScreen();
Q_INVOKABLE void changeEventHandler(QEvent *event);
Q_INVOKABLE void paintEventHandler(QPaintEvent *event);
@ -96,6 +97,8 @@ private:
QWidgetList m_hitTestVisibleWidgets = {};
QWidget *m_userContentContainerWidget = nullptr;
QVBoxLayout *m_userContentContainerLayout = nullptr;
Qt::WindowStates m_savedWindowState = {};
QWindow *m_window = nullptr;
};
FRAMELESSHELPER_END_NAMESPACE

View File

@ -30,11 +30,17 @@
FRAMELESSHELPER_BEGIN_NAMESPACE
struct QtHelperInternalData
{
QWindow *window = nullptr;
FramelessHelperQt *qtFramelessHelper = nullptr;
Options options = {};
};
struct QtHelper
{
QMutex mutex = {};
QHash<QWindow *, FramelessHelperQt *> qtFramelessHelpers = {};
QHash<QWindow *, Options> options = {};
QHash<QWindow *, QtHelperInternalData> data = {};
explicit QtHelper() = default;
~QtHelper() = default;
@ -56,18 +62,19 @@ void FramelessHelperQt::addWindow(QWindow *window)
return;
}
g_qtHelper()->mutex.lock();
if (g_qtHelper()->qtFramelessHelpers.contains(window)) {
if (g_qtHelper()->data.contains(window)) {
g_qtHelper()->mutex.unlock();
return;
}
const auto options = qvariant_cast<Options>(window->property(kInternalOptionsFlag));
g_qtHelper()->options.insert(window, options);
QtHelperInternalData data = {};
data.window = window;
// Give it a parent so that it can be deleted even if we forget to do so.
const auto qtFramelessHelper = new FramelessHelperQt(window);
g_qtHelper()->qtFramelessHelpers.insert(window, qtFramelessHelper);
data.qtFramelessHelper = new FramelessHelperQt(window);
data.options = qvariant_cast<Options>(window->property(kInternalOptionsFlag));
g_qtHelper()->data.insert(window, data);
g_qtHelper()->mutex.unlock();
window->setFlags(window->flags() | Qt::FramelessWindowHint);
window->installEventFilter(qtFramelessHelper);
window->installEventFilter(data.qtFramelessHelper);
}
void FramelessHelperQt::removeWindow(QWindow *window)
@ -77,17 +84,15 @@ void FramelessHelperQt::removeWindow(QWindow *window)
return;
}
g_qtHelper()->mutex.lock();
if (!g_qtHelper()->qtFramelessHelpers.contains(window)) {
if (!g_qtHelper()->data.contains(window)) {
g_qtHelper()->mutex.unlock();
return;
}
g_qtHelper()->options.remove(window);
FramelessHelperQt *qtFramelessHelper = g_qtHelper()->qtFramelessHelpers.value(window);
g_qtHelper()->qtFramelessHelpers.remove(window);
const QtHelperInternalData data = g_qtHelper()->data.value(window);
g_qtHelper()->data.remove(window);
g_qtHelper()->mutex.unlock();
window->removeEventFilter(qtFramelessHelper);
delete qtFramelessHelper;
qtFramelessHelper = nullptr;
window->removeEventFilter(data.qtFramelessHelper);
delete data.qtFramelessHelper;
window->setFlags(window->flags() & ~Qt::FramelessWindowHint);
}
@ -109,11 +114,11 @@ bool FramelessHelperQt::eventFilter(QObject *object, QEvent *event)
}
const auto window = qobject_cast<QWindow *>(object);
g_qtHelper()->mutex.lock();
if (!g_qtHelper()->qtFramelessHelpers.contains(window)) {
if (!g_qtHelper()->data.contains(window)) {
g_qtHelper()->mutex.unlock();
return false;
}
const Options options = g_qtHelper()->options.value(window);
const QtHelperInternalData data = g_qtHelper()->data.value(window);
g_qtHelper()->mutex.unlock();
if (Utils::isWindowFixedSize(window)) {
return false;
@ -126,7 +131,7 @@ bool FramelessHelperQt::eventFilter(QObject *object, QEvent *event)
#endif
switch (type) {
case QEvent::MouseMove: {
if (options & Option::DontTouchCursorShape) {
if (data.options & Option::DontTouchCursorShape) {
return false;
}
const Qt::CursorShape cs = Utils::calculateCursorShape(window, scenePos);

View File

@ -53,11 +53,18 @@ Q_DECLARE_METATYPE(QMargins)
FRAMELESSHELPER_BEGIN_NAMESPACE
struct Win32UtilsInternalData
{
HWND hwnd = nullptr;
QWindow *window = nullptr;
WNDPROC originalWindowProc = nullptr;
Options options = {};
};
struct Win32UtilsHelper
{
QMutex mutex = {};
QHash<HWND, WNDPROC> qtWindowProcs = {};
QHash<HWND, QWindow *> windowMapping = {};
QHash<HWND, Win32UtilsInternalData> data = {};
explicit Win32UtilsHelper() = default;
~Win32UtilsHelper() = default;
@ -178,28 +185,28 @@ static const QString successErrorText = QStringLiteral("The operation completed
(const HWND hWnd, const UINT uMsg, const WPARAM wParam, const LPARAM lParam)
{
g_utilsHelper()->mutex.lock();
if (!g_utilsHelper()->qtWindowProcs.contains(hWnd)) {
if (!g_utilsHelper()->data.contains(hWnd)) {
g_utilsHelper()->mutex.unlock();
return DefWindowProcW(hWnd, uMsg, wParam, lParam);
}
const QWindow * const window = g_utilsHelper()->windowMapping.value(hWnd);
Q_ASSERT(window);
if (!window) {
const Win32UtilsInternalData data = g_utilsHelper()->data.value(hWnd);
g_utilsHelper()->mutex.unlock();
Q_ASSERT(data.window);
if (!data.window) {
return DefWindowProcW(hWnd, uMsg, wParam, lParam);
}
g_utilsHelper()->mutex.unlock();
const auto winId = reinterpret_cast<WId>(hWnd);
const auto getGlobalPosFromMouse = [lParam]() -> QPoint {
return {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
};
const auto getGlobalPosFromKeyboard = [hWnd, winId]() -> QPoint {
const auto getGlobalPosFromKeyboard = [hWnd, winId, &data]() -> QPoint {
RECT windowPos = {};
if (GetWindowRect(hWnd, &windowPos) == FALSE) {
qWarning() << Utils::getSystemErrorMessage(QStringLiteral("GetWindowRect"));
return {};
}
const bool maxOrFull = (IsMaximized(hWnd) || Utils::isFullScreen(winId));
const bool maxOrFull = (IsMaximized(hWnd) ||
((data.options & Option::DontTreatFullScreenAsZoomed) ? false : Utils::isFullScreen(winId)));
const int frameSizeX = Utils::getResizeBorderThickness(winId, true, true);
const bool frameBorderVisible = Utils::isWindowFrameBorderVisible();
const int horizontalOffset = ((maxOrFull || !frameBorderVisible) ? 0 : frameSizeX);
@ -244,21 +251,18 @@ static const QString successErrorText = QStringLiteral("The operation completed
}
}
if (shouldShowSystemMenu) {
Utils::showSystemMenu(window, globalPos);
Utils::showSystemMenu(data.window, globalPos);
// QPA's internal code will handle system menu events separately, and its
// behavior is not what we would want to see because it doesn't know our
// window doesn't have any window frame now, so return early here to avoid
// entering Qt's own handling logic.
return 0; // Return 0 means we have handled this event.
}
g_utilsHelper()->mutex.lock();
const WNDPROC originalWindowProc = g_utilsHelper()->qtWindowProcs.value(hWnd);
g_utilsHelper()->mutex.unlock();
Q_ASSERT(originalWindowProc);
if (originalWindowProc) {
Q_ASSERT(data.originalWindowProc);
if (data.originalWindowProc) {
// Hand over to Qt's original window proc function for events we are not
// interested in.
return CallWindowProcW(originalWindowProc, hWnd, uMsg, wParam, lParam);
return CallWindowProcW(data.originalWindowProc, hWnd, uMsg, wParam, lParam);
} else {
return DefWindowProcW(hWnd, uMsg, wParam, lParam);
}
@ -519,9 +523,10 @@ void Utils::showSystemMenu(const QWindow *window, const QPoint &pos)
}
return true;
};
const bool maxOrFull = (IsMaximized(hWnd) || isFullScreen(reinterpret_cast<WId>(hWnd)));
const bool fixedSize = isWindowFixedSize(window);
const auto options = qvariant_cast<Options>(window->property(kInternalOptionsFlag));
const bool maxOrFull = (IsMaximized(hWnd) ||
((options & Option::DontTreatFullScreenAsZoomed) ? false : isFullScreen(reinterpret_cast<WId>(hWnd))));
const bool fixedSize = isWindowFixedSize(window);
if (!setState(SC_RESTORE, (maxOrFull && !fixedSize), true)) {
return;
}
@ -1027,7 +1032,7 @@ void Utils::installSystemMenuHook(const QWindow *window)
}
const auto hwnd = reinterpret_cast<HWND>(window->winId());
QMutexLocker locker(&g_utilsHelper()->mutex);
if (g_utilsHelper()->qtWindowProcs.contains(hwnd)) {
if (g_utilsHelper()->data.contains(hwnd)) {
return;
}
SetLastError(ERROR_SUCCESS);
@ -1043,8 +1048,12 @@ void Utils::installSystemMenuHook(const QWindow *window)
return;
}
//triggerFrameChange(winId);
g_utilsHelper()->qtWindowProcs.insert(hwnd, originalWindowProc);
g_utilsHelper()->windowMapping.insert(hwnd, const_cast<QWindow *>(window));
Win32UtilsInternalData data = {};
data.hwnd = hwnd;
data.window = const_cast<QWindow *>(window);
data.originalWindowProc = originalWindowProc;
data.options = qvariant_cast<Options>(window->property(kInternalOptionsFlag));
g_utilsHelper()->data.insert(hwnd, data);
}
void Utils::uninstallSystemMenuHook(const WId winId)
@ -1055,22 +1064,21 @@ void Utils::uninstallSystemMenuHook(const WId winId)
}
const auto hwnd = reinterpret_cast<HWND>(winId);
QMutexLocker locker(&g_utilsHelper()->mutex);
if (!g_utilsHelper()->qtWindowProcs.contains(hwnd)) {
if (!g_utilsHelper()->data.contains(hwnd)) {
return;
}
const WNDPROC originalWindowProc = g_utilsHelper()->qtWindowProcs.value(hwnd);
Q_ASSERT(originalWindowProc);
if (!originalWindowProc) {
const Win32UtilsInternalData data = g_utilsHelper()->data.value(hwnd);
Q_ASSERT(data.originalWindowProc);
if (!data.originalWindowProc) {
return;
}
SetLastError(ERROR_SUCCESS);
if (SetWindowLongPtrW(hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(originalWindowProc)) == 0) {
if (SetWindowLongPtrW(hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(data.originalWindowProc)) == 0) {
qWarning() << getSystemErrorMessage(QStringLiteral("SetWindowLongPtrW"));
return;
}
//triggerFrameChange(winId);
g_utilsHelper()->qtWindowProcs.remove(hwnd);
g_utilsHelper()->windowMapping.remove(hwnd);
g_utilsHelper()->data.remove(hwnd);
}
void Utils::sendMouseReleaseEvent()

View File

@ -58,7 +58,7 @@ if(MSVC)
_WIN32_IE=${_WIN32_WINNT_WIN10} NTDDI_VERSION=${NTDDI_WIN10_CO}
)
target_compile_options(${SUB_PROJ_NAME} PRIVATE
/W4 /WX
/utf-8 /W4 /WX
)
else()
target_compile_options(${SUB_PROJ_NAME} PRIVATE

View File

@ -147,7 +147,7 @@ void FramelessQuickEventFilter::setTitleBarItem(QQuickWindow *window, QQuickItem
g_data()->data[window].titleBarItem = item;
}
void FramelessQuickEventFilter::setHitTestVisible(QQuickWindow *window, QQuickItem *item, const bool visible)
void FramelessQuickEventFilter::setHitTestVisible(QQuickWindow *window, QQuickItem *item)
{
Q_ASSERT(window);
Q_ASSERT(item);
@ -159,11 +159,12 @@ void FramelessQuickEventFilter::setHitTestVisible(QQuickWindow *window, QQuickIt
return;
}
auto &items = g_data()->data[window].hitTestVisibleItems;
static constexpr const bool visible = true;
const bool exists = items.contains(item);
if (visible && !exists) {
items.append(item);
}
if (!visible && exists) {
if constexpr (!visible && exists) {
items.removeAll(item);
}
}
@ -246,7 +247,7 @@ bool FramelessQuickEventFilter::eventFilter(QObject *object, QEvent *event)
return true;
}
case QEvent::MouseButtonDblClick: {
if ((options & Option::NoDoubleClickMaximizeToggle) || (options & Option::DisableResizing)) {
if ((options & Option::NoDoubleClickMaximizeToggle) || Utils::isWindowFixedSize(window)) {
return false;
}
if (button != Qt::LeftButton) {
@ -255,7 +256,8 @@ bool FramelessQuickEventFilter::eventFilter(QObject *object, QEvent *event)
if (!titleBar) {
return false;
}
if ((visibility == QQuickWindow::Maximized) || (visibility == QQuickWindow::FullScreen)) {
if ((visibility == QQuickWindow::Maximized) ||
((options & Option::DontTreatFullScreenAsZoomed) ? false : (visibility == QQuickWindow::FullScreen))) {
window->showNormal();
} else {
window->showMaximized();

View File

@ -63,14 +63,14 @@ void FramelessQuickHelper::setTitleBarItem(QQuickWindow *window, QQuickItem *ite
FramelessQuickEventFilter::setTitleBarItem(window, item);
}
void FramelessQuickHelper::setHitTestVisible(QQuickWindow *window, QQuickItem *item, const bool visible)
void FramelessQuickHelper::setHitTestVisible(QQuickWindow *window, QQuickItem *item)
{
Q_ASSERT(window);
Q_ASSERT(item);
if (!window || !item) {
return;
}
FramelessQuickEventFilter::setHitTestVisible(window, item, visible);
FramelessQuickEventFilter::setHitTestVisible(window, item);
}
FRAMELESSHELPER_END_NAMESPACE

View File

@ -23,27 +23,21 @@
*/
#include "framelessquickutils.h"
#include <QtQuick/qquickwindow.h>
#if (QT_VERSION >= QT_VERSION_CHECK(6, 2, 1))
# include <QtGui/qpa/qplatformtheme.h>
# include <QtGui/private/qguiapplication_p.h>
#endif
#include <framelesswindowsmanager.h>
#include <utils.h>
#ifdef Q_OS_WINDOWS
# include <framelesshelper_windows.h>
#endif
FRAMELESSHELPER_BEGIN_NAMESPACE
FramelessQuickUtils::FramelessQuickUtils(QObject *parent) : QObject(parent)
{
connect(FramelessWindowsManager::instance(), &FramelessWindowsManager::systemThemeChanged, this, [this](){
Q_EMIT frameBorderActiveColorChanged();
Q_EMIT frameBorderInactiveColorChanged();
Q_EMIT darkModeEnabledChanged();
Q_EMIT systemAccentColorChanged();
Q_EMIT titleBarColorVisibleChanged();
Q_EMIT titleBarColorizedChanged();
});
}
@ -51,7 +45,7 @@ FramelessQuickUtils::~FramelessQuickUtils() = default;
qreal FramelessQuickUtils::titleBarHeight()
{
return 30;
return 30.0;
}
bool FramelessQuickUtils::frameBorderVisible()
@ -65,25 +59,7 @@ bool FramelessQuickUtils::frameBorderVisible()
qreal FramelessQuickUtils::frameBorderThickness()
{
return 1;
}
QColor FramelessQuickUtils::frameBorderActiveColor()
{
#ifdef Q_OS_WINDOWS
return Utils::getFrameBorderColor(true);
#else
return {};
#endif
}
QColor FramelessQuickUtils::frameBorderInactiveColor()
{
#ifdef Q_OS_WINDOWS
return Utils::getFrameBorderColor(false);
#else
return {};
#endif
return 1.0;
}
bool FramelessQuickUtils::darkModeEnabled()
@ -111,7 +87,7 @@ QColor FramelessQuickUtils::systemAccentColor()
#endif
}
bool FramelessQuickUtils::titleBarColorVisible()
bool FramelessQuickUtils::titleBarColorized()
{
#ifdef Q_OS_WINDOWS
return Utils::isTitleBarColorized();
@ -140,81 +116,4 @@ QSizeF FramelessQuickUtils::defaultSystemButtonIconSize()
return kDefaultSystemButtonIconSize;
}
void FramelessQuickUtils::showMinimized2(QQuickWindow *window)
{
Q_ASSERT(window);
if (!window) {
return;
}
#ifdef Q_OS_WINDOWS
// Work-around a QtQuick bug: https://bugreports.qt.io/browse/QTBUG-69711
// Don't use "SW_SHOWMINIMIZED" because it will activate the current window
// instead of the next window in the Z order, which is not the default behavior
// of native Win32 applications.
ShowWindow(reinterpret_cast<HWND>(window->winId()), SW_MINIMIZE);
#else
window->showMinimized();
#endif
}
void FramelessQuickUtils::toggleMaximize(QQuickWindow *window)
{
Q_ASSERT(window);
if (!window) {
return;
}
const QQuickWindow::Visibility visibility = window->visibility();
if ((visibility == QQuickWindow::Maximized) || (visibility == QQuickWindow::FullScreen)) {
window->showNormal();
} else {
window->showMaximized();
}
}
void FramelessQuickUtils::showSystemMenu(QQuickWindow *window, const QPoint &pos)
{
Q_ASSERT(window);
if (!window) {
return;
}
#ifdef Q_OS_WINDOWS
# if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
const QPoint globalPos = window->mapToGlobal(pos);
# else
const QPoint globalPos = window->mapToGlobal(pos);
# endif
const QPoint nativePos = QPointF(QPointF(globalPos) * window->effectiveDevicePixelRatio()).toPoint();
Utils::showSystemMenu(window, nativePos);
#endif
}
void FramelessQuickUtils::startSystemMove2(QQuickWindow *window)
{
Q_ASSERT(window);
if (!window) {
return;
}
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
window->startSystemMove();
#else
Utils::startSystemMove(window);
#endif
}
void FramelessQuickUtils::startSystemResize2(QQuickWindow *window, const Qt::Edges edges)
{
Q_ASSERT(window);
if (!window) {
return;
}
if (edges == Qt::Edges{}) {
return;
}
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
window->startSystemResize(edges);
#else
Utils::startSystemResize(window, edges);
#endif
}
FRAMELESSHELPER_END_NAMESPACE

View File

@ -29,16 +29,18 @@
#include <QtQuick/private/qquickanchors_p.h>
#include <framelesswindowsmanager.h>
#include <utils.h>
#include "framelessquickhelper.h"
FRAMELESSHELPER_BEGIN_NAMESPACE
FramelessQuickWindowPrivate::FramelessQuickWindowPrivate(FramelessQuickWindow *q) : QObject(q)
FramelessQuickWindowPrivate::FramelessQuickWindowPrivate(FramelessQuickWindow *q, const Options options) : QObject(q)
{
Q_ASSERT(q);
if (!q) {
return;
}
q_ptr = q;
m_options = options;
initialize();
}
@ -48,45 +50,191 @@ bool FramelessQuickWindowPrivate::isZoomed() const
{
Q_Q(const FramelessQuickWindow);
const FramelessQuickWindow::Visibility visibility = q->visibility();
return ((visibility == FramelessQuickWindow::Maximized) || (visibility == FramelessQuickWindow::FullScreen));
return ((visibility == FramelessQuickWindow::Maximized) ||
((m_options & Option::DontTreatFullScreenAsZoomed) ? false : (visibility == FramelessQuickWindow::FullScreen)));
}
QColor FramelessQuickWindowPrivate::getFrameBorderColor() const
{
#ifdef Q_OS_WINDOWS
Q_Q(const FramelessQuickWindow);
return Utils::getFrameBorderColor(q->isActive());
#else
return {};
#endif
}
void FramelessQuickWindowPrivate::setTitleBarItem(QQuickItem *item)
{
Q_ASSERT(item);
if (!item) {
return;
}
Q_Q(FramelessQuickWindow);
FramelessQuickHelper::setTitleBarItem(q, item);
}
void FramelessQuickWindowPrivate::setHitTestVisible(QQuickItem *item)
{
Q_ASSERT(item);
if (!item) {
return;
}
Q_Q(FramelessQuickWindow);
FramelessQuickHelper::setHitTestVisible(q, item);
}
void FramelessQuickWindowPrivate::showMinimized2()
{
Q_Q(FramelessQuickWindow);
#ifdef Q_OS_WINDOWS
// Work-around a QtQuick bug: https://bugreports.qt.io/browse/QTBUG-69711
// Don't use "SW_SHOWMINIMIZED" because it will activate the current window
// instead of the next window in the Z order, which is not the default behavior
// of native Win32 applications.
ShowWindow(reinterpret_cast<HWND>(q->winId()), SW_MINIMIZE);
#else
q->showMinimized();
#endif
}
void FramelessQuickWindowPrivate::toggleMaximize()
{
Q_Q(FramelessQuickWindow);
if (Utils::isWindowFixedSize(q)) {
return;
}
if (isZoomed()) {
q->showNormal();
} else {
q->showMaximized();
}
}
void FramelessQuickWindowPrivate::toggleFullScreen()
{
Q_Q(FramelessQuickWindow);
if (Utils::isWindowFixedSize(q)) {
return;
}
const QWindow::Visibility visibility = q->visibility();
if (visibility == QWindow::FullScreen) {
q->setVisibility(m_savedVisibility);
} else {
m_savedVisibility = visibility;
q->showFullScreen();
}
}
void FramelessQuickWindowPrivate::showSystemMenu(const QPoint &pos)
{
#ifdef Q_OS_WINDOWS
Q_Q(FramelessQuickWindow);
# if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
const QPoint globalPos = q->mapToGlobal(pos);
# else
const QPoint globalPos = q->mapToGlobal(pos);
# endif
const QPoint nativePos = QPointF(QPointF(globalPos) * q->effectiveDevicePixelRatio()).toPoint();
Utils::showSystemMenu(q, nativePos);
#endif
}
void FramelessQuickWindowPrivate::startSystemMove2()
{
Q_Q(FramelessQuickWindow);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
q->startSystemMove();
#else
Utils::startSystemMove(q);
#endif
}
void FramelessQuickWindowPrivate::startSystemResize2(const Qt::Edges edges)
{
if (edges == Qt::Edges{}) {
return;
}
Q_Q(FramelessQuickWindow);
if (Utils::isWindowFixedSize(q)) {
return;
}
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
q->startSystemResize(edges);
#else
Utils::startSystemResize(q, edges);
#endif
}
void FramelessQuickWindowPrivate::initialize()
{
if (m_initialized) {
return;
}
m_initialized = true;
Q_Q(FramelessQuickWindow);
FramelessWindowsManager * const manager = FramelessWindowsManager::instance();
manager->addWindow(q);
q->setProperty(kInternalOptionsFlag, QVariant::fromValue(m_options));
FramelessQuickHelper::addWindow(q);
#ifdef Q_OS_WINDOWS
if (isFrameBorderVisible()) {
QQuickItem * const rootItem = q->contentItem();
const QQuickItemPrivate * const rootItemPrivate = QQuickItemPrivate::get(rootItem);
m_topBorderRectangle.reset(new QQuickRectangle(rootItem));
updateTopBorderHeight();
updateTopBorderColor();
connect(q, &FramelessQuickWindow::visibilityChanged, this, &FramelessQuickWindowPrivate::updateTopBorderHeight);
connect(q, &FramelessQuickWindow::visibilityChanged, this, [this, q](){
updateTopBorderHeight();
Q_EMIT q->zoomedChanged();
});
connect(q, &FramelessQuickWindow::activeChanged, this, &FramelessQuickWindowPrivate::updateTopBorderColor);
connect(manager, &FramelessWindowsManager::systemThemeChanged, this, &FramelessQuickWindowPrivate::updateTopBorderColor);
connect(FramelessWindowsManager::instance(), &FramelessWindowsManager::systemThemeChanged, this, [this, q](){
updateTopBorderColor();
Q_EMIT q->frameBorderColorChanged();
});
const auto topBorderAnchors = new QQuickAnchors(m_topBorderRectangle.data(), m_topBorderRectangle.data());
topBorderAnchors->setTop(rootItemPrivate->top());
topBorderAnchors->setLeft(rootItemPrivate->left());
topBorderAnchors->setRight(rootItemPrivate->right());
connect(q, &FramelessQuickWindow::visibilityChanged, q, &FramelessQuickWindow::zoomedChanged);
}
#endif
Utils::moveWindowToDesktopCenter([q]() -> QScreen * { return q->screen(); },
[q]() -> QSize { return q->size(); },
[q](const int x, const int y) -> void {
q->setX(x);
q->setY(y);
}, true);
}
bool FramelessQuickWindowPrivate::isFrameBorderVisible() const
{
#ifdef Q_OS_WINDOWS
return (Utils::isWindowFrameBorderVisible() && !Utils::isWin11OrGreater());
#else
return false;
#endif
}
void FramelessQuickWindowPrivate::updateTopBorderColor()
{
Q_Q(FramelessQuickWindow);
m_topBorderRectangle->setColor(Utils::getFrameBorderColor(q->isActive()));
if (!isFrameBorderVisible()) {
return;
}
m_topBorderRectangle->setColor(getFrameBorderColor());
}
void FramelessQuickWindowPrivate::updateTopBorderHeight()
{
if (!isFrameBorderVisible()) {
return;
}
Q_Q(FramelessQuickWindow);
const qreal newHeight = ((q->visibility() == FramelessQuickWindow::Windowed) ? 1.0 : 0.0);
m_topBorderRectangle->setHeight(newHeight);
}
FramelessQuickWindow::FramelessQuickWindow(QWindow *parent) : QQuickWindow(parent)
FramelessQuickWindow::FramelessQuickWindow(QWindow *parent, const Options options) : QQuickWindow(parent)
{
d_ptr.reset(new FramelessQuickWindowPrivate(this));
d_ptr.reset(new FramelessQuickWindowPrivate(this, options));
}
FramelessQuickWindow::~FramelessQuickWindow() = default;
@ -97,4 +245,66 @@ bool FramelessQuickWindow::zoomed() const
return d->isZoomed();
}
QColor FramelessQuickWindow::frameBorderColor() const
{
Q_D(const FramelessQuickWindow);
return d->getFrameBorderColor();
}
void FramelessQuickWindow::setTitleBarItem(QQuickItem *item)
{
Q_ASSERT(item);
if (!item) {
return;
}
Q_D(FramelessQuickWindow);
d->setTitleBarItem(item);
}
void FramelessQuickWindow::setHitTestVisible(QQuickItem *item)
{
Q_ASSERT(item);
if (!item) {
return;
}
Q_D(FramelessQuickWindow);
d->setHitTestVisible(item);
}
void FramelessQuickWindow::showMinimized2()
{
Q_D(FramelessQuickWindow);
d->showMinimized2();
}
void FramelessQuickWindow::toggleMaximize()
{
Q_D(FramelessQuickWindow);
d->toggleMaximize();
}
void FramelessQuickWindow::toggleFullScreen()
{
Q_D(FramelessQuickWindow);
d->toggleFullScreen();
}
void FramelessQuickWindow::showSystemMenu(const QPoint &pos)
{
Q_D(FramelessQuickWindow);
d->showSystemMenu(pos);
}
void FramelessQuickWindow::startSystemMove2()
{
Q_D(FramelessQuickWindow);
d->startSystemMove2();
}
void FramelessQuickWindow::startSystemResize2(const Qt::Edges edges)
{
Q_D(FramelessQuickWindow);
d->startSystemResize2(edges);
}
FRAMELESSHELPER_END_NAMESPACE

View File

@ -26,8 +26,10 @@
#include "framelesshelperquick_global.h"
#include <QtCore/qobject.h>
#include <QtGui/qwindow.h>
QT_BEGIN_NAMESPACE
class QQuickItem;
class QQuickRectangle;
QT_END_NAMESPACE
@ -42,10 +44,22 @@ class FRAMELESSHELPER_QUICK_API FramelessQuickWindowPrivate : public QObject
Q_DISABLE_COPY_MOVE(FramelessQuickWindowPrivate)
public:
explicit FramelessQuickWindowPrivate(FramelessQuickWindow *q);
explicit FramelessQuickWindowPrivate(FramelessQuickWindow *q, const Options options);
~FramelessQuickWindowPrivate() override;
Q_NODISCARD bool isZoomed() const;
Q_INVOKABLE Q_NODISCARD bool isZoomed() const;
Q_INVOKABLE Q_NODISCARD QColor getFrameBorderColor() const;
Q_INVOKABLE Q_NODISCARD bool isFrameBorderVisible() const;
public Q_SLOTS:
void showMinimized2();
void toggleMaximize();
void toggleFullScreen();
void showSystemMenu(const QPoint &pos);
void startSystemMove2();
void startSystemResize2(const Qt::Edges edges);
void setTitleBarItem(QQuickItem *item);
void setHitTestVisible(QQuickItem *item);
private:
void initialize();
@ -56,7 +70,10 @@ private Q_SLOTS:
private:
FramelessQuickWindow *q_ptr = nullptr;
bool m_initialized = false;
QScopedPointer<QQuickRectangle> m_topBorderRectangle;
QWindow::Visibility m_savedVisibility = QWindow::Windowed;
Options m_options = {};
};
FRAMELESSHELPER_END_NAMESPACE

View File

@ -36,7 +36,7 @@ Button {
Image {
anchors.centerIn: parent
source: (FramelessUtils.darkModeEnabled || FramelessUtils.titleBarColorVisible)
source: (FramelessUtils.darkModeEnabled || FramelessUtils.titleBarColorized)
? "image://framelesshelper/dark/close" : "image://framelesshelper/light/close"
}
}

View File

@ -39,9 +39,9 @@ Button {
Image {
anchors.centerIn: parent
source: button.maximized ?
((FramelessUtils.darkModeEnabled || FramelessUtils.titleBarColorVisible)
((FramelessUtils.darkModeEnabled || FramelessUtils.titleBarColorized)
? "image://framelesshelper/dark/restore" : "image://framelesshelper/light/restore") :
((FramelessUtils.darkModeEnabled || FramelessUtils.titleBarColorVisible)
((FramelessUtils.darkModeEnabled || FramelessUtils.titleBarColorized)
? "image://framelesshelper/dark/maximize" : "image://framelesshelper/light/maximize")
}
}

View File

@ -36,7 +36,7 @@ Button {
Image {
anchors.centerIn: parent
source: (FramelessUtils.darkModeEnabled || FramelessUtils.titleBarColorVisible)
source: (FramelessUtils.darkModeEnabled || FramelessUtils.titleBarColorized)
? "image://framelesshelper/dark/minimize" : "image://framelesshelper/light/minimize"
}
}

View File

@ -36,7 +36,7 @@ Rectangle {
id: titleBar
height: FramelessUtils.titleBarHeight
color: titleBar.active ? (FramelessUtils.titleBarColorVisible ? FramelessUtils.systemAccentColor
color: titleBar.active ? (FramelessUtils.titleBarColorized ? FramelessUtils.systemAccentColor
: (FramelessUtils.darkModeEnabled ? "black" : "white"))
: (FramelessUtils.darkModeEnabled ? FramelessUtils.defaultSystemDarkColor : "white")
@ -44,7 +44,7 @@ Rectangle {
id: windowTitleLabel
font.pointSize: 11
color: titleBar.active ? ((FramelessUtils.darkModeEnabled
|| FramelessUtils.titleBarColorVisible) ? "white" : "black") : "darkGray"
|| FramelessUtils.titleBarColorized) ? "white" : "black") : "darkGray"
anchors {
left: parent.left
leftMargin: 10

View File

@ -41,7 +41,7 @@ target_compile_definitions(${SUB_PROJ_NAME} PRIVATE
if(MSVC)
target_compile_options(${SUB_PROJ_NAME} PRIVATE
/W4 /WX
/utf-8 /W4 /WX
)
else()
target_compile_options(${SUB_PROJ_NAME} PRIVATE

View File

@ -54,9 +54,9 @@ QWidget *FramelessMainWindow::titleBarWidget() const
return m_helper->titleBarWidget();
}
void FramelessMainWindow::setHitTestVisible(QWidget *widget, const bool visible)
void FramelessMainWindow::setHitTestVisible(QWidget *widget)
{
m_helper->setHitTestVisible(widget, visible);
m_helper->setHitTestVisible(widget);
}
void FramelessMainWindow::toggleMaximized()
@ -64,6 +64,11 @@ void FramelessMainWindow::toggleMaximized()
m_helper->toggleMaximized();
}
void FramelessMainWindow::toggleFullScreen()
{
m_helper->toggleFullScreen();
}
void FramelessMainWindow::changeEvent(QEvent *event)
{
QMainWindow::changeEvent(event);

View File

@ -64,9 +64,9 @@ QWidget *FramelessWidget::contentWidget() const
return m_helper->contentWidget();
}
void FramelessWidget::setHitTestVisible(QWidget *widget, const bool visible)
void FramelessWidget::setHitTestVisible(QWidget *widget)
{
m_helper->setHitTestVisible(widget, visible);
m_helper->setHitTestVisible(widget);
}
void FramelessWidget::toggleMaximized()
@ -74,6 +74,11 @@ void FramelessWidget::toggleMaximized()
m_helper->toggleMaximized();
}
void FramelessWidget::toggleFullScreen()
{
m_helper->toggleFullScreen();
}
void FramelessWidget::changeEvent(QEvent *event)
{
QWidget::changeEvent(event);

View File

@ -73,7 +73,7 @@ bool FramelessWidgetsHelper::isNormal() const
bool FramelessWidgetsHelper::isZoomed() const
{
return (q->isMaximized() || q->isFullScreen());
return (q->isMaximized() || ((m_options & Option::DontTreatFullScreenAsZoomed) ? false : q->isFullScreen()));
}
void FramelessWidgetsHelper::setTitleBarWidget(QWidget *widget)
@ -133,17 +133,18 @@ QWidget *FramelessWidgetsHelper::contentWidget() const
return m_userContentWidget;
}
void FramelessWidgetsHelper::setHitTestVisible(QWidget *widget, const bool visible)
void FramelessWidgetsHelper::setHitTestVisible(QWidget *widget)
{
Q_ASSERT(widget);
if (!widget) {
return;
}
static constexpr const bool visible = true;
const bool exists = m_hitTestVisibleWidgets.contains(widget);
if (visible && !exists) {
m_hitTestVisibleWidgets.append(widget);
}
if (!visible && exists) {
if constexpr (!visible && exists) {
m_hitTestVisibleWidgets.removeAll(widget);
}
}
@ -218,7 +219,7 @@ void FramelessWidgetsHelper::mousePressEventHandler(QMouseEvent *event)
if (!isInTitleBarDraggableArea(scenePos)) {
return;
}
Utils::startSystemMove(q->windowHandle());
Utils::startSystemMove(m_window);
}
void FramelessWidgetsHelper::mouseReleaseEventHandler(QMouseEvent *event)
@ -251,7 +252,7 @@ void FramelessWidgetsHelper::mouseReleaseEventHandler(QMouseEvent *event)
const QPoint globalPos = event->globalPos();
# endif
const QPoint nativePos = QPointF(QPointF(globalPos) * q->devicePixelRatioF()).toPoint();
Utils::showSystemMenu(q->windowHandle(), nativePos);
Utils::showSystemMenu(m_window, nativePos);
#endif
}
@ -261,7 +262,7 @@ void FramelessWidgetsHelper::mouseDoubleClickEventHandler(QMouseEvent *event)
if (!event) {
return;
}
if ((m_options & Option::NoDoubleClickMaximizeToggle) || (m_options & Option::DisableResizing)) {
if ((m_options & Option::NoDoubleClickMaximizeToggle) || Utils::isWindowFixedSize(m_window)) {
return;
}
if (event->button() != Qt::LeftButton) {
@ -295,12 +296,12 @@ void FramelessWidgetsHelper::initialize()
// Force the widget become a native window now so that we can deal with its
// win32 events as soon as possible.
q->setAttribute(Qt::WA_NativeWindow);
QWindow * const window = q->windowHandle();
Q_ASSERT(window);
if (!window) {
m_window = q->windowHandle();
Q_ASSERT(m_window);
if (!m_window) {
return;
}
window->setProperty(kInternalOptionsFlag, QVariant::fromValue(m_options));
m_window->setProperty(kInternalOptionsFlag, QVariant::fromValue(m_options));
if (m_options & Option::UseStandardWindowLayout) {
if (q->inherits(QT_MAINWINDOW_CLASS_NAME)) {
m_options &= ~Options(Option::UseStandardWindowLayout);
@ -315,7 +316,7 @@ void FramelessWidgetsHelper::initialize()
true);
}
FramelessWindowsManager *manager = FramelessWindowsManager::instance();
manager->addWindow(window);
manager->addWindow(m_window);
connect(manager, &FramelessWindowsManager::systemThemeChanged, this, [this](){
if (m_options & Option::UseStandardWindowLayout) {
updateSystemTitleBarStyleSheet();
@ -327,12 +328,11 @@ void FramelessWidgetsHelper::initialize()
setupInitialUi();
if (!(m_options & Option::DontMoveWindowToDesktopCenter)) {
Utils::moveWindowToDesktopCenter(
[this, window]() -> QScreen * {
[this]() -> QScreen * {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
Q_UNUSED(window);
return q->screen();
#else
return window->screen();
return m_window->screen();
#endif
},
[this]() -> QSize { return q->size(); },
@ -525,7 +525,7 @@ void FramelessWidgetsHelper::updateSystemButtonsIcon()
void FramelessWidgetsHelper::toggleMaximized()
{
if (m_options & Option::DisableResizing) {
if (Utils::isWindowFixedSize(m_window)) {
return;
}
if (isZoomed()) {
@ -535,4 +535,18 @@ void FramelessWidgetsHelper::toggleMaximized()
}
}
void FramelessWidgetsHelper::toggleFullScreen()
{
if (Utils::isWindowFixedSize(m_window)) {
return;
}
const Qt::WindowStates windowState = q->windowState();
if (windowState & Qt::WindowFullScreen) {
q->setWindowState(m_savedWindowState);
} else {
m_savedWindowState = windowState;
q->showFullScreen();
}
}
FRAMELESSHELPER_END_NAMESPACE