improve dpi change experience

Signed-off-by: Yuhang Zhao <2546789017@qq.com>
This commit is contained in:
Yuhang Zhao 2023-05-13 11:30:54 +08:00
parent 250aff0faf
commit a3cfa59d1d
7 changed files with 149 additions and 24 deletions

View File

@ -61,6 +61,7 @@ using GetPropertyCallback = std::function<QVariant(const QByteArray &, const QVa
using SetCursorCallback = std::function<void(const QCursor &)>; using SetCursorCallback = std::function<void(const QCursor &)>;
using UnsetCursorCallback = std::function<void()>; using UnsetCursorCallback = std::function<void()>;
using GetWidgetHandleCallback = std::function<QObject *()>; using GetWidgetHandleCallback = std::function<QObject *()>;
using ForceChildrenRepaintCallback = std::function<void(const int)>;
struct SystemParameters struct SystemParameters
{ {
@ -90,6 +91,7 @@ struct SystemParameters
SetCursorCallback setCursor = nullptr; SetCursorCallback setCursor = nullptr;
UnsetCursorCallback unsetCursor = nullptr; UnsetCursorCallback unsetCursor = nullptr;
GetWidgetHandleCallback getWidgetHandle = nullptr; GetWidgetHandleCallback getWidgetHandle = nullptr;
ForceChildrenRepaintCallback forceChildrenRepaint = nullptr;
}; };
using FramelessParams = SystemParameters *; using FramelessParams = SystemParameters *;

View File

@ -88,6 +88,8 @@ public:
Q_NODISCARD bool isReady() const; Q_NODISCARD bool isReady() const;
void waitForReady(); void waitForReady();
void repaintAllChildren(const int delay = 0) const;
private: private:
Q_NODISCARD QRect mapItemGeometryToScene(const QQuickItem * const item) const; Q_NODISCARD QRect mapItemGeometryToScene(const QQuickItem * const item) const;
Q_NODISCARD bool isInSystemButtons(const QPoint &pos, QuickGlobal::SystemButtonType *button) const; Q_NODISCARD bool isInSystemButtons(const QPoint &pos, QuickGlobal::SystemButtonType *button) const;

View File

@ -26,6 +26,7 @@
#include <FramelessHelper/Widgets/framelesshelperwidgets_global.h> #include <FramelessHelper/Widgets/framelesshelperwidgets_global.h>
#include <QtCore/qvariant.h> #include <QtCore/qvariant.h>
#include <QtWidgets/qsizepolicy.h>
FRAMELESSHELPER_BEGIN_NAMESPACE FRAMELESSHELPER_BEGIN_NAMESPACE
@ -89,6 +90,8 @@ public:
Q_NODISCARD bool isReady() const; Q_NODISCARD bool isReady() const;
void waitForReady(); void waitForReady();
void repaintAllChildren(const int delay = 0) const;
private: private:
Q_NODISCARD QRect mapWidgetGeometryToScene(const QWidget * const widget) const; Q_NODISCARD QRect mapWidgetGeometryToScene(const QWidget * const widget) const;
Q_NODISCARD bool isInSystemButtons(const QPoint &pos, Global::SystemButtonType *button) const; Q_NODISCARD bool isInSystemButtons(const QPoint &pos, Global::SystemButtonType *button) const;
@ -106,6 +109,7 @@ private:
QPointer<QWidget> m_window = nullptr; QPointer<QWidget> m_window = nullptr;
bool m_destroying = false; bool m_destroying = false;
bool m_qpaReady = false; bool m_qpaReady = false;
QSizePolicy m_savedSizePolicy = {};
}; };
FRAMELESSHELPER_END_NAMESPACE FRAMELESSHELPER_END_NAMESPACE

View File

