mica material: fix multi monitor scenario

This commit is contained in:
Yuhang Zhao 2023-08-07 14:31:40 +08:00
parent 8d3ef93885
commit 177e377bb4
4 changed files with 86 additions and 207 deletions

View File

@ -26,15 +26,10 @@
#include <FramelessHelper/Quick/framelesshelperquick_global.h>
QT_BEGIN_NAMESPACE
class QQuickRectangle;
QT_END_NAMESPACE
FRAMELESSHELPER_BEGIN_NAMESPACE
class MicaMaterial;
class QuickMicaMaterial;
class WallpaperImageNode;
class FRAMELESSHELPER_QUICK_API QuickMicaMaterialPrivate : public QObject
{
@ -51,23 +46,16 @@ public:
public Q_SLOTS:
void rebindWindow();
void forceRegenerateWallpaperImageCache();
void appendNode(WallpaperImageNode *node);
void removeNode(WallpaperImageNode *node);
void updateFallbackColor();
void repaint(QPainter *painter);
private:
void initialize();
private:
friend class WallpaperImageNode;
QuickMicaMaterial *q_ptr = nullptr;
QMetaObject::Connection m_rootWindowXChangedConnection = {};
QMetaObject::Connection m_rootWindowYChangedConnection = {};
QMetaObject::Connection m_rootWindowActiveChangedConnection = {};
QList<QPointer<WallpaperImageNode>> m_nodes = {};
QQuickRectangle *m_fallbackColorItem = nullptr;
MicaMaterial *m_micaMaterial = nullptr;
};

View File

