improve dpi change experience
Signed-off-by: Yuhang Zhao <2546789017@qq.com>
This commit is contained in:
parent
250aff0faf
commit
a3cfa59d1d
|
@ -61,6 +61,7 @@ using GetPropertyCallback = std::function<QVariant(const QByteArray &, const QVa
|
|||
using SetCursorCallback = std::function<void(const QCursor &)>;
|
||||
using UnsetCursorCallback = std::function<void()>;
|
||||
using GetWidgetHandleCallback = std::function<QObject *()>;
|
||||
using ForceChildrenRepaintCallback = std::function<void(const int)>;
|
||||
|
||||
struct SystemParameters
|
||||
{
|
||||
|
@ -90,6 +91,7 @@ struct SystemParameters
|
|||
SetCursorCallback setCursor = nullptr;
|
||||
UnsetCursorCallback unsetCursor = nullptr;
|
||||
GetWidgetHandleCallback getWidgetHandle = nullptr;
|
||||
ForceChildrenRepaintCallback forceChildrenRepaint = nullptr;
|
||||
};
|
||||
|
||||
using FramelessParams = SystemParameters *;
|
||||
|
|
|
@ -88,6 +88,8 @@ public:
|
|||
Q_NODISCARD bool isReady() const;
|
||||
void waitForReady();
|
||||
|
||||
void repaintAllChildren(const int delay = 0) const;
|
||||
|
||||
private:
|
||||
Q_NODISCARD QRect mapItemGeometryToScene(const QQuickItem * const item) const;
|
||||
Q_NODISCARD bool isInSystemButtons(const QPoint &pos, QuickGlobal::SystemButtonType *button) const;
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
#include <FramelessHelper/Widgets/framelesshelperwidgets_global.h>
|
||||
#include <QtCore/qvariant.h>
|
||||
#include <QtWidgets/qsizepolicy.h>
|
||||
|
||||
FRAMELESSHELPER_BEGIN_NAMESPACE
|
||||
|
||||
|
@ -89,6 +90,8 @@ public:
|
|||
Q_NODISCARD bool isReady() const;
|
||||
void waitForReady();
|
||||
|
||||
void repaintAllChildren(const int delay = 0) const;
|
||||
|
||||
private:
|
||||
Q_NODISCARD QRect mapWidgetGeometryToScene(const QWidget * const widget) const;
|
||||
Q_NODISCARD bool isInSystemButtons(const QPoint &pos, Global::SystemButtonType *button) const;
|
||||
|
@ -106,6 +109,7 @@ private:
|
|||
QPointer<QWidget> m_window = nullptr;
|
||||
bool m_destroying = false;
|
||||
bool m_qpaReady = false;
|
||||
QSizePolicy m_savedSizePolicy = {};
|
||||
};
|
||||
|
||||
FRAMELESSHELPER_END_NAMESPACE
|
||||
|
|
|
@ -1200,16 +1200,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
|
|||
Utils::rescaleSize(data.restoreGeometry.size(), oldDpi.x, newDpi.x));
|
||||
}
|
||||
g_win32Helper()->mutex.unlock();
|
||||
#if (QT_VERSION <= QT_VERSION_CHECK(6, 4, 2))
|
||||
// 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))
|
||||
data.params.forceChildrenRepaint(500);
|
||||
} break;
|
||||
case WM_DWMCOMPOSITIONCHANGED: {
|
||||
// Re-apply the custom window frame if recovered from the basic theme.
|
||||
|
|
|
@ -186,6 +186,7 @@ FRAMELESSHELPER_STRING_CONSTANT(SendMessageTimeoutW)
|
|||
FRAMELESSHELPER_STRING_CONSTANT(AttachThreadInput)
|
||||
FRAMELESSHELPER_STRING_CONSTANT(BringWindowToTop)
|
||||
FRAMELESSHELPER_STRING_CONSTANT(SetActiveWindow)
|
||||
FRAMELESSHELPER_STRING_CONSTANT(RedrawWindow)
|
||||
|
||||
struct Win32UtilsHelperData
|
||||
{
|
||||
|
@ -636,11 +637,18 @@ void Utils::triggerFrameChange(const WId windowId)
|
|||
return;
|
||||
}
|
||||
const auto hwnd = reinterpret_cast<HWND>(windowId);
|
||||
static constexpr const UINT flags =
|
||||
static constexpr const UINT swpFlags =
|
||||
(SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOSIZE
|
||||
| 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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -224,6 +224,7 @@ void FramelessQuickHelperPrivate::attach()
|
|||
params.setCursor = [window](const QCursor &cursor) -> void { window->setCursor(cursor); };
|
||||
params.unsetCursor = [window]() -> void { window->unsetCursor(); };
|
||||
params.getWidgetHandle = []() -> QObject * { return nullptr; };
|
||||
params.forceChildrenRepaint = [this](const int delay) -> void { repaintAllChildren(delay); };
|
||||
|
||||
FramelessManager::instance()->addWindow(¶ms);
|
||||
|
||||
|
@ -685,6 +686,38 @@ void FramelessQuickHelperPrivate::waitForReady()
|
|||
#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
|
||||
{
|
||||
Q_ASSERT(item);
|
||||
|
|
|
@ -88,6 +88,76 @@ struct 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)
|
||||
{
|
||||
Q_ASSERT(q);
|
||||
|
@ -126,18 +196,7 @@ bool FramelessWidgetsHelperPrivate::isWindowFixedSize() const
|
|||
if (!m_window) {
|
||||
return false;
|
||||
}
|
||||
if (m_window->windowFlags() & Qt::MSWindowsFixedSizeDialogHint) {
|
||||
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;
|
||||
return isWidgetFixedSize(m_window);
|
||||
}
|
||||
|
||||
void FramelessWidgetsHelperPrivate::setWindowFixedSize(const bool value)
|
||||
|
@ -149,8 +208,11 @@ void FramelessWidgetsHelperPrivate::setWindowFixedSize(const bool value)
|
|||
return;
|
||||
}
|
||||
if (value) {
|
||||
m_savedSizePolicy = m_window->sizePolicy();
|
||||
m_window->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
m_window->setFixedSize(m_window->size());
|
||||
} else {
|
||||
m_window->setSizePolicy(m_savedSizePolicy);
|
||||
m_window->setMinimumSize(kDefaultWindowSize);
|
||||
m_window->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
|
||||
}
|
||||
|
@ -345,6 +407,28 @@ void FramelessWidgetsHelperPrivate::waitForReady()
|
|||
#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
|
||||
{
|
||||
return getWindowData().ready;
|
||||
|
@ -487,6 +571,7 @@ void FramelessWidgetsHelperPrivate::attach()
|
|||
params.setCursor = [window](const QCursor &cursor) -> void { window->setCursor(cursor); };
|
||||
params.unsetCursor = [window]() -> void { window->unsetCursor(); };
|
||||
params.getWidgetHandle = [window]() -> QObject * { return window; };
|
||||
params.forceChildrenRepaint = [this](const int delay) -> void { repaintAllChildren(delay); };
|
||||
|
||||
FramelessManager::instance()->addWindow(¶ms);
|
||||
|
||||
|
|
Loading…
Reference in New Issue