@ -1200,16 +1200,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
Utils::rescaleSize(data.restoreGeometry.size(), oldDpi.x, newDpi.x)); Utils::rescaleSize(data.restoreGeometry.size(), oldDpi.x, newDpi.x));
} }
g_win32Helper()->mutex.unlock(); g_win32Helper()->mutex.unlock();
#if (QT_VERSION <= QT_VERSION_CHECK(6, 4, 2)) data.params.forceChildrenRepaint(500);
// We need to wait until Qt has handled this message, otherwise everything
// we have done here will always be overwritten.
QWindow *window = data.params.getWindowHandle();
QTimer::singleShot(0, qApp, [window](){
// Sync the internal window frame margins with the latest DPI, otherwise
// we will get wrong window sizes after the DPI change.
Utils::updateInternalWindowFrameMargins(window, true);
});
#endif // (QT_VERSION <= QT_VERSION_CHECK(6, 4, 2))
} break; } break;
case WM_DWMCOMPOSITIONCHANGED: { case WM_DWMCOMPOSITIONCHANGED: {
// Re-apply the custom window frame if recovered from the basic theme. // Re-apply the custom window frame if recovered from the basic theme.

View File

@ -186,6 +186,7 @@ FRAMELESSHELPER_STRING_CONSTANT(SendMessageTimeoutW)
FRAMELESSHELPER_STRING_CONSTANT(AttachThreadInput) FRAMELESSHELPER_STRING_CONSTANT(AttachThreadInput)
FRAMELESSHELPER_STRING_CONSTANT(BringWindowToTop) FRAMELESSHELPER_STRING_CONSTANT(BringWindowToTop)
FRAMELESSHELPER_STRING_CONSTANT(SetActiveWindow) FRAMELESSHELPER_STRING_CONSTANT(SetActiveWindow)
FRAMELESSHELPER_STRING_CONSTANT(RedrawWindow)
struct Win32UtilsHelperData struct Win32UtilsHelperData
{ {
@ -636,11 +637,18 @@ void Utils::triggerFrameChange(const WId windowId)
return; return;
} }
const auto hwnd = reinterpret_cast<HWND>(windowId); const auto hwnd = reinterpret_cast<HWND>(windowId);
static constexpr const UINT flags = static constexpr const UINT swpFlags =
(SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOSIZE (SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOSIZE
| SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER); | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER);
if (SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, flags) == FALSE) { if (SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, swpFlags) == FALSE) {
WARNING << getSystemErrorMessage(kSetWindowPos); WARNING << getSystemErrorMessage(kSetWindowPos);
return;
}
static constexpr const UINT rdwFlags =
(RDW_ERASE | RDW_FRAME | RDW_INVALIDATE
| RDW_UPDATENOW | RDW_ALLCHILDREN);
if (RedrawWindow(hwnd, nullptr, nullptr, rdwFlags) == FALSE) {
WARNING << getSystemErrorMessage(kRedrawWindow);
} }
} }

View File

