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)
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_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${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Gui)

View File

@ -57,29 +57,33 @@ target_sources(${DEMO_NAME} PRIVATE
if(WIN32)
set(__rc_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.rc")
generate_win32_rc_file(
PATH "${__rc_path}"
VERSION "${PROJECT_VERSION}"
COMPANY "wangwenx190"
DESCRIPTION "FramelessHelper Demo Application: Dialog"
COPYRIGHT "MIT License"
PRODUCT "FramelessHelper Demo"
ICONS "../shared/example.ico"
)
if(NOT EXISTS "${__rc_path}")
generate_win32_rc_file(
PATH "${__rc_path}"
VERSION "${PROJECT_VERSION}"
COMPANY "wangwenx190"
DESCRIPTION "FramelessHelper Demo Application: Dialog"
COPYRIGHT "MIT License"
PRODUCT "FramelessHelper Demo"
ICONS "../shared/example.ico"
)
endif()
set(__manifest_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.manifest")
generate_win32_manifest_file(
PATH "${__manifest_path}"
ID "org.wangwenx190.demo.Dialog"
VERSION "${PROJECT_VERSION}"
VISTA_COMPAT
WIN7_COMPAT
WIN8_COMPAT
WIN8_1_COMPAT
WIN10_COMPAT
WIN11_COMPAT
XAML_ISLANDS_COMPAT
UTF8_CODEPAGE
)
if(NOT EXISTS "${__manifest_path}")
generate_win32_manifest_file(
PATH "${__manifest_path}"
ID "org.wangwenx190.demo.Dialog"
VERSION "${PROJECT_VERSION}"
VISTA_COMPAT
WIN7_COMPAT
WIN8_COMPAT
WIN8_1_COMPAT
WIN10_COMPAT
WIN11_COMPAT
XAML_ISLANDS_COMPAT
UTF8_CODEPAGE
)
endif()
target_sources(${DEMO_NAME} PRIVATE
"${__rc_path}"
"${__manifest_path}"

View File

@ -62,29 +62,33 @@ target_sources(${DEMO_NAME} PRIVATE
if(WIN32)
set(__rc_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.rc")
generate_win32_rc_file(
PATH "${__rc_path}"
VERSION "${PROJECT_VERSION}"
COMPANY "wangwenx190"
DESCRIPTION "FramelessHelper Demo Application: MainWindow"
COPYRIGHT "MIT License"
PRODUCT "FramelessHelper Demo"
ICONS "../shared/example.ico"
)
if(NOT EXISTS "${__rc_path}")
generate_win32_rc_file(
PATH "${__rc_path}"
VERSION "${PROJECT_VERSION}"
COMPANY "wangwenx190"
DESCRIPTION "FramelessHelper Demo Application: MainWindow"
COPYRIGHT "MIT License"
PRODUCT "FramelessHelper Demo"
ICONS "../shared/example.ico"
)
endif()
set(__manifest_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.manifest")
generate_win32_manifest_file(
PATH "${__manifest_path}"
ID "org.wangwenx190.demo.MainWindow"
VERSION "${PROJECT_VERSION}"
VISTA_COMPAT
WIN7_COMPAT
WIN8_COMPAT
WIN8_1_COMPAT
WIN10_COMPAT
WIN11_COMPAT
XAML_ISLANDS_COMPAT
UTF8_CODEPAGE
)
if(NOT EXISTS "${__manifest_path}")
generate_win32_manifest_file(
PATH "${__manifest_path}"
ID "org.wangwenx190.demo.MainWindow"
VERSION "${PROJECT_VERSION}"
VISTA_COMPAT
WIN7_COMPAT
WIN8_COMPAT
WIN8_1_COMPAT
WIN10_COMPAT
WIN11_COMPAT
XAML_ISLANDS_COMPAT
UTF8_CODEPAGE
)
endif()
target_sources(${DEMO_NAME} PRIVATE
"${__rc_path}"
"${__manifest_path}"

View File

@ -65,29 +65,33 @@ target_sources(${DEMO_NAME} PRIVATE
if(WIN32)
set(__rc_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.rc")
generate_win32_rc_file(
PATH "${__rc_path}"
VERSION "${PROJECT_VERSION}"
COMPANY "wangwenx190"
DESCRIPTION "FramelessHelper Demo Application: OpenGLWidget"
COPYRIGHT "MIT License"
PRODUCT "FramelessHelper Demo"
ICONS "../shared/example.ico"
)
if(NOT EXISTS "${__rc_path}")
generate_win32_rc_file(
PATH "${__rc_path}"
VERSION "${PROJECT_VERSION}"
COMPANY "wangwenx190"
DESCRIPTION "FramelessHelper Demo Application: OpenGLWidget"
COPYRIGHT "MIT License"
PRODUCT "FramelessHelper Demo"
ICONS "../shared/example.ico"
)
endif()
set(__manifest_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.manifest")
generate_win32_manifest_file(
PATH "${__manifest_path}"
ID "org.wangwenx190.demo.OpenGLWidget"
VERSION "${PROJECT_VERSION}"
VISTA_COMPAT
WIN7_COMPAT
WIN8_COMPAT
WIN8_1_COMPAT
WIN10_COMPAT
WIN11_COMPAT
XAML_ISLANDS_COMPAT
UTF8_CODEPAGE
)
if(NOT EXISTS "${__manifest_path}")
generate_win32_manifest_file(
PATH "${__manifest_path}"
ID "org.wangwenx190.demo.OpenGLWidget"
VERSION "${PROJECT_VERSION}"
VISTA_COMPAT
WIN7_COMPAT
WIN8_COMPAT
WIN8_1_COMPAT
WIN10_COMPAT
WIN11_COMPAT
XAML_ISLANDS_COMPAT
UTF8_CODEPAGE
)
endif()
target_sources(${DEMO_NAME} PRIVATE
"${__rc_path}"
"${__manifest_path}"

View File

@ -57,29 +57,33 @@ target_sources(${DEMO_NAME} PRIVATE
if(WIN32)
set(__rc_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.rc")
generate_win32_rc_file(
PATH "${__rc_path}"
VERSION "${PROJECT_VERSION}"
COMPANY "wangwenx190"
DESCRIPTION "FramelessHelper Demo Application: Quick"
COPYRIGHT "MIT License"
PRODUCT "FramelessHelper Demo"
ICONS "../shared/example.ico"
)
if(NOT EXISTS "${__rc_path}")
generate_win32_rc_file(
PATH "${__rc_path}"
VERSION "${PROJECT_VERSION}"
COMPANY "wangwenx190"
DESCRIPTION "FramelessHelper Demo Application: Quick"
COPYRIGHT "MIT License"
PRODUCT "FramelessHelper Demo"
ICONS "../shared/example.ico"
)
endif()
set(__manifest_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.manifest")
generate_win32_manifest_file(
PATH "${__manifest_path}"
ID "org.wangwenx190.demo.Quick"
VERSION "${PROJECT_VERSION}"
VISTA_COMPAT
WIN7_COMPAT
WIN8_COMPAT
WIN8_1_COMPAT
WIN10_COMPAT
WIN11_COMPAT
XAML_ISLANDS_COMPAT
UTF8_CODEPAGE
)
if(NOT EXISTS "${__manifest_path}")
generate_win32_manifest_file(
PATH "${__manifest_path}"
ID "org.wangwenx190.demo.Quick"
VERSION "${PROJECT_VERSION}"
VISTA_COMPAT
WIN7_COMPAT
WIN8_COMPAT
WIN8_1_COMPAT
WIN10_COMPAT
WIN11_COMPAT
XAML_ISLANDS_COMPAT
UTF8_CODEPAGE
)
endif()
target_sources(${DEMO_NAME} PRIVATE
"${__rc_path}"
"${__manifest_path}"

View File

@ -57,29 +57,33 @@ target_sources(${DEMO_NAME} PRIVATE
if(WIN32)
set(__rc_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.rc")
generate_win32_rc_file(
PATH "${__rc_path}"
VERSION "${PROJECT_VERSION}"
COMPANY "wangwenx190"
DESCRIPTION "FramelessHelper Demo Application: Widget"
COPYRIGHT "MIT License"
PRODUCT "FramelessHelper Demo"
ICONS "../shared/example.ico"
)
if(NOT EXISTS "${__rc_path}")
generate_win32_rc_file(
PATH "${__rc_path}"
VERSION "${PROJECT_VERSION}"
COMPANY "wangwenx190"
DESCRIPTION "FramelessHelper Demo Application: Widget"
COPYRIGHT "MIT License"
PRODUCT "FramelessHelper Demo"
ICONS "../shared/example.ico"
)
endif()
set(__manifest_path "${PROJECT_BINARY_DIR}/${DEMO_NAME}.manifest")
generate_win32_manifest_file(
PATH "${__manifest_path}"
ID "org.wangwenx190.demo.Widget"
VERSION "${PROJECT_VERSION}"
VISTA_COMPAT
WIN7_COMPAT
WIN8_COMPAT
WIN8_1_COMPAT
WIN10_COMPAT
WIN11_COMPAT
XAML_ISLANDS_COMPAT
UTF8_CODEPAGE
)
if(NOT EXISTS "${__manifest_path}")
generate_win32_manifest_file(
PATH "${__manifest_path}"
ID "org.wangwenx190.demo.Widget"
VERSION "${PROJECT_VERSION}"
VISTA_COMPAT
WIN7_COMPAT
WIN8_COMPAT
WIN8_1_COMPAT
WIN10_COMPAT
WIN11_COMPAT
XAML_ISLANDS_COMPAT
UTF8_CODEPAGE
)
endif()
target_sources(${DEMO_NAME} PRIVATE
"${__rc_path}"
"${__manifest_path}"

View File

@ -25,6 +25,7 @@
#pragma once
#include <FramelessHelper/Core/framelesshelpercore_global.h>
#include <QtCore/qrect.h>
FRAMELESSHELPER_BEGIN_NAMESPACE
@ -62,7 +63,13 @@ public:
void setFallbackEnabled(const bool value);
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:
void tintColorChanged();

View File

@ -103,3 +103,21 @@ FRAMELESSHELPER_CORE_API void registerInitializeHook(const InitializeHookCallbac
FRAMELESSHELPER_CORE_API void registerUninitializeHook(const UninitializeHookCallback &cb);
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 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:
void maybeGenerateBlurredWallpaper(const bool force = false);
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:
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 int horizontalAdvance(const QFontMetrics &fm, const QString &str);
[[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 bool isValidGeometry(const QRectF &rect);
[[nodiscard]] FRAMELESSHELPER_CORE_API bool isValidGeometry(const QRect &rect);
[[nodiscard]] FRAMELESSHELPER_CORE_API QColor getAccentColor();
[[nodiscard]] FRAMELESSHELPER_CORE_API quint32 defaultScreenDpi();

View File

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

View File

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

View File

@ -27,6 +27,7 @@
#include "framelessmanager.h"
#include "utils.h"
#include "framelessconfig_p.h"
#include "framelesshelpercore_global_p.h"
#include <QtCore/qsysinfo.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qmutex.h>
@ -57,6 +58,11 @@ static Q_LOGGING_CATEGORY(lcMicaMaterial, "wangwenx190.framelesshelper.core.mica
# define CRITICAL qCCritical(lcMicaMaterial)
#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;
[[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")
#endif // FRAMELESSHELPER_CORE_NO_BUNDLE_RESOURCE
struct MicaMaterialData
struct ImageData
{
QPixmap blurredWallpaper = {};
bool graphicsResourcesReady = false;
QMutex mutex{};
};
Q_GLOBAL_STATIC(MicaMaterialData, g_micaMaterialData)
[[maybe_unused]] [[nodiscard]] static inline constexpr bool operator>(const QSize &lhs, const QSize &rhs) noexcept
struct MetricsData
{
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
{
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));
}
Q_GLOBAL_STATIC(ImageData, g_imageData)
Q_GLOBAL_STATIC(MetricsData, g_metricsData)
#ifndef FRAMELESSHELPER_CORE_NO_PRIVATE
template<const int shift>
@ -516,13 +510,10 @@ protected:
void run() override
{
Transform transform = {};
// ### FIXME: Ideally, we should not use virtual desktop size here.
QSize monitorSize = QGuiApplication::primaryScreen()->virtualSize();
if (monitorSize.isEmpty()) {
WARNING << "Failed to retrieve the monitor size. Using default size (1920x1080) instead ...";
monitorSize = kMaximumPictureSize;
}
const QSize imageSize = (monitorSize > kMaximumPictureSize ? kMaximumPictureSize : monitorSize);
const QSize monitorSize = MicaMaterialPrivate::monitorSize();
const QSize imageSize = MicaMaterialPrivate::wallpaperSize();
// If we scaled the image size, record the scale factor and we need it to map our clip rect
// to the real (unscaled) rect.
if (imageSize != monitorSize) {
transform.Horizontal = (qreal(imageSize.width()) / qreal(monitorSize.width()));
transform.Vertical = (qreal(imageSize.height()) / qreal(monitorSize.height()));
@ -532,6 +523,8 @@ protected:
WARNING << "Failed to retrieve the wallpaper file path.";
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);
if (!reader.canRead()) {
WARNING << "Qt can't read the wallpaper file:" << reader.errorString();
@ -590,18 +583,19 @@ protected:
const QRect rect = alignedRect(Qt::LeftToRight, Qt::AlignCenter, image.size(), desktopRect);
bufferPainter.drawImage(rect.topLeft(), image);
}
g_micaMaterialData()->mutex.lock();
g_micaMaterialData()->blurredWallpaper = QPixmap(imageSize);
g_micaMaterialData()->blurredWallpaper.fill(kDefaultTransparentColor);
QPainter painter(&g_micaMaterialData()->blurredWallpaper);
painter.setRenderHints(QPainter::Antialiasing |
QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
{
const QMutexLocker locker(&g_imageData()->mutex);
g_imageData()->blurredWallpaper = QPixmap(imageSize);
g_imageData()->blurredWallpaper.fill(kDefaultTransparentColor);
QPainter painter(&g_imageData()->blurredWallpaper);
painter.setRenderHints(QPainter::Antialiasing |
QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
#ifdef FRAMELESSHELPER_CORE_NO_PRIVATE
painter.drawImage(desktopOriginPoint, buffer);
painter.drawImage(desktopOriginPoint, buffer);
#else // !FRAMELESSHELPER_CORE_NO_PRIVATE
qt_blurImage(&painter, buffer, kDefaultBlurRadius, true, false);
qt_blurImage(&painter, buffer, kDefaultBlurRadius, true, false);
#endif // FRAMELESSHELPER_CORE_NO_PRIVATE
g_micaMaterialData()->mutex.unlock();
}
Q_EMIT imageUpdated(transform);
}
};
@ -656,12 +650,12 @@ const MicaMaterialPrivate *MicaMaterialPrivate::get(const MicaMaterial *q)
void MicaMaterialPrivate::maybeGenerateBlurredWallpaper(const bool force)
{
g_micaMaterialData()->mutex.lock();
if (!g_micaMaterialData()->blurredWallpaper.isNull() && !force) {
g_micaMaterialData()->mutex.unlock();
g_imageData()->mutex.lock();
if (!g_imageData()->blurredWallpaper.isNull() && !force) {
g_imageData()->mutex.unlock();
return;
}
g_micaMaterialData()->mutex.unlock();
g_imageData()->mutex.unlock();
const QMutexLocker locker(&g_threadData()->mutex);
if (g_threadData()->thread->isRunning()) {
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(!size.isEmpty());
if (!painter || size.isEmpty()) {
if (!painter) {
return;
}
prepareGraphicsResources();
static constexpr const QPointF originPoint = {0, 0};
QPointF correctedPos = pos;
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);
}
static constexpr const QPoint originPoint = {0, 0};
const QRect mappedRect = mapToWallpaper(rect);
painter->save();
painter->setRenderHints(QPainter::Antialiasing |
QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
if (active) {
const QMutexLocker locker(&g_micaMaterialData()->mutex);
painter->drawPixmap(originPoint, g_micaMaterialData()->blurredWallpaper, QRectF{correctedPos, correctedSize});
const QMutexLocker locker(&g_imageData()->mutex);
painter->drawPixmap(originPoint, g_imageData()->blurredWallpaper, mappedRect);
}
painter->setCompositionMode(QPainter::CompositionMode_SourceOver);
painter->setOpacity(qreal(1));
painter->fillRect(QRectF{originPoint, correctedSize}, [this, active]() -> QBrush {
painter->fillRect(QRect{originPoint, mappedRect.size()}, [this, active]() -> QBrush {
if (!fallbackEnabled || active) {
return micaBrush;
}
@ -740,6 +722,15 @@ void MicaMaterialPrivate::paint(QPainter *painter, const QSize &size, const QPoi
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()
{
g_threadData()->mutex.lock();
@ -767,9 +758,9 @@ void MicaMaterialPrivate::initialize()
connect(FramelessManager::instance(), &FramelessManager::systemThemeChanged,
this, &MicaMaterialPrivate::updateMaterialBrush);
connect(FramelessManager::instance(), &FramelessManager::wallpaperChanged,
this, [this](){
maybeGenerateBlurredWallpaper(true);
});
this, &MicaMaterialPrivate::forceRebuildWallpaper);
connect(qGuiApp, &QGuiApplication::primaryScreenChanged,
this, &MicaMaterialPrivate::forceRebuildWallpaper);
if (FramelessConfig::instance()->isSet(Option::DisableLazyInitializationForMicaMaterial)) {
prepareGraphicsResources();
@ -780,13 +771,13 @@ void MicaMaterialPrivate::initialize()
void MicaMaterialPrivate::prepareGraphicsResources()
{
g_micaMaterialData()->mutex.lock();
if (g_micaMaterialData()->graphicsResourcesReady) {
g_micaMaterialData()->mutex.unlock();
g_imageData()->mutex.lock();
if (g_imageData()->graphicsResourcesReady) {
g_imageData()->mutex.unlock();
return;
}
g_micaMaterialData()->graphicsResourcesReady = true;
g_micaMaterialData()->mutex.unlock();
g_imageData()->graphicsResourcesReady = true;
g_imageData()->mutex.unlock();
maybeGenerateBlurredWallpaper();
}
@ -795,6 +786,123 @@ QColor MicaMaterialPrivate::systemFallbackColor()
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)
: QObject(parent), d_ptr(new MicaMaterialPrivate(this))
{
@ -900,10 +1008,10 @@ void MicaMaterial::setFallbackEnabled(const bool value)
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);
d->paint(painter, size, pos, active);
d->paint(painter, rect, active);
}
FRAMELESSHELPER_END_NAMESPACE

View File

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

View File

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

View File

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

View File

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