mica material: fix drawing on multi-monitor scenario

This commit is contained in:
Yuhang Zhao 2023-07-15 16:10:48 +08:00
parent 43f632f261
commit d71dc75b77
17 changed files with 410 additions and 221 deletions

View File

@ -54,7 +54,7 @@ option(FRAMELESSHELPER_ENABLE_CFGUARD "Enable Control Flow Guard (CFG)." OFF)
option(FRAMELESSHELPER_EXAMPLES_STANDALONE "Build the demo projects as standalone CMake projects." OFF) option(FRAMELESSHELPER_EXAMPLES_STANDALONE "Build the demo projects as standalone CMake projects." OFF)
cmake_dependent_option(FRAMELESSHELPER_ENABLE_UNIVERSAL_BUILD "macOS only: build universal library/example for Mac." ON APPLE OFF) cmake_dependent_option(FRAMELESSHELPER_ENABLE_UNIVERSAL_BUILD "macOS only: build universal library/example for Mac." ON APPLE OFF)
option(FRAMELESSHELPER_FORCE_LTO "Force enable LTO/LTCG even when building static libraries." OFF) option(FRAMELESSHELPER_FORCE_LTO "Force enable LTO/LTCG even when building static libraries." OFF)
option(FRAMELESSHELPER_REPRODUCIBLE_OUTPUT "Don't update the build commit and date dynamically." OFF) option(FRAMELESSHELPER_REPRODUCIBLE_OUTPUT "Don't update the build commit and date dynamically." ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Gui) find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Gui)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Gui) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Gui)

View File