@ -25,13 +25,13 @@
#pragma once
#include <FramelessHelper/Quick/framelesshelperquick_global.h>
#include <QtQuick/qquickitem.h>
#include <QtQuick/qquickpainteditem.h>
FRAMELESSHELPER_BEGIN_NAMESPACE
class QuickMicaMaterialPrivate;
class FRAMELESSHELPER_QUICK_API QuickMicaMaterial : public QQuickItem
class FRAMELESSHELPER_QUICK_API QuickMicaMaterial : public QQuickPaintedItem
{
Q_OBJECT
#ifdef QML_NAMED_ELEMENT
@ -50,6 +50,8 @@ public:
explicit QuickMicaMaterial(QQuickItem *parent = nullptr);
~QuickMicaMaterial() override;
void paint(QPainter *painter) override;
Q_NODISCARD QColor tintColor() const;
void setTintColor(const QColor &value);
@ -74,7 +76,6 @@ Q_SIGNALS:
protected:
void itemChange(const ItemChange change, const ItemChangeData &value) override;
[[nodiscard]] QSGNode *updatePaintNode(QSGNode *old, UpdatePaintNodeData *data) override;
void classBegin() override;
void componentComplete() override;

View File

@ -704,6 +704,7 @@ void MicaMaterialPrivate::paint(QPainter *painter, const QRect &rect, const bool
}
prepareGraphicsResources();
static constexpr const QPoint originPoint = {0, 0};
const QRect wallpaperRect = { originPoint, wallpaperSize() };
const QRect mappedRect = mapToWallpaper(rect);
painter->save();
// Same as above. Speed is more important here.
@ -711,8 +712,39 @@ void MicaMaterialPrivate::paint(QPainter *painter, const QRect &rect, const bool
painter->setRenderHint(QPainter::TextAntialiasing, false);
painter->setRenderHint(QPainter::SmoothPixmapTransform, false);
if (active) {
const QRect intersectedRect = wallpaperRect.intersected(mappedRect);
g_imageData()->mutex.lock();
painter->drawPixmap(originPoint, g_imageData()->blurredWallpaper, intersectedRect);
g_imageData()->mutex.unlock();
if (intersectedRect != mappedRect) {
static constexpr const auto xOffset = QPoint{ 1, 0 };
if (mappedRect.y() + mappedRect.height() <= wallpaperRect.height()) {
const QRect outerRect = { intersectedRect.topRight() + xOffset, QSize{ mappedRect.width() - intersectedRect.width(), intersectedRect.height() } };
const QPoint outerRectOriginPoint = originPoint + QPoint{ intersectedRect.width(), 0 } + xOffset;
const QRect mappedOuterRect = mapToWallpaper(outerRect);
const QMutexLocker locker(&g_imageData()->mutex);
painter->drawPixmap(originPoint, g_imageData()->blurredWallpaper, mappedRect);
painter->drawPixmap(outerRectOriginPoint, g_imageData()->blurredWallpaper, mappedOuterRect);
} else {
static constexpr const auto yOffset = QPoint{ 0, 1 };
const QRect outerRectBottom = { intersectedRect.bottomLeft() + yOffset, QSize{ intersectedRect.width(), mappedRect.height() - intersectedRect.height() } };
const QPoint outerRectBottomOriginPoint = originPoint + QPoint{ 0, intersectedRect.height() } + yOffset;
const QRect mappedOuterRectBottom = mapToWallpaper(outerRectBottom);
g_imageData()->mutex.lock();
painter->drawPixmap(outerRectBottomOriginPoint, g_imageData()->blurredWallpaper, mappedOuterRectBottom);
g_imageData()->mutex.unlock();
if (mappedRect.x() + mappedRect.width() > wallpaperRect.width()) {
const QRect outerRectRight = { intersectedRect.topRight() + xOffset, QSize{ mappedRect.width() - intersectedRect.width(), intersectedRect.height() } };
const QPoint outerRectRightOriginPoint = originPoint + QPoint{ intersectedRect.width(), 0 } + xOffset;
const QRect mappedOuterRectRight = mapToWallpaper(outerRectRight);
const QRect outerRectCorner = { intersectedRect.bottomRight() + xOffset + yOffset, QSize{ outerRectRight.width(), outerRectBottom.height() } };
const QPoint outerRectCornerOriginPoint = originPoint + QPoint{ intersectedRect.width(), intersectedRect.height() } + xOffset + yOffset;
const QRect mappedOuterRectCorner = mapToWallpaper(outerRectCorner);
const QMutexLocker locker(&g_imageData()->mutex);
painter->drawPixmap(outerRectRightOriginPoint, g_imageData()->blurredWallpaper, mappedOuterRectRight);
painter->drawPixmap(outerRectCornerOriginPoint, g_imageData()->blurredWallpaper, mappedOuterRectCorner);
}
}
}
}
painter->setCompositionMode(QPainter::CompositionMode_SourceOver);
painter->setOpacity(qreal(1));
@ -854,13 +886,13 @@ QPoint MicaMaterialPrivate::mapToWallpaper(const QPoint &pos) const
while (result.x() < qreal(0)) {
result.setX(result.x() + imageSize.width());
}
while (result.x() > imageSize.width()) {
while ((result.x() > imageSize.width()) || qFuzzyCompare(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()) {
while ((result.y() > imageSize.height()) || qFuzzyCompare(result.y(), imageSize.height())) {
result.setY(result.y() - imageSize.height());
}
return result.toPoint();
@ -899,14 +931,7 @@ QRect MicaMaterialPrivate::mapToWallpaper(const QRect &rect) const
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();
return mappedRect.toRect();
}
MicaMaterial::MicaMaterial(QObject *parent)

View File

@ -25,16 +25,10 @@
#include "quickmicamaterial.h"
#include "quickmicamaterial_p.h"
#include <FramelessHelper/Core/micamaterial.h>
#include <FramelessHelper/Core/framelessmanager.h>
#include <FramelessHelper/Core/private/micamaterial_p.h>
#include <memory>
#include <QtCore/qloggingcategory.h>
#include <QtGui/qpainter.h>
#include <QtQuick/qquickwindow.h>
#include <QtQuick/qsgsimpletexturenode.h>
#ifndef FRAMELESSHELPER_QUICK_NO_PRIVATE
# include <QtQuick/private/qquickitem_p.h>
# include <QtQuick/private/qquickrectangle_p.h>
# include <QtQuick/private/qquickanchors_p.h>
#endif // FRAMELESSHELPER_QUICK_NO_PRIVATE
@ -56,94 +50,6 @@ FRAMELESSHELPER_BEGIN_NAMESPACE
using namespace Global;
class WallpaperImageNode : public QObject, public QSGTransformNode
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(WallpaperImageNode)
public:
explicit WallpaperImageNode(QuickMicaMaterial *item);
~WallpaperImageNode() override;
public Q_SLOTS:
void maybeUpdateWallpaperImageClipRect();
void maybeGenerateWallpaperImageCache(const bool force = false);
private:
void initialize();
private:
QPointer<QuickMicaMaterial> m_item = nullptr;
std::unique_ptr<QSGSimpleTextureNode> m_node = nullptr;
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)
{
Q_ASSERT(item);
if (!item) {
return;
}
m_item = item;
initialize();
}
WallpaperImageNode::~WallpaperImageNode()
{
QuickMicaMaterialPrivate::get(m_item)->removeNode(this);
}
void WallpaperImageNode::initialize()
{
m_window = m_item->window();
m_mica = QuickMicaMaterialPrivate::get(m_item)->m_micaMaterial;
m_micaPriv = MicaMaterialPrivate::get(m_mica);
m_node = std::make_unique<QSGSimpleTextureNode>();
m_node->setFiltering(QSGTexture::Linear);
maybeGenerateWallpaperImageCache();
maybeUpdateWallpaperImageClipRect();
appendChildNode(m_node.get());
connect(m_window, &QQuickWindow::beforeRendering, this,
&WallpaperImageNode::maybeUpdateWallpaperImageClipRect, Qt::DirectConnection);
QuickMicaMaterialPrivate::get(m_item)->appendNode(this);
}
void WallpaperImageNode::maybeGenerateWallpaperImageCache(const bool force)
{
if (m_texture && !force) {
return;
}
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.
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()
{
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
const QSizeF itemSize = m_item->size();
#else
const QSizeF itemSize = {m_item->width(), m_item->height()};
#endif
m_node->setRect(QRectF(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)
{
Q_ASSERT(q);
@ -178,15 +84,23 @@ void QuickMicaMaterialPrivate::initialize()
{
Q_Q(QuickMicaMaterial);
// Without this flag, our QQuickItem won't paint anything.
// We MUST enable this flag manually if we want to create a visible item.
q->setFlag(QuickMicaMaterial::ItemHasContents);
// No smooth needed.
// No smooth needed. The blurry image is already low quality, enabling
// smooth won't help much and we also don't want it to slow down the
// general performance.
q->setSmooth(false);
// We don't need anti-aliasing.
// We don't need anti-aliasing. Same reason as above.
q->setAntialiasing(false);
// Enable clipping, to improve performance in some certain cases.
q->setClip(true);
// Disable mipmap, we don't need high quality scaling here.
q->setMipmap(false);
// Mica material should not be translucent anyway, enabling this option
// will disable the alpha blending of this item, which can also improve
// the rendering performance.
q->setOpaquePainting(true);
// Set an invalid fill color to prevent QQuickPaintedItem from drawing the background,
// we don't need it anyway and it can improve the general performance as well.
q->setFillColor(QColor{});
m_micaMaterial = new MicaMaterial(this);
connect(m_micaMaterial, &MicaMaterial::tintColorChanged, q, &QuickMicaMaterial::tintColorChanged);
@ -194,18 +108,7 @@ void QuickMicaMaterialPrivate::initialize()
connect(m_micaMaterial, &MicaMaterial::fallbackColorChanged, q, &QuickMicaMaterial::fallbackColorChanged);
connect(m_micaMaterial, &MicaMaterial::noiseOpacityChanged, q, &QuickMicaMaterial::noiseOpacityChanged);
connect(m_micaMaterial, &MicaMaterial::fallbackEnabledChanged, q, &QuickMicaMaterial::fallbackEnabledChanged);
connect(m_micaMaterial, &MicaMaterial::shouldRedraw, this, &QuickMicaMaterialPrivate::forceRegenerateWallpaperImageCache);
#ifndef FRAMELESSHELPER_QUICK_NO_PRIVATE
m_fallbackColorItem = new QQuickRectangle(q);
QQuickItemPrivate::get(m_fallbackColorItem)->anchors()->setFill(q);
QQuickPen * const border = m_fallbackColorItem->border();
border->setColor(kDefaultTransparentColor);
border->setWidth(0);
updateFallbackColor();
m_fallbackColorItem->setVisible(false);
connect(FramelessManager::instance(), &FramelessManager::systemThemeChanged, this, &QuickMicaMaterialPrivate::updateFallbackColor);
#endif // FRAMELESSHELPER_QUICK_NO_PRIVATE
connect(m_micaMaterial, &MicaMaterial::shouldRedraw, q, [q](){ q->update(); });
}
void QuickMicaMaterialPrivate::rebindWindow()
@ -230,75 +133,50 @@ void QuickMicaMaterialPrivate::rebindWindow()
disconnect(m_rootWindowYChangedConnection);
m_rootWindowYChangedConnection = {};
}
m_rootWindowXChangedConnection = connect(window, &QQuickWindow::xChanged, q, [q](){ q->update(); });
m_rootWindowYChangedConnection = connect(window, &QQuickWindow::yChanged, q, [q](){ q->update(); });
#ifndef FRAMELESSHELPER_QUICK_NO_PRIVATE
if (m_rootWindowActiveChangedConnection) {
disconnect(m_rootWindowActiveChangedConnection);
m_rootWindowActiveChangedConnection = {};
}
m_rootWindowActiveChangedConnection = connect(window, &QQuickWindow::activeChanged, q, [this, window](){
if (m_micaMaterial->isFallbackEnabled()) {
m_fallbackColorItem->setVisible(!window->isActive());
} else {
m_fallbackColorItem->setVisible(false);
}
});
#endif // FRAMELESSHELPER_QUICK_NO_PRIVATE
m_rootWindowXChangedConnection = connect(window, &QQuickWindow::xChanged, q, [q](){ q->update(); });
m_rootWindowYChangedConnection = connect(window, &QQuickWindow::yChanged, q, [q](){ q->update(); });
m_rootWindowActiveChangedConnection = connect(window, &QQuickWindow::activeChanged, q, [q](){ q->update(); });
}
void QuickMicaMaterialPrivate::forceRegenerateWallpaperImageCache()
void QuickMicaMaterialPrivate::repaint(QPainter *painter)
{
if (m_nodes.isEmpty()) {
Q_ASSERT(painter);
Q_ASSERT(m_micaMaterial);
if (!painter || !m_micaMaterial) {
return;
}
for (auto &&node : std::as_const(m_nodes)) {
if (node) {
node->maybeGenerateWallpaperImageCache(true);
}
}
}
void QuickMicaMaterialPrivate::appendNode(WallpaperImageNode *node)
{
Q_ASSERT(node);
if (!node) {
return;
}
m_nodes.append(node);
}
void QuickMicaMaterialPrivate::removeNode(WallpaperImageNode *node)
{
Q_ASSERT(node);
if (!node) {
return;
}
m_nodes.removeAll(node);
}
void QuickMicaMaterialPrivate::updateFallbackColor()
{
#ifndef FRAMELESSHELPER_QUICK_NO_PRIVATE
if (!m_fallbackColorItem || !m_micaMaterial) {
return;
}
const QColor color = m_micaMaterial->fallbackColor();
if (color.isValid()) {
m_fallbackColorItem->setColor(color);
return;
}
m_fallbackColorItem->setColor(MicaMaterialPrivate::systemFallbackColor());
#endif // FRAMELESSHELPER_QUICK_NO_PRIVATE
Q_Q(QuickMicaMaterial);
const bool isActive = q->window() ? q->window()->isActive() : false;
const QPoint originPoint = q->mapToGlobal(QPointF{ 0, 0 }).toPoint();
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
const QSize size = q->size().toSize();
#else // (QT_VERSION < QT_VERSION_CHECK(5, 10, 0))
const QSize size = QSizeF{ q->width(), q->height() }.toSize();
#endif // (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
m_micaMaterial->paint(painter, QRect{ originPoint, size }, isActive);
}
QuickMicaMaterial::QuickMicaMaterial(QQuickItem *parent)
: QQuickItem(parent), d_ptr(new QuickMicaMaterialPrivate(this))
: QQuickPaintedItem(parent), d_ptr(new QuickMicaMaterialPrivate(this))
{
}
QuickMicaMaterial::~QuickMicaMaterial() = default;
void QuickMicaMaterial::paint(QPainter *painter)
{
Q_ASSERT(painter);
if (!painter) {
return;
}
Q_D(QuickMicaMaterial);
d->repaint(painter);
}
QColor QuickMicaMaterial::tintColor() const
{
Q_D(const QuickMicaMaterial);
@ -361,11 +239,10 @@ void QuickMicaMaterial::setFallbackEnabled(const bool value)
void QuickMicaMaterial::itemChange(const ItemChange change, const ItemChangeData &value)
{
QQuickItem::itemChange(change, value);
QQuickPaintedItem::itemChange(change, value);
Q_D(QuickMicaMaterial);
switch (change) {
case ItemDevicePixelRatioHasChanged: {
d->forceRegenerateWallpaperImageCache();
update(); // Force re-paint immediately.
} break;
case ItemSceneChange: {
@ -378,26 +255,14 @@ void QuickMicaMaterial::itemChange(const ItemChange change, const ItemChangeData
}
}
QSGNode *QuickMicaMaterial::updatePaintNode(QSGNode *old, UpdatePaintNodeData *data)
{
Q_UNUSED(data);
auto node = static_cast<WallpaperImageNode *>(old);
if (!node) {
node = new WallpaperImageNode(this);
}
return node;
}
void QuickMicaMaterial::classBegin()
{
QQuickItem::classBegin();
QQuickPaintedItem::classBegin();
}
void QuickMicaMaterial::componentComplete()
{
QQuickItem::componentComplete();
QQuickPaintedItem::componentComplete();
}
FRAMELESSHELPER_END_NAMESPACE
#include "quickmicamaterial.moc"