@ -224,6 +224,7 @@ void FramelessQuickHelperPrivate::attach()
params.setCursor = [window](const QCursor &cursor) -> void { window->setCursor(cursor); }; params.setCursor = [window](const QCursor &cursor) -> void { window->setCursor(cursor); };
params.unsetCursor = [window]() -> void { window->unsetCursor(); }; params.unsetCursor = [window]() -> void { window->unsetCursor(); };
params.getWidgetHandle = []() -> QObject * { return nullptr; }; params.getWidgetHandle = []() -> QObject * { return nullptr; };
params.forceChildrenRepaint = [this](const int delay) -> void { repaintAllChildren(delay); };
FramelessManager::instance()->addWindow(&params); FramelessManager::instance()->addWindow(&params);
@ -685,6 +686,38 @@ void FramelessQuickHelperPrivate::waitForReady()
#endif #endif
} }
void FramelessQuickHelperPrivate::repaintAllChildren(const int delay) const
{
Q_Q(const FramelessQuickHelper);
QQuickWindow * const window = q->window();
if (!window) {
return;
}
const auto update = [window]() -> void {
window->requestUpdate();
#ifdef Q_OS_WINDOWS
// Sync the internal window frame margins with the latest DPI, otherwise
// we will get wrong window sizes after the DPI change.
Utils::updateInternalWindowFrameMargins(window, true);
#endif // Q_OS_WINDOWS
const QList<QQuickItem *> items = window->findChildren<QQuickItem *>();
if (items.isEmpty()) {
return;
}
for (auto &&item : std::as_const(items)) {
// Only items with the "QQuickItem::ItemHasContents" flag enabled are allowed to call "update()".
if (item->flags() & QQuickItem::ItemHasContents) {
item->update();
}
}
};
if (delay > 0) {
QTimer::singleShot(delay, this, update);
} else {
update();
}
}
QRect FramelessQuickHelperPrivate::mapItemGeometryToScene(const QQuickItem * const item) const QRect FramelessQuickHelperPrivate::mapItemGeometryToScene(const QQuickItem * const item) const
{ {
Q_ASSERT(item); Q_ASSERT(item);

View File

@ -88,6 +88,76 @@ struct WidgetsHelper
Q_GLOBAL_STATIC(WidgetsHelper, g_widgetsHelper) Q_GLOBAL_STATIC(WidgetsHelper, g_widgetsHelper)
[[nodiscard]] static inline bool isWidgetFixedSize(const QWidget * const widget)
{
Q_ASSERT(widget);
if (!widget) {
return false;
}
// "Qt::MSWindowsFixedSizeDialogHint" is used cross-platform actually.
if (widget->windowFlags() & Qt::MSWindowsFixedSizeDialogHint) {
return true;
}
// Caused by setFixedWidth/Height/Size().
const QSize minSize = widget->minimumSize();
const QSize maxSize = widget->maximumSize();
if (!minSize.isEmpty() && !maxSize.isEmpty() && (minSize == maxSize)) {
return true;
}
// Usually set by the user.
const QSizePolicy sizePolicy = widget->sizePolicy();
if ((sizePolicy.horizontalPolicy() == QSizePolicy::Fixed)
&& (sizePolicy.verticalPolicy() == QSizePolicy::Fixed)) {
return true;
}
return false;
}
static inline void forceWidgetRepaint(QWidget * const widget)
{
Q_ASSERT(widget);
if (!widget) {
return;
}
// Tell the widget to repaint itself, but it may not happen due to QWidget's
// internal painting optimizations.
widget->update();
// Try to force the widget to repaint itself, in case:
// (1) It's a child widget;
// (2) It's a top level window but not minimized/maximized/fullscreen.
if (!widget->isWindow() || !(widget->windowState() & (Qt::WindowMinimized | Qt::WindowMaximized | Qt::WindowFullScreen))) {
// A widget will most likely repaint itself if it's size is changed.
if (!isWidgetFixedSize(widget)) {
const QSize originalSize = widget->size();
static constexpr const auto margins = QMargins{1, 1, 1, 1};
widget->resize(originalSize.shrunkBy(margins));
widget->resize(originalSize.grownBy(margins));
widget->resize(originalSize);
}
// However, some widgets won't repaint themselves unless their position is changed.
const QPoint originalPosition = widget->pos();
static constexpr const auto offset = QPoint{1, 1};
widget->move(originalPosition - offset);
widget->move(originalPosition + offset);
widget->move(originalPosition);
}
#ifdef Q_OS_WINDOWS
// There's some additional things to do for top level windows on Windows.
if (widget->isWindow()) {
// Don't crash if the QWindow instance has not been created yet.
if (QWindow * const window = widget->windowHandle()) {
// Sync the internal window frame margins with the latest DPI, otherwise
// we will get wrong window sizes after the DPI change.
Utils::updateInternalWindowFrameMargins(window, true);
}
}
#endif // Q_OS_WINDOWS
// Let's try again with the ordinary way.
widget->update();
// ### TODO: I observed the font size is often wrong after DPI changes,
// do we need to refresh the font settings here as well?
}
FramelessWidgetsHelperPrivate::FramelessWidgetsHelperPrivate(FramelessWidgetsHelper *q) : QObject(q) FramelessWidgetsHelperPrivate::FramelessWidgetsHelperPrivate(FramelessWidgetsHelper *q) : QObject(q)
{ {
Q_ASSERT(q); Q_ASSERT(q);
@ -126,18 +196,7 @@ bool FramelessWidgetsHelperPrivate::isWindowFixedSize() const
if (!m_window) { if (!m_window) {
return false; return false;
} }
if (m_window->windowFlags() & Qt::MSWindowsFixedSizeDialogHint) { return isWidgetFixedSize(m_window);
return true;
}
const QSize minSize = m_window->minimumSize();
const QSize maxSize = m_window->maximumSize();
if (!minSize.isEmpty() && !maxSize.isEmpty() && (minSize == maxSize)) {
return true;
}
if (m_window->sizePolicy() == QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)) {
return true;
}
return false;
} }
void FramelessWidgetsHelperPrivate::setWindowFixedSize(const bool value) void FramelessWidgetsHelperPrivate::setWindowFixedSize(const bool value)
@ -149,8 +208,11 @@ void FramelessWidgetsHelperPrivate::setWindowFixedSize(const bool value)
return; return;
} }
if (value) { if (value) {
m_savedSizePolicy = m_window->sizePolicy();
m_window->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
m_window->setFixedSize(m_window->size()); m_window->setFixedSize(m_window->size());
} else { } else {
m_window->setSizePolicy(m_savedSizePolicy);
m_window->setMinimumSize(kDefaultWindowSize); m_window->setMinimumSize(kDefaultWindowSize);
m_window->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); m_window->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
} }
@ -345,6 +407,28 @@ void FramelessWidgetsHelperPrivate::waitForReady()
#endif #endif
} }
void FramelessWidgetsHelperPrivate::repaintAllChildren(const int delay) const
{
if (!m_window) {
return;
}
const auto update = [this]() -> void {
forceWidgetRepaint(m_window);
const QList<QWidget *> widgets = m_window->findChildren<QWidget *>();
if (widgets.isEmpty()) {
return;
}
for (auto &&widget : std::as_const(widgets)) {
forceWidgetRepaint(widget);
}
};
if (delay > 0) {
QTimer::singleShot(delay, this, update);
} else {
update();
}
}
bool FramelessWidgetsHelperPrivate::isContentExtendedIntoTitleBar() const bool FramelessWidgetsHelperPrivate::isContentExtendedIntoTitleBar() const
{ {
return getWindowData().ready; return getWindowData().ready;
@ -487,6 +571,7 @@ void FramelessWidgetsHelperPrivate::attach()
params.setCursor = [window](const QCursor &cursor) -> void { window->setCursor(cursor); }; params.setCursor = [window](const QCursor &cursor) -> void { window->setCursor(cursor); };
params.unsetCursor = [window]() -> void { window->unsetCursor(); }; params.unsetCursor = [window]() -> void { window->unsetCursor(); };
params.getWidgetHandle = [window]() -> QObject * { return window; }; params.getWidgetHandle = [window]() -> QObject * { return window; };
params.forceChildrenRepaint = [this](const int delay) -> void { repaintAllChildren(delay); };
FramelessManager::instance()->addWindow(&params); FramelessManager::instance()->addWindow(&params);