@ -57,6 +57,7 @@ target_sources(${DEMO_NAME} PRIVATE
if(WIN32) if(WIN32)
set(__rc_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.rc") set(__rc_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.rc")
if(NOT EXISTS "${__rc_path}")
generate_win32_rc_file( generate_win32_rc_file(
PATH "${__rc_path}" PATH "${__rc_path}"
VERSION "${PROJECT_VERSION}" VERSION "${PROJECT_VERSION}"
@ -66,7 +67,9 @@ if(WIN32)
PRODUCT "FramelessHelper Demo" PRODUCT "FramelessHelper Demo"
ICONS "../shared/example.ico" ICONS "../shared/example.ico"
) )
endif()
set(__manifest_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.manifest") set(__manifest_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.manifest")
if(NOT EXISTS "${__manifest_path}")
generate_win32_manifest_file( generate_win32_manifest_file(
PATH "${__manifest_path}" PATH "${__manifest_path}"
ID "org.wangwenx190.demo.Dialog" ID "org.wangwenx190.demo.Dialog"
@ -80,6 +83,7 @@ if(WIN32)
XAML_ISLANDS_COMPAT XAML_ISLANDS_COMPAT
UTF8_CODEPAGE UTF8_CODEPAGE
) )
endif()
target_sources(${DEMO_NAME} PRIVATE target_sources(${DEMO_NAME} PRIVATE
"${__rc_path}" "${__rc_path}"
"${__manifest_path}" "${__manifest_path}"

View File

@ -62,6 +62,7 @@ target_sources(${DEMO_NAME} PRIVATE
if(WIN32) if(WIN32)
set(__rc_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.rc") set(__rc_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.rc")
if(NOT EXISTS "${__rc_path}")
generate_win32_rc_file( generate_win32_rc_file(
PATH "${__rc_path}" PATH "${__rc_path}"
VERSION "${PROJECT_VERSION}" VERSION "${PROJECT_VERSION}"
@ -71,7 +72,9 @@ if(WIN32)
PRODUCT "FramelessHelper Demo" PRODUCT "FramelessHelper Demo"
ICONS "../shared/example.ico" ICONS "../shared/example.ico"
) )
endif()
set(__manifest_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.manifest") set(__manifest_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.manifest")
if(NOT EXISTS "${__manifest_path}")
generate_win32_manifest_file( generate_win32_manifest_file(
PATH "${__manifest_path}" PATH "${__manifest_path}"
ID "org.wangwenx190.demo.MainWindow" ID "org.wangwenx190.demo.MainWindow"
@ -85,6 +88,7 @@ if(WIN32)
XAML_ISLANDS_COMPAT XAML_ISLANDS_COMPAT
UTF8_CODEPAGE UTF8_CODEPAGE
) )
endif()
target_sources(${DEMO_NAME} PRIVATE target_sources(${DEMO_NAME} PRIVATE
"${__rc_path}" "${__rc_path}"
"${__manifest_path}" "${__manifest_path}"

View File

@ -65,6 +65,7 @@ target_sources(${DEMO_NAME} PRIVATE
if(WIN32) if(WIN32)
set(__rc_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.rc") set(__rc_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.rc")
if(NOT EXISTS "${__rc_path}")
generate_win32_rc_file( generate_win32_rc_file(
PATH "${__rc_path}" PATH "${__rc_path}"
VERSION "${PROJECT_VERSION}" VERSION "${PROJECT_VERSION}"
@ -74,7 +75,9 @@ if(WIN32)
PRODUCT "FramelessHelper Demo" PRODUCT "FramelessHelper Demo"
ICONS "../shared/example.ico" ICONS "../shared/example.ico"
) )
endif()
set(__manifest_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.manifest") set(__manifest_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.manifest")
if(NOT EXISTS "${__manifest_path}")
generate_win32_manifest_file( generate_win32_manifest_file(
PATH "${__manifest_path}" PATH "${__manifest_path}"
ID "org.wangwenx190.demo.OpenGLWidget" ID "org.wangwenx190.demo.OpenGLWidget"
@ -88,6 +91,7 @@ if(WIN32)
XAML_ISLANDS_COMPAT XAML_ISLANDS_COMPAT
UTF8_CODEPAGE UTF8_CODEPAGE
) )
endif()
target_sources(${DEMO_NAME} PRIVATE target_sources(${DEMO_NAME} PRIVATE
"${__rc_path}" "${__rc_path}"
"${__manifest_path}" "${__manifest_path}"

View File

@ -57,6 +57,7 @@ target_sources(${DEMO_NAME} PRIVATE
if(WIN32) if(WIN32)
set(__rc_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.rc") set(__rc_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.rc")
if(NOT EXISTS "${__rc_path}")
generate_win32_rc_file( generate_win32_rc_file(
PATH "${__rc_path}" PATH "${__rc_path}"
VERSION "${PROJECT_VERSION}" VERSION "${PROJECT_VERSION}"
@ -66,7 +67,9 @@ if(WIN32)
PRODUCT "FramelessHelper Demo" PRODUCT "FramelessHelper Demo"
ICONS "../shared/example.ico" ICONS "../shared/example.ico"
) )
endif()
set(__manifest_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.manifest") set(__manifest_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.manifest")
if(NOT EXISTS "${__manifest_path}")
generate_win32_manifest_file( generate_win32_manifest_file(
PATH "${__manifest_path}" PATH "${__manifest_path}"
ID "org.wangwenx190.demo.Quick" ID "org.wangwenx190.demo.Quick"
@ -80,6 +83,7 @@ if(WIN32)
XAML_ISLANDS_COMPAT XAML_ISLANDS_COMPAT
UTF8_CODEPAGE UTF8_CODEPAGE
) )
endif()
target_sources(${DEMO_NAME} PRIVATE target_sources(${DEMO_NAME} PRIVATE
"${__rc_path}" "${__rc_path}"
"${__manifest_path}" "${__manifest_path}"

View File

@ -57,6 +57,7 @@ target_sources(${DEMO_NAME} PRIVATE
if(WIN32) if(WIN32)
set(__rc_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.rc") set(__rc_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.rc")
if(NOT EXISTS "${__rc_path}")
generate_win32_rc_file( generate_win32_rc_file(
PATH "${__rc_path}" PATH "${__rc_path}"
VERSION "${PROJECT_VERSION}" VERSION "${PROJECT_VERSION}"
@ -66,7 +67,9 @@ if(WIN32)
PRODUCT "FramelessHelper Demo" PRODUCT "FramelessHelper Demo"
ICONS "../shared/example.ico" ICONS "../shared/example.ico"
) )
endif()
set(__manifest_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.manifest") set(__manifest_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.manifest")
if(NOT EXISTS "${__manifest_path}")
generate_win32_manifest_file( generate_win32_manifest_file(
PATH "${__manifest_path}" PATH "${__manifest_path}"
ID "org.wangwenx190.demo.Widget" ID "org.wangwenx190.demo.Widget"
@ -80,6 +83,7 @@ if(WIN32)
XAML_ISLANDS_COMPAT XAML_ISLANDS_COMPAT
UTF8_CODEPAGE UTF8_CODEPAGE
) )
endif()
target_sources(${DEMO_NAME} PRIVATE target_sources(${DEMO_NAME} PRIVATE
"${__rc_path}" "${__rc_path}"
"${__manifest_path}" "${__manifest_path}"

View File

@ -25,6 +25,7 @@
#pragma once #pragma once
#include <FramelessHelper/Core/framelesshelpercore_global.h> #include <FramelessHelper/Core/framelesshelpercore_global.h>
#include <QtCore/qrect.h>
FRAMELESSHELPER_BEGIN_NAMESPACE FRAMELESSHELPER_BEGIN_NAMESPACE
@ -62,7 +63,13 @@ public:
void setFallbackEnabled(const bool value); void setFallbackEnabled(const bool value);
public Q_SLOTS: public Q_SLOTS:
void paint(QPainter *painter, const QSize &size, const QPoint &pos, const bool active = true); void paint(QPainter *painter, const QRect &rect, const bool active = true);
[[deprecated("Use another overload instead.")]]
void paint(QPainter *painter, const QSize &size, const QPoint &pos, const bool active = true)
{
paint(painter, QRect{ pos, size }, active);
}
Q_SIGNALS: Q_SIGNALS:
void tintColorChanged(); void tintColorChanged();

View File

@ -103,3 +103,21 @@ FRAMELESSHELPER_CORE_API void registerInitializeHook(const InitializeHookCallbac
FRAMELESSHELPER_CORE_API void registerUninitializeHook(const UninitializeHookCallback &cb); FRAMELESSHELPER_CORE_API void registerUninitializeHook(const UninitializeHookCallback &cb);
FRAMELESSHELPER_END_NAMESPACE FRAMELESSHELPER_END_NAMESPACE
#define DECLARE_SIZE_COMPARE_OPERATORS(Type1, Type2) \
[[maybe_unused]] [[nodiscard]] static inline constexpr bool operator>(const Type1 &lhs, const Type2 &rhs) noexcept \
{ \
return ((lhs.width() * lhs.height()) > (rhs.width() * rhs.height())); \
} \
[[maybe_unused]] [[nodiscard]] static inline constexpr bool operator>=(const Type1 &lhs, const Type2 &rhs) noexcept \
{ \
return (operator>(lhs, rhs) || operator==(lhs, rhs)); \
} \
[[maybe_unused]] [[nodiscard]] static inline constexpr bool operator<(const Type1 &lhs, const Type2 &rhs) noexcept \
{ \
return (operator!=(lhs, rhs) && !operator>(lhs, rhs)); \
} \
[[maybe_unused]] [[nodiscard]] static inline constexpr bool operator<=(const Type1 &lhs, const Type2 &rhs) noexcept \
{ \
return (operator<(lhs, rhs) || operator==(lhs, rhs)); \
}

View File

@ -52,10 +52,18 @@ public:
Q_NODISCARD static QColor systemFallbackColor(); Q_NODISCARD static QColor systemFallbackColor();
Q_NODISCARD static QSize monitorSize();
Q_NODISCARD static QSize wallpaperSize();
Q_NODISCARD QPoint mapToWallpaper(const QPoint &pos) const;
Q_NODISCARD QSize mapToWallpaper(const QSize &size) const;
Q_NODISCARD QRect mapToWallpaper(const QRect &rect) const;
public Q_SLOTS: public Q_SLOTS:
void maybeGenerateBlurredWallpaper(const bool force = false); void maybeGenerateBlurredWallpaper(const bool force = false);
void updateMaterialBrush(); void updateMaterialBrush();
void paint(QPainter *painter, const QSize &size, const QPoint &pos, const bool active = true); void paint(QPainter *painter, const QRect &rect, const bool active = true);
void forceRebuildWallpaper();
private: private:
void initialize(); void initialize();

View File

@ -81,7 +81,9 @@ FRAMELESSHELPER_CORE_API void registerThemeChangeNotification();
[[nodiscard]] FRAMELESSHELPER_CORE_API QPoint fromNativeGlobalPosition(const QWindow *window, const QPoint &point); [[nodiscard]] FRAMELESSHELPER_CORE_API QPoint fromNativeGlobalPosition(const QWindow *window, const QPoint &point);
[[nodiscard]] FRAMELESSHELPER_CORE_API int horizontalAdvance(const QFontMetrics &fm, const QString &str); [[nodiscard]] FRAMELESSHELPER_CORE_API int horizontalAdvance(const QFontMetrics &fm, const QString &str);
[[nodiscard]] FRAMELESSHELPER_CORE_API qreal getRelativeScaleFactor(const quint32 oldDpi, const quint32 newDpi); [[nodiscard]] FRAMELESSHELPER_CORE_API qreal getRelativeScaleFactor(const quint32 oldDpi, const quint32 newDpi);
[[nodiscard]] FRAMELESSHELPER_CORE_API QSizeF rescaleSize(const QSizeF &oldSize, const quint32 oldDpi, const quint32 newDpi);
[[nodiscard]] FRAMELESSHELPER_CORE_API QSize rescaleSize(const QSize &oldSize, const quint32 oldDpi, const quint32 newDpi); [[nodiscard]] FRAMELESSHELPER_CORE_API QSize rescaleSize(const QSize &oldSize, const quint32 oldDpi, const quint32 newDpi);
[[nodiscard]] FRAMELESSHELPER_CORE_API bool isValidGeometry(const QRectF &rect);
[[nodiscard]] FRAMELESSHELPER_CORE_API bool isValidGeometry(const QRect &rect); [[nodiscard]] FRAMELESSHELPER_CORE_API bool isValidGeometry(const QRect &rect);
[[nodiscard]] FRAMELESSHELPER_CORE_API QColor getAccentColor(); [[nodiscard]] FRAMELESSHELPER_CORE_API QColor getAccentColor();
[[nodiscard]] FRAMELESSHELPER_CORE_API quint32 defaultScreenDpi(); [[nodiscard]] FRAMELESSHELPER_CORE_API quint32 defaultScreenDpi();

View File

@ -56,7 +56,7 @@ private:
void fromImage(const QImage &value, QPainter *painter) const; void fromImage(const QImage &value, QPainter *painter) const;
void fromPixmap(const QPixmap &value, QPainter *painter) const; void fromPixmap(const QPixmap &value, QPainter *painter) const;
void fromIcon(const QIcon &value, QPainter *painter) const; void fromIcon(const QIcon &value, QPainter *painter) const;
Q_NODISCARD QRect paintArea() const; Q_NODISCARD QRectF paintArea() const;
private: private:
QuickImageItem *q_ptr = nullptr; QuickImageItem *q_ptr = nullptr;

View File

@ -53,6 +53,7 @@ public Q_SLOTS:
void rebindWindow(); void rebindWindow();
void forceRegenerateWallpaperImageCache(); void forceRegenerateWallpaperImageCache();
void appendNode(WallpaperImageNode *node); void appendNode(WallpaperImageNode *node);
void removeNode(WallpaperImageNode *node);
void updateFallbackColor(); void updateFallbackColor();
private: private:

View File

@ -27,6 +27,7 @@
#include "framelessmanager.h" #include "framelessmanager.h"
#include "utils.h" #include "utils.h"
#include "framelessconfig_p.h" #include "framelessconfig_p.h"
#include "framelesshelpercore_global_p.h"
#include <QtCore/qsysinfo.h> #include <QtCore/qsysinfo.h>
#include <QtCore/qloggingcategory.h> #include <QtCore/qloggingcategory.h>
#include <QtCore/qmutex.h> #include <QtCore/qmutex.h>
@ -57,6 +58,11 @@ static Q_LOGGING_CATEGORY(lcMicaMaterial, "wangwenx190.framelesshelper.core.mica
# define CRITICAL qCCritical(lcMicaMaterial) # define CRITICAL qCCritical(lcMicaMaterial)
#endif #endif
DECLARE_SIZE_COMPARE_OPERATORS(QSize, QSize)
DECLARE_SIZE_COMPARE_OPERATORS(QSizeF, QSizeF)
DECLARE_SIZE_COMPARE_OPERATORS(QSize, QSizeF)
DECLARE_SIZE_COMPARE_OPERATORS(QSizeF, QSize)
using namespace Global; using namespace Global;
[[maybe_unused]] static constexpr const QSize kMaximumPictureSize = { 1920, 1080 }; [[maybe_unused]] static constexpr const QSize kMaximumPictureSize = { 1920, 1080 };
@ -75,34 +81,22 @@ using namespace Global;
FRAMELESSHELPER_STRING_CONSTANT2(NoiseImageFilePath, ":/org.wangwenx190.FramelessHelper/resources/images/noise.png") FRAMELESSHELPER_STRING_CONSTANT2(NoiseImageFilePath, ":/org.wangwenx190.FramelessHelper/resources/images/noise.png")
#endif // FRAMELESSHELPER_CORE_NO_BUNDLE_RESOURCE #endif // FRAMELESSHELPER_CORE_NO_BUNDLE_RESOURCE
struct MicaMaterialData struct ImageData
{ {
QPixmap blurredWallpaper = {}; QPixmap blurredWallpaper = {};
bool graphicsResourcesReady = false; bool graphicsResourcesReady = false;
QMutex mutex{}; QMutex mutex{};
}; };
Q_GLOBAL_STATIC(MicaMaterialData, g_micaMaterialData) struct MetricsData
[[maybe_unused]] [[nodiscard]] static inline constexpr bool operator>(const QSize &lhs, const QSize &rhs) noexcept
{ {
return ((lhs.width() * lhs.height()) > (rhs.width() * rhs.height())); std::optional<QSize> monitorSize = std::nullopt;
} std::optional<QSize> wallpaperSize = std::nullopt;
QMutex mutex{};
};
[[maybe_unused]] [[nodiscard]] static inline constexpr bool operator>=(const QSize &lhs, const QSize &rhs) noexcept Q_GLOBAL_STATIC(ImageData, g_imageData)
{ Q_GLOBAL_STATIC(MetricsData, g_metricsData)
return (operator>(lhs, rhs) || operator==(lhs, rhs));
}
[[maybe_unused]] [[nodiscard]] static inline constexpr bool operator<(const QSize &lhs, const QSize &rhs) noexcept
{
return (operator!=(lhs, rhs) && !operator>(lhs, rhs));
}
[[maybe_unused]] [[nodiscard]] static inline constexpr bool operator<=(const QSize &lhs, const QSize &rhs) noexcept
{
return (operator<(lhs, rhs) || operator==(lhs, rhs));
}
#ifndef FRAMELESSHELPER_CORE_NO_PRIVATE #ifndef FRAMELESSHELPER_CORE_NO_PRIVATE
template<const int shift> template<const int shift>
@ -516,13 +510,10 @@ protected:
void run() override void run() override
{ {
Transform transform = {}; Transform transform = {};
// ### FIXME: Ideally, we should not use virtual desktop size here. const QSize monitorSize = MicaMaterialPrivate::monitorSize();
QSize monitorSize = QGuiApplication::primaryScreen()->virtualSize(); const QSize imageSize = MicaMaterialPrivate::wallpaperSize();
if (monitorSize.isEmpty()) { // If we scaled the image size, record the scale factor and we need it to map our clip rect
WARNING << "Failed to retrieve the monitor size. Using default size (1920x1080) instead ..."; // to the real (unscaled) rect.
monitorSize = kMaximumPictureSize;
}
const QSize imageSize = (monitorSize > kMaximumPictureSize ? kMaximumPictureSize : monitorSize);
if (imageSize != monitorSize) { if (imageSize != monitorSize) {
transform.Horizontal = (qreal(imageSize.width()) / qreal(monitorSize.width())); transform.Horizontal = (qreal(imageSize.width()) / qreal(monitorSize.width()));
transform.Vertical = (qreal(imageSize.height()) / qreal(monitorSize.height())); transform.Vertical = (qreal(imageSize.height()) / qreal(monitorSize.height()));
@ -532,6 +523,8 @@ protected:
WARNING << "Failed to retrieve the wallpaper file path."; WARNING << "Failed to retrieve the wallpaper file path.";
return; return;
} }
// QImageReader allows us read the image size before we actually loading it, this behavior
// can help us avoid consume too much memory if the image resolution is very large, eg, 4K.
QImageReader reader(wallpaperFilePath); QImageReader reader(wallpaperFilePath);
if (!reader.canRead()) { if (!reader.canRead()) {
WARNING << "Qt can't read the wallpaper file:" << reader.errorString(); WARNING << "Qt can't read the wallpaper file:" << reader.errorString();
@ -590,10 +583,11 @@ protected:
const QRect rect = alignedRect(Qt::LeftToRight, Qt::AlignCenter, image.size(), desktopRect); const QRect rect = alignedRect(Qt::LeftToRight, Qt::AlignCenter, image.size(), desktopRect);
bufferPainter.drawImage(rect.topLeft(), image); bufferPainter.drawImage(rect.topLeft(), image);
} }
g_micaMaterialData()->mutex.lock(); {
g_micaMaterialData()->blurredWallpaper = QPixmap(imageSize); const QMutexLocker locker(&g_imageData()->mutex);
g_micaMaterialData()->blurredWallpaper.fill(kDefaultTransparentColor); g_imageData()->blurredWallpaper = QPixmap(imageSize);
QPainter painter(&g_micaMaterialData()->blurredWallpaper); g_imageData()->blurredWallpaper.fill(kDefaultTransparentColor);
QPainter painter(&g_imageData()->blurredWallpaper);
painter.setRenderHints(QPainter::Antialiasing | painter.setRenderHints(QPainter::Antialiasing |
QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
#ifdef FRAMELESSHELPER_CORE_NO_PRIVATE #ifdef FRAMELESSHELPER_CORE_NO_PRIVATE
@ -601,7 +595,7 @@ protected:
#else // !FRAMELESSHELPER_CORE_NO_PRIVATE #else // !FRAMELESSHELPER_CORE_NO_PRIVATE
qt_blurImage(&painter, buffer, kDefaultBlurRadius, true, false); qt_blurImage(&painter, buffer, kDefaultBlurRadius, true, false);
#endif // FRAMELESSHELPER_CORE_NO_PRIVATE #endif // FRAMELESSHELPER_CORE_NO_PRIVATE
g_micaMaterialData()->mutex.unlock(); }
Q_EMIT imageUpdated(transform); Q_EMIT imageUpdated(transform);
} }
}; };
@ -656,12 +650,12 @@ const MicaMaterialPrivate *MicaMaterialPrivate::get(const MicaMaterial *q)
void MicaMaterialPrivate::maybeGenerateBlurredWallpaper(const bool force) void MicaMaterialPrivate::maybeGenerateBlurredWallpaper(const bool force)
{ {
g_micaMaterialData()->mutex.lock(); g_imageData()->mutex.lock();
if (!g_micaMaterialData()->blurredWallpaper.isNull() && !force) { if (!g_imageData()->blurredWallpaper.isNull() && !force) {
g_micaMaterialData()->mutex.unlock(); g_imageData()->mutex.unlock();
return; return;
} }
g_micaMaterialData()->mutex.unlock(); g_imageData()->mutex.unlock();
const QMutexLocker locker(&g_threadData()->mutex); const QMutexLocker locker(&g_threadData()->mutex);
if (g_threadData()->thread->isRunning()) { if (g_threadData()->thread->isRunning()) {
g_threadData()->thread->requestInterruption(); g_threadData()->thread->requestInterruption();
@ -698,37 +692,25 @@ void MicaMaterialPrivate::updateMaterialBrush()
} }
} }
void MicaMaterialPrivate::paint(QPainter *painter, const QSize &size, const QPoint &pos, const bool active) void MicaMaterialPrivate::paint(QPainter *painter, const QRect &rect, const bool active)
{ {
Q_ASSERT(painter); Q_ASSERT(painter);
Q_ASSERT(!size.isEmpty()); if (!painter) {
if (!painter || size.isEmpty()) {
return; return;
} }
prepareGraphicsResources(); prepareGraphicsResources();
static constexpr const QPointF originPoint = {0, 0}; static constexpr const QPoint originPoint = {0, 0};
QPointF correctedPos = pos; const QRect mappedRect = mapToWallpaper(rect);
QSizeF correctedSize = size;
if (!qFuzzyIsNull(transform.Horizontal) && (transform.Horizontal > qreal(0))
&& !qFuzzyCompare(transform.Horizontal, qreal(1))) {
correctedPos.setX(correctedPos.x() * transform.Horizontal);
correctedSize.setWidth(correctedSize.width() * transform.Horizontal);
}
if (!qFuzzyIsNull(transform.Vertical) && (transform.Vertical > qreal(0))
&& !qFuzzyCompare(transform.Vertical, qreal(1))) {
correctedPos.setY(correctedPos.y() * transform.Vertical);
correctedSize.setHeight(correctedSize.height() * transform.Vertical);
}
painter->save(); painter->save();
painter->setRenderHints(QPainter::Antialiasing | painter->setRenderHints(QPainter::Antialiasing |
QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
if (active) { if (active) {
const QMutexLocker locker(&g_micaMaterialData()->mutex); const QMutexLocker locker(&g_imageData()->mutex);
painter->drawPixmap(originPoint, g_micaMaterialData()->blurredWallpaper, QRectF{correctedPos, correctedSize}); painter->drawPixmap(originPoint, g_imageData()->blurredWallpaper, mappedRect);
} }
painter->setCompositionMode(QPainter::CompositionMode_SourceOver); painter->setCompositionMode(QPainter::CompositionMode_SourceOver);
painter->setOpacity(qreal(1)); painter->setOpacity(qreal(1));
painter->fillRect(QRectF{originPoint, correctedSize}, [this, active]() -> QBrush { painter->fillRect(QRect{originPoint, mappedRect.size()}, [this, active]() -> QBrush {
if (!fallbackEnabled || active) { if (!fallbackEnabled || active) {
return micaBrush; return micaBrush;
} }
@ -740,6 +722,15 @@ void MicaMaterialPrivate::paint(QPainter *painter, const QSize &size, const QPoi
painter->restore(); painter->restore();
} }
void MicaMaterialPrivate::forceRebuildWallpaper()
{
g_metricsData()->mutex.lock();
g_metricsData()->monitorSize = std::nullopt;
g_metricsData()->wallpaperSize = std::nullopt;
g_metricsData()->mutex.unlock();
maybeGenerateBlurredWallpaper(true);
}
void MicaMaterialPrivate::initialize() void MicaMaterialPrivate::initialize()
{ {
g_threadData()->mutex.lock(); g_threadData()->mutex.lock();
@ -767,9 +758,9 @@ void MicaMaterialPrivate::initialize()
connect(FramelessManager::instance(), &FramelessManager::systemThemeChanged, connect(FramelessManager::instance(), &FramelessManager::systemThemeChanged,
this, &MicaMaterialPrivate::updateMaterialBrush); this, &MicaMaterialPrivate::updateMaterialBrush);
connect(FramelessManager::instance(), &FramelessManager::wallpaperChanged, connect(FramelessManager::instance(), &FramelessManager::wallpaperChanged,
this, [this](){ this, &MicaMaterialPrivate::forceRebuildWallpaper);
maybeGenerateBlurredWallpaper(true); connect(qGuiApp, &QGuiApplication::primaryScreenChanged,
}); this, &MicaMaterialPrivate::forceRebuildWallpaper);
if (FramelessConfig::instance()->isSet(Option::DisableLazyInitializationForMicaMaterial)) { if (FramelessConfig::instance()->isSet(Option::DisableLazyInitializationForMicaMaterial)) {
prepareGraphicsResources(); prepareGraphicsResources();
@ -780,13 +771,13 @@ void MicaMaterialPrivate::initialize()
void MicaMaterialPrivate::prepareGraphicsResources() void MicaMaterialPrivate::prepareGraphicsResources()
{ {
g_micaMaterialData()->mutex.lock(); g_imageData()->mutex.lock();
if (g_micaMaterialData()->graphicsResourcesReady) { if (g_imageData()->graphicsResourcesReady) {
g_micaMaterialData()->mutex.unlock(); g_imageData()->mutex.unlock();
return; return;
} }
g_micaMaterialData()->graphicsResourcesReady = true; g_imageData()->graphicsResourcesReady = true;
g_micaMaterialData()->mutex.unlock(); g_imageData()->mutex.unlock();
maybeGenerateBlurredWallpaper(); maybeGenerateBlurredWallpaper();
} }
@ -795,6 +786,123 @@ QColor MicaMaterialPrivate::systemFallbackColor()
return ((FramelessManager::instance()->systemTheme() == SystemTheme::Dark) ? kDefaultFallbackColorDark : kDefaultFallbackColorLight); return ((FramelessManager::instance()->systemTheme() == SystemTheme::Dark) ? kDefaultFallbackColorDark : kDefaultFallbackColorLight);
} }
QSize MicaMaterialPrivate::monitorSize()
{
g_metricsData()->mutex.lock();
if (!g_metricsData()->monitorSize.has_value()) {
g_metricsData()->mutex.unlock();
const QScreen * const monitor = QGuiApplication::primaryScreen();
Q_ASSERT(monitor);
// We do not use the virtual desktop size here, instead, we only calculate the primary
// monitor size to simplify the logic, otherwise we will need a lot more code to take
// every case into account.
QSize size = (monitor ? monitor->size() : kMaximumPictureSize);
if (Q_UNLIKELY(size.isEmpty())) {
WARNING << "Failed to retrieve the monitor size. Using default size (1920x1080) instead ...";
size = kMaximumPictureSize;
}
g_metricsData()->mutex.lock();
g_metricsData()->monitorSize = size;
// Don't unlock the mutex here, we'll unlock it from outside.
DEBUG << "Primary monitor size:" << size * (monitor ? monitor->devicePixelRatio() : qreal(1));
}
const QSize result = g_metricsData()->monitorSize.value();
g_metricsData()->mutex.unlock();
return result;
}
QSize MicaMaterialPrivate::wallpaperSize()
{
g_metricsData()->mutex.lock();
if (!g_metricsData()->wallpaperSize.has_value()) {
g_metricsData()->mutex.unlock();
const QSize desktopSize = monitorSize();
// It's observed that QImage consumes too much memory if the image resolution is very large.
const QSize size = (desktopSize > kMaximumPictureSize ? kMaximumPictureSize : desktopSize);
g_metricsData()->mutex.lock();
g_metricsData()->wallpaperSize = size;
// Don't unlock the mutex here, we'll unlock it from outside.
DEBUG << "Wallpaper size:" << size;
}
const QSize result = g_metricsData()->wallpaperSize.value();
g_metricsData()->mutex.unlock();
return result;
}
QPoint MicaMaterialPrivate::mapToWallpaper(const QPoint &pos) const
{
if (pos.isNull()) {
return {};
}
QPointF result = pos;
if (!qFuzzyIsNull(transform.Horizontal) && (transform.Horizontal > qreal(0))
&& !qFuzzyCompare(transform.Horizontal, qreal(1))) {
result.setX(result.x() * transform.Horizontal);
}
if (!qFuzzyIsNull(transform.Vertical) && (transform.Vertical > qreal(0))
&& !qFuzzyCompare(transform.Vertical, qreal(1))) {
result.setY(result.y() * transform.Vertical);
}
const QSizeF imageSize = wallpaperSize();
// Make sure the position is always inside the wallpaper rectangle.
while (result.x() < qreal(0)) {
result.setX(result.x() + imageSize.width());
}
while (result.x() > imageSize.width()) {
result.setX(result.x() - imageSize.width());
}
while (result.y() < qreal(0)) {
result.setY(result.y() + imageSize.height());
}
while (result.y() > imageSize.height()) {
result.setY(result.y() - imageSize.height());
}
return result.toPoint();
}
QSize MicaMaterialPrivate::mapToWallpaper(const QSize &size) const
{
if (size.isEmpty()) {
return {};
}
QSizeF result = size;
if (!qFuzzyIsNull(transform.Horizontal) && (transform.Horizontal > qreal(0))
&& !qFuzzyCompare(transform.Horizontal, qreal(1))) {
result.setWidth(result.width() * transform.Horizontal);
}
if (!qFuzzyIsNull(transform.Vertical) && (transform.Vertical > qreal(0))
&& !qFuzzyCompare(transform.Vertical, qreal(1))) {
result.setHeight(result.height() * transform.Vertical);
}
const QSizeF imageSize = wallpaperSize();
// Make sure we don't get a size larger than the wallpaper's size.
if (result.width() > imageSize.width()) {
result.setWidth(imageSize.width());
}
if (result.height() > imageSize.height()) {
result.setHeight(imageSize.height());
}
return result.toSize();
}
QRect MicaMaterialPrivate::mapToWallpaper(const QRect &rect) const
{
const auto wallpaperRect = QRectF{ QPointF{ 0, 0 }, wallpaperSize() };
const auto mappedRect = QRectF{ mapToWallpaper(rect.topLeft()), mapToWallpaper(rect.size()) };
if (!Utils::isValidGeometry(mappedRect)) {
WARNING << "The calculated mapped rectangle is not valid.";
return wallpaperRect.toRect();
}
// Make sure we don't get something outside of the wallpaper area.
const QRectF intersectedRect = wallpaperRect.intersected(mappedRect);
// OK, the two rectangles are not intersected, just draw the whole wallpaper.
if (!Utils::isValidGeometry(intersectedRect)) {
WARNING << "The mapped rectangle and the wallpaper rectangle are not intersected.";
return wallpaperRect.toRect();
}
return intersectedRect.toRect();
}
MicaMaterial::MicaMaterial(QObject *parent) MicaMaterial::MicaMaterial(QObject *parent)
: QObject(parent), d_ptr(new MicaMaterialPrivate(this)) : QObject(parent), d_ptr(new MicaMaterialPrivate(this))
{ {
@ -900,10 +1008,10 @@ void MicaMaterial::setFallbackEnabled(const bool value)
Q_EMIT fallbackEnabledChanged(); Q_EMIT fallbackEnabledChanged();
} }
void MicaMaterial::paint(QPainter *painter, const QSize &size, const QPoint &pos, const bool active) void MicaMaterial::paint(QPainter *painter, const QRect &rect, const bool active)
{ {
Q_D(MicaMaterial); Q_D(MicaMaterial);
d->paint(painter, size, pos, active); d->paint(painter, rect, active);
} }
FRAMELESSHELPER_END_NAMESPACE FRAMELESSHELPER_END_NAMESPACE

View File

@ -530,7 +530,7 @@ qreal Utils::getRelativeScaleFactor(const quint32 oldDpi, const quint32 newDpi)
return qreal(newDpr / oldDpr); return qreal(newDpr / oldDpr);
} }
QSize Utils::rescaleSize(const QSize &oldSize, const quint32 oldDpi, const quint32 newDpi) QSizeF Utils::rescaleSize(const QSizeF &oldSize, const quint32 oldDpi, const quint32 newDpi)
{ {
if (oldSize.isEmpty()) { if (oldSize.isEmpty()) {
return {}; return {};
@ -545,14 +545,23 @@ QSize Utils::rescaleSize(const QSize &oldSize, const quint32 oldDpi, const quint
if (qFuzzyCompare(scaleFactor, qreal(1))) { if (qFuzzyCompare(scaleFactor, qreal(1))) {
return oldSize; return oldSize;
} }
const QSizeF newSize = QSizeF(oldSize) * scaleFactor; return QSizeF(oldSize * scaleFactor);
return newSize.toSize(); // The numbers will be rounded to the nearest integer. }
QSize Utils::rescaleSize(const QSize &oldSize, const quint32 oldDpi, const quint32 newDpi)
{
return rescaleSize(QSizeF(oldSize), oldDpi, newDpi).toSize();
}
bool Utils::isValidGeometry(const QRectF &rect)
{
// The position of the rectangle is not relevant.
return ((rect.right() > rect.left()) && (rect.bottom() > rect.top()));
} }
bool Utils::isValidGeometry(const QRect &rect) bool Utils::isValidGeometry(const QRect &rect)
{ {
// The position of the rectangle is not relevant. return isValidGeometry(QRectF(rect));
return ((rect.right() > rect.left()) && (rect.bottom() > rect.top()));
} }
quint32 Utils::defaultScreenDpi() quint32 Utils::defaultScreenDpi()

View File

@ -89,7 +89,7 @@ void QuickImageItemPrivate::paint(QPainter *painter) const
if (!painter) { if (!painter) {
return; return;
} }
if (!m_source.isValid()) { if (!m_source.isValid() || m_source.isNull()) {
return; return;
} }
painter->save(); painter->save();
@ -126,7 +126,8 @@ QVariant QuickImageItemPrivate::source() const
void QuickImageItemPrivate::setSource(const QVariant &value) void QuickImageItemPrivate::setSource(const QVariant &value)
{ {
Q_ASSERT(value.isValid()); Q_ASSERT(value.isValid());
if (!value.isValid()) { Q_ASSERT(!value.isNull());
if (!value.isValid() || value.isNull()) {
return; return;
} }
if (m_source == value) { if (m_source == value) {
@ -196,7 +197,9 @@ void QuickImageItemPrivate::fromPixmap(const QPixmap &value, QPainter *painter)
if (value.isNull() || !painter) { if (value.isNull() || !painter) {
return; return;
} }
painter->drawPixmap(paintArea(), value); const QRectF paintRect = paintArea();
const QSize paintSize = paintRect.size().toSize();
painter->drawPixmap(paintRect.topLeft(), (value.size() == paintSize ? value : value.scaled(paintSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)));
} }
void QuickImageItemPrivate::fromIcon(const QIcon &value, QPainter *painter) const void QuickImageItemPrivate::fromIcon(const QIcon &value, QPainter *painter) const
@ -206,18 +209,18 @@ void QuickImageItemPrivate::fromIcon(const QIcon &value, QPainter *painter) cons
if (value.isNull() || !painter) { if (value.isNull() || !painter) {
return; return;
} }
value.paint(painter, paintArea()); fromPixmap(value.pixmap(paintArea().size().toSize()), painter);
} }
QRect QuickImageItemPrivate::paintArea() const QRectF QuickImageItemPrivate::paintArea() const
{ {
Q_Q(const QuickImageItem); Q_Q(const QuickImageItem);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
const QSize size = q->size().toSize(); const QSizeF size = q->size();
#else #else
const QSize size = {int(std::round(q->width())), int(std::round(q->height()))}; const QSizeF size = {q->width(), q->height()};
#endif #endif
return {QPoint(0, 0), size}; return {QPointF(0, 0), size};
} }
QuickImageItem::QuickImageItem(QQuickItem *parent) QuickImageItem::QuickImageItem(QQuickItem *parent)

View File

@ -28,9 +28,7 @@
#include <FramelessHelper/Core/framelessmanager.h> #include <FramelessHelper/Core/framelessmanager.h>
#include <FramelessHelper/Core/private/micamaterial_p.h> #include <FramelessHelper/Core/private/micamaterial_p.h>
#include <QtCore/qloggingcategory.h> #include <QtCore/qloggingcategory.h>
#include <QtGui/qscreen.h>
#include <QtGui/qpainter.h> #include <QtGui/qpainter.h>
#include <QtGui/qguiapplication.h>
#include <QtQuick/qquickwindow.h> #include <QtQuick/qquickwindow.h>
#include <QtQuick/qsgsimpletexturenode.h> #include <QtQuick/qsgsimpletexturenode.h>
#ifndef FRAMELESSHELPER_QUICK_NO_PRIVATE #ifndef FRAMELESSHELPER_QUICK_NO_PRIVATE
@ -74,10 +72,12 @@ private:
void initialize(); void initialize();
private: private:
QSGTexture *m_texture = nullptr;
QPointer<QuickMicaMaterial> m_item = nullptr; QPointer<QuickMicaMaterial> m_item = nullptr;
QSGSimpleTextureNode *m_node = nullptr; QSGSimpleTextureNode *m_node = nullptr;
QPixmap m_pixmapCache = {}; std::unique_ptr<QSGTexture> m_texture = nullptr;
QPointer<MicaMaterial> m_mica{ nullptr };
QPointer<MicaMaterialPrivate> m_micaPriv{ nullptr };
QPointer<QQuickWindow> m_window{ nullptr };
}; };
WallpaperImageNode::WallpaperImageNode(QuickMicaMaterial *item) WallpaperImageNode::WallpaperImageNode(QuickMicaMaterial *item)
@ -90,12 +90,18 @@ WallpaperImageNode::WallpaperImageNode(QuickMicaMaterial *item)
initialize(); initialize();
} }
WallpaperImageNode::~WallpaperImageNode() = default; WallpaperImageNode::~WallpaperImageNode()
{
QuickMicaMaterialPrivate::get(m_item)->removeNode(this);
}
void WallpaperImageNode::initialize() void WallpaperImageNode::initialize()
{ {
QQuickWindow * const window = m_item->window(); m_window = m_item->window();
m_mica = QuickMicaMaterialPrivate::get(m_item)->m_micaMaterial;
m_micaPriv = MicaMaterialPrivate::get(m_mica);
// QtQuick's render engine will free it when appropriate.
m_node = new QSGSimpleTextureNode; m_node = new QSGSimpleTextureNode;
m_node->setFiltering(QSGTexture::Linear); m_node->setFiltering(QSGTexture::Linear);
@ -104,7 +110,7 @@ void WallpaperImageNode::initialize()
appendChildNode(m_node); appendChildNode(m_node);
connect(window, &QQuickWindow::beforeRendering, this, connect(m_window, &QQuickWindow::beforeRendering, this,
&WallpaperImageNode::maybeUpdateWallpaperImageClipRect, Qt::DirectConnection); &WallpaperImageNode::maybeUpdateWallpaperImageClipRect, Qt::DirectConnection);
QuickMicaMaterialPrivate::get(m_item)->appendNode(this); QuickMicaMaterialPrivate::get(m_item)->appendNode(this);
@ -112,24 +118,18 @@ void WallpaperImageNode::initialize()
void WallpaperImageNode::maybeGenerateWallpaperImageCache(const bool force) void WallpaperImageNode::maybeGenerateWallpaperImageCache(const bool force)
{ {
if (!m_pixmapCache.isNull() && !force) { if (m_texture && !force) {
return; return;
} }
const QSize desktopSize = QGuiApplication::primaryScreen()->virtualSize(); static constexpr const auto originPoint = QPoint{ 0, 0 };
static constexpr const QPoint originPoint = {0, 0}; const QSize imageSize = MicaMaterialPrivate::wallpaperSize();
m_pixmapCache = QPixmap(desktopSize); auto pixmap = QPixmap(imageSize);
m_pixmapCache.fill(kDefaultTransparentColor); pixmap.fill(kDefaultTransparentColor);
QPainter painter(&m_pixmapCache); QPainter painter(&pixmap);
MicaMaterial * const mica = QuickMicaMaterialPrivate::get(m_item)->m_micaMaterial;
Q_ASSERT(mica);
// We need the real wallpaper image here, so always use "active" state. // We need the real wallpaper image here, so always use "active" state.
mica->paint(&painter, desktopSize, originPoint, true); m_mica->paint(&painter, QRect{ originPoint, imageSize }, true);
if (m_texture) { m_texture.reset(m_window->createTextureFromImage(pixmap.toImage()));
delete m_texture; m_node->setTexture(m_texture.get());
m_texture = nullptr;
}
m_texture = m_item->window()->createTextureFromImage(m_pixmapCache.toImage());
m_node->setTexture(m_texture);
} }
void WallpaperImageNode::maybeUpdateWallpaperImageClipRect() void WallpaperImageNode::maybeUpdateWallpaperImageClipRect()
@ -140,7 +140,8 @@ void WallpaperImageNode::maybeUpdateWallpaperImageClipRect()
const QSizeF itemSize = {m_item->width(), m_item->height()}; const QSizeF itemSize = {m_item->width(), m_item->height()};
#endif #endif
m_node->setRect(QRectF(QPointF(0.0, 0.0), itemSize)); m_node->setRect(QRectF(QPointF(0.0, 0.0), itemSize));
m_node->setSourceRect(QRectF(m_item->mapToGlobal(QPointF(0.0, 0.0)), itemSize)); const auto rect = QRectF(m_item->mapToGlobal(QPointF(0.0, 0.0)), itemSize);
m_node->setSourceRect(m_micaPriv->mapToWallpaper(rect.toRect()));
} }
QuickMicaMaterialPrivate::QuickMicaMaterialPrivate(QuickMicaMaterial *q) : QObject(q) QuickMicaMaterialPrivate::QuickMicaMaterialPrivate(QuickMicaMaterial *q) : QObject(q)
@ -265,6 +266,18 @@ void QuickMicaMaterialPrivate::appendNode(WallpaperImageNode *node)
m_nodes.append(node); m_nodes.append(node);
} }
void QuickMicaMaterialPrivate::removeNode(WallpaperImageNode *node)
{
Q_ASSERT(node);
if (!node) {
return;
}
if (!m_nodes.contains(node)) {
return;
}
m_nodes.removeAll(node);
}
void QuickMicaMaterialPrivate::updateFallbackColor() void QuickMicaMaterialPrivate::updateFallbackColor()
{ {
#ifndef FRAMELESSHELPER_QUICK_NO_PRIVATE #ifndef FRAMELESSHELPER_QUICK_NO_PRIVATE

View File

@ -187,8 +187,8 @@ void WidgetsSharedHelper::repaintMica()
return; return;
} }
QPainter painter(m_targetWidget); QPainter painter(m_targetWidget);
m_micaMaterial->paint(&painter, m_targetWidget->size(), const QRect rect = { m_targetWidget->mapToGlobal(QPoint(0, 0)), m_targetWidget->size() };
m_targetWidget->mapToGlobal(QPoint(0, 0)), m_targetWidget->isActiveWindow()); m_micaMaterial->paint(&painter, rect, m_targetWidget->isActiveWindow());
} }
void WidgetsSharedHelper::repaintBorder() void WidgetsSharedHelper::repaintBorder()