fix: reduce shader usage
This commit is contained in:
@@ -10,6 +10,8 @@ qml_module(ZShell-components
|
||||
sparklinecontent.hpp sparklinecontent.cpp
|
||||
sparklineitem.hpp sparklineitem.cpp
|
||||
sparklinesceneitem.hpp sparklinesceneitem.cpp
|
||||
roundedmask.hpp roundedmask.cpp
|
||||
roundedclip.hpp roundedclip.cpp
|
||||
wheelinverter.hpp
|
||||
LIBRARIES
|
||||
Qt::Quick
|
||||
@@ -17,3 +19,12 @@ qml_module(ZShell-components
|
||||
Qt::CanvasPainter
|
||||
ZShell-internal
|
||||
)
|
||||
|
||||
qt_add_shaders(ZShell-components "components_shaders"
|
||||
BATCHABLE OPTIMIZED NOHLSL NOMSL
|
||||
GLSL "300es,330"
|
||||
PREFIX "/"
|
||||
FILES
|
||||
shaders/roundedmask.frag
|
||||
shaders/roundedmask.vert
|
||||
)
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
#include "roundedclip.hpp"
|
||||
|
||||
#include <private/qquickclipnode_p.h>
|
||||
#include <private/qquickitem_p.h>
|
||||
|
||||
#include <qsggeometry.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace ZShell::components {
|
||||
|
||||
RoundedClip::RoundedClip(QQuickItem* parent) : QQuickItem(parent) {
|
||||
setFlag(ItemHasContents);
|
||||
setClip(true);
|
||||
}
|
||||
|
||||
bool RoundedClip::updateRadius(qreal& member, qreal radius) {
|
||||
if (qFuzzyCompare(member + 1.0, radius + 1.0)) return false;
|
||||
|
||||
member = radius;
|
||||
update();
|
||||
return true;
|
||||
}
|
||||
|
||||
void RoundedClip::geometryChange(
|
||||
const QRectF& newGeometry, const QRectF& oldGeometry) {
|
||||
QQuickItem::geometryChange(newGeometry, oldGeometry);
|
||||
if (newGeometry.size() != oldGeometry.size()) update();
|
||||
}
|
||||
|
||||
QSGNode* RoundedClip::updatePaintNode(
|
||||
QSGNode* oldNode, UpdatePaintNodeData* data) {
|
||||
Q_UNUSED(oldNode)
|
||||
Q_UNUSED(data)
|
||||
|
||||
QQuickItemPrivate* priv = QQuickItemPrivate::get(this);
|
||||
QQuickDefaultClipNode* clip = priv->clipNode();
|
||||
|
||||
if (!clip) return nullptr;
|
||||
|
||||
// Clip node geometry is in item-local coordinates. The clip node is a
|
||||
// child of the item's transform node, so the clip rect is
|
||||
// QRectF(0, 0, width, height), i.e. boundingRect()/clipRect().
|
||||
const QRectF r(0, 0, width(), height());
|
||||
|
||||
// Per-corner radii fall back to the shared `radius` shorthand.
|
||||
const qreal tl = m_topLeftRadius > 0 ? m_topLeftRadius : m_radius;
|
||||
const qreal tr = m_topRightRadius > 0 ? m_topRightRadius : m_radius;
|
||||
const qreal br = m_bottomRightRadius > 0 ? m_bottomRightRadius : m_radius;
|
||||
const qreal bl = m_bottomLeftRadius > 0 ? m_bottomLeftRadius : m_radius;
|
||||
|
||||
// Nothing rounded: plain rectangular clip.
|
||||
if (tl <= 0 && tr <= 0 && br <= 0 && bl <= 0) {
|
||||
clip->setRect(r);
|
||||
clip->update();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// All corners equal: let QQuickDefaultClipNode build its own known-correct
|
||||
// scanline geometry. This is the working path.
|
||||
if (qFuzzyCompare(tl, tr) && qFuzzyCompare(tr, br) &&
|
||||
qFuzzyCompare(br, bl)) {
|
||||
clip->setRect(r);
|
||||
clip->setRadius(tl);
|
||||
clip->update();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Per-corner radii. setRadius() supports only a single value, so we
|
||||
// must supply the geometry ourselves. Build it as a triangle fan from an
|
||||
// interior point so the polygon interior fills for the stencil clip.
|
||||
const qreal half = std::min(r.width() / 2, r.height() / 2);
|
||||
const qreal rtl = std::min(tl, half);
|
||||
const qreal rtr = std::min(tr, half);
|
||||
const qreal rbr = std::min(br, half);
|
||||
const qreal rbl = std::min(bl, half);
|
||||
|
||||
auto segments = [](qreal radius) {
|
||||
return qMin(30, int(std::ceil(radius)));
|
||||
};
|
||||
|
||||
const int s_tl = segments(rtl);
|
||||
const int s_tr = segments(rtr);
|
||||
const int s_br = segments(rbr);
|
||||
const int s_bl = segments(rbl);
|
||||
|
||||
clip->setRect(r);
|
||||
clip->setIsRectangular(false);
|
||||
|
||||
QSGGeometry* g = clip->geometry();
|
||||
g->setDrawingMode(QSGGeometry::DrawTriangleFan);
|
||||
|
||||
// One apex + one point per arc step + one point per zero-radius corner,
|
||||
// plus a closing apex to complete the fan.
|
||||
g->allocate(1 + s_tl + s_tr + s_br + s_bl + 5);
|
||||
|
||||
QSGGeometry::Point2D* vertices = g->vertexDataAsPoint2D();
|
||||
int vi = 0;
|
||||
|
||||
// Apex at an interior point.
|
||||
vertices[vi++] =
|
||||
QSGGeometry::Point2D(float(r.center().x()), float(r.center().y()));
|
||||
|
||||
// Each corner contributes its arc points (i = 0..segments) from its
|
||||
// edge-entry tangent to its edge-exit tangent. Consecutive corners are
|
||||
// joined by the implicit straight edges, forming the rounded rectangle.
|
||||
auto addArc =
|
||||
[&](int segs, qreal cx, qreal cy, qreal rad, qreal a0, qreal a1) {
|
||||
for (int i = 0; i <= segs; ++i) {
|
||||
const qreal angle = a0 + (a1 - a0) * qreal(i) / qreal(segs);
|
||||
vertices[vi++] = QSGGeometry::Point2D(
|
||||
float(cx + rad * std::cos(angle)),
|
||||
float(cy + rad * std::sin(angle)));
|
||||
}
|
||||
};
|
||||
|
||||
addArc(s_tl, rtl, rtl, rtl, M_PI, 3.0 * M_PI / 2.0);
|
||||
addArc(s_tr, r.width() - rtr, rtr, rtr, -M_PI / 2.0, 0.0);
|
||||
addArc(s_br, r.width() - rbr, r.height() - rbr, rbr, 0.0, M_PI / 2.0);
|
||||
addArc(s_bl, rbl, r.height() - rbl, rbl, M_PI / 2.0, M_PI);
|
||||
|
||||
// Close the fan back to the first perimeter vertex.
|
||||
vertices[vi] = vertices[1];
|
||||
|
||||
clip->markDirty(QSGNode::DirtyGeometry);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace ZShell::components
|
||||
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include <qqmlintegration.h>
|
||||
#include <qquickitem.h>
|
||||
#include <qsgnode.h>
|
||||
|
||||
namespace ZShell::components {
|
||||
|
||||
class RoundedClip : public QQuickItem {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged)
|
||||
Q_PROPERTY(
|
||||
qreal topLeftRadius READ topLeftRadius WRITE setTopLeftRadius NOTIFY
|
||||
topLeftRadiusChanged)
|
||||
Q_PROPERTY(
|
||||
qreal topRightRadius READ topRightRadius WRITE setTopRightRadius NOTIFY
|
||||
topRightRadiusChanged)
|
||||
Q_PROPERTY(
|
||||
qreal bottomRightRadius READ bottomRightRadius WRITE
|
||||
setBottomRightRadius NOTIFY bottomRightRadiusChanged)
|
||||
Q_PROPERTY(
|
||||
qreal bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius
|
||||
NOTIFY bottomLeftRadiusChanged)
|
||||
|
||||
public:
|
||||
explicit RoundedClip(QQuickItem* parent = nullptr);
|
||||
|
||||
qreal radius() const { return m_radius; }
|
||||
|
||||
void setRadius(qreal radius) {
|
||||
if (updateRadius(m_radius, radius)) emit radiusChanged();
|
||||
}
|
||||
|
||||
[[nodiscard]] qreal topLeftRadius() const { return m_topLeftRadius; }
|
||||
|
||||
void setTopLeftRadius(qreal radius) {
|
||||
if (updateRadius(m_topLeftRadius, radius)) emit topLeftRadiusChanged();
|
||||
}
|
||||
|
||||
[[nodiscard]] qreal topRightRadius() const { return m_topRightRadius; }
|
||||
|
||||
void setTopRightRadius(qreal radius) {
|
||||
if (updateRadius(m_topRightRadius, radius))
|
||||
emit topRightRadiusChanged();
|
||||
}
|
||||
|
||||
[[nodiscard]] qreal bottomRightRadius() const {
|
||||
return m_bottomRightRadius;
|
||||
}
|
||||
|
||||
void setBottomRightRadius(qreal radius) {
|
||||
if (updateRadius(m_bottomRightRadius, radius))
|
||||
emit bottomRightRadiusChanged();
|
||||
}
|
||||
|
||||
[[nodiscard]] qreal bottomLeftRadius() const { return m_bottomLeftRadius; }
|
||||
|
||||
void setBottomLeftRadius(qreal radius) {
|
||||
if (updateRadius(m_bottomLeftRadius, radius))
|
||||
emit bottomLeftRadiusChanged();
|
||||
}
|
||||
|
||||
signals:
|
||||
void radiusChanged();
|
||||
void topLeftRadiusChanged();
|
||||
void topRightRadiusChanged();
|
||||
void bottomRightRadiusChanged();
|
||||
void bottomLeftRadiusChanged();
|
||||
|
||||
protected:
|
||||
QSGNode* updatePaintNode(
|
||||
QSGNode* oldNode, UpdatePaintNodeData* data) override;
|
||||
void geometryChange(
|
||||
const QRectF& newGeometry, const QRectF& oldGeometry) override;
|
||||
|
||||
private:
|
||||
bool updateRadius(qreal& member, qreal radius);
|
||||
|
||||
qreal m_radius = 0;
|
||||
qreal m_topLeftRadius = 0;
|
||||
qreal m_topRightRadius = 0;
|
||||
qreal m_bottomRightRadius = 0;
|
||||
qreal m_bottomLeftRadius = 0;
|
||||
};
|
||||
|
||||
} // namespace ZShell::components
|
||||
@@ -0,0 +1,268 @@
|
||||
#include "roundedmask.hpp"
|
||||
|
||||
#include <qimage.h>
|
||||
#include <qquickwindow.h>
|
||||
#include <qsggeometry.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace ZShell::components {
|
||||
|
||||
using Qt::StringLiterals::operator""_s;
|
||||
|
||||
RoundedMask::RoundedMask(QQuickItem* parent) : QQuickItem(parent) {
|
||||
setFlag(ItemHasContents);
|
||||
}
|
||||
|
||||
QQuickItem* RoundedMask::source() const {
|
||||
return m_source;
|
||||
}
|
||||
|
||||
void RoundedMask::setSource(QQuickItem* source) {
|
||||
if (m_source == source) return;
|
||||
|
||||
m_source = source;
|
||||
emit sourceChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
qreal RoundedMask::topLeftRadius() const {
|
||||
return m_topLeftRadius;
|
||||
}
|
||||
|
||||
void RoundedMask::setTopLeftRadius(qreal radius) {
|
||||
if (updateRadius(m_topLeftRadius, radius)) emit topLeftRadiusChanged();
|
||||
}
|
||||
|
||||
qreal RoundedMask::topRightRadius() const {
|
||||
return m_topRightRadius;
|
||||
}
|
||||
|
||||
void RoundedMask::setTopRightRadius(qreal radius) {
|
||||
if (updateRadius(m_topRightRadius, radius)) emit topRightRadiusChanged();
|
||||
}
|
||||
|
||||
qreal RoundedMask::bottomRightRadius() const {
|
||||
return m_bottomRightRadius;
|
||||
}
|
||||
|
||||
void RoundedMask::setBottomRightRadius(qreal radius) {
|
||||
if (updateRadius(m_bottomRightRadius, radius))
|
||||
emit bottomRightRadiusChanged();
|
||||
}
|
||||
|
||||
qreal RoundedMask::bottomLeftRadius() const {
|
||||
return m_bottomLeftRadius;
|
||||
}
|
||||
|
||||
void RoundedMask::setBottomLeftRadius(qreal radius) {
|
||||
if (updateRadius(m_bottomLeftRadius, radius))
|
||||
emit bottomLeftRadiusChanged();
|
||||
}
|
||||
|
||||
bool RoundedMask::updateRadius(qreal& member, qreal radius) {
|
||||
if (qFuzzyCompare(member + 1.0, radius + 1.0)) return false;
|
||||
|
||||
member = radius;
|
||||
update();
|
||||
return true;
|
||||
}
|
||||
|
||||
QColor RoundedMask::color() const {
|
||||
return m_color;
|
||||
}
|
||||
|
||||
void RoundedMask::setColor(const QColor& color) {
|
||||
if (m_color == color) return;
|
||||
|
||||
m_color = color;
|
||||
emit colorChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
void RoundedMask::geometryChange(
|
||||
const QRectF& newGeometry, const QRectF& oldGeometry) {
|
||||
QQuickItem::geometryChange(newGeometry, oldGeometry);
|
||||
if (newGeometry.size() != oldGeometry.size()) update();
|
||||
}
|
||||
|
||||
void RoundedMask::releaseResources() {
|
||||
m_node = nullptr;
|
||||
}
|
||||
|
||||
void RoundedMask::invalidateSceneGraph() {
|
||||
m_node = nullptr;
|
||||
}
|
||||
|
||||
void RoundedMask::watchProvider(QSGTextureProvider* provider) {
|
||||
if (m_provider == provider) return;
|
||||
|
||||
if (m_provider) QObject::disconnect(m_provider, nullptr, this, nullptr);
|
||||
|
||||
m_provider = provider;
|
||||
if (!m_provider) return;
|
||||
|
||||
QObject::connect(
|
||||
m_provider,
|
||||
&QSGTextureProvider::textureChanged,
|
||||
this,
|
||||
&QQuickItem::update);
|
||||
// Emitted on the render thread by the provider's cleanup job; drop the texture before it is rendered again
|
||||
QObject::connect(
|
||||
m_provider,
|
||||
&QObject::destroyed,
|
||||
this,
|
||||
[this] {
|
||||
m_provider = nullptr;
|
||||
if (m_node) {
|
||||
static_cast<RoundedMaskMaterial*>(m_node->material())
|
||||
->m_texture = nullptr;
|
||||
m_node->markDirty(QSGNode::DirtyMaterial);
|
||||
}
|
||||
update();
|
||||
},
|
||||
Qt::DirectConnection);
|
||||
}
|
||||
|
||||
QSGNode* RoundedMask::updatePaintNode(
|
||||
QSGNode* oldNode, UpdatePaintNodeData* data) {
|
||||
Q_UNUSED(data);
|
||||
|
||||
auto* const provider = m_source && m_source->isTextureProvider()
|
||||
? m_source->textureProvider()
|
||||
: nullptr;
|
||||
watchProvider(provider);
|
||||
|
||||
auto* const texture = provider ? provider->texture() : nullptr;
|
||||
if (!texture || width() <= 0 || height() <= 0) {
|
||||
delete oldNode;
|
||||
m_node = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto* node = static_cast<RoundedMaskNode*>(oldNode);
|
||||
if (!node) node = new RoundedMaskNode;
|
||||
m_node = node;
|
||||
|
||||
QSGGeometry::updateTexturedRectGeometry(
|
||||
node->geometry(), boundingRect(), QRectF(0, 0, 1, 1));
|
||||
node->markDirty(QSGNode::DirtyGeometry);
|
||||
|
||||
const auto dpr = static_cast<float>(window()->effectiveDevicePixelRatio());
|
||||
auto* const material = static_cast<RoundedMaskMaterial*>(node->material());
|
||||
if (!material->m_fallback) {
|
||||
QImage blank(1, 1, QImage::Format_RGBA8888_Premultiplied);
|
||||
blank.fill(Qt::transparent);
|
||||
material->m_fallback.reset(window()->createTextureFromImage(blank));
|
||||
}
|
||||
material->m_texture = texture;
|
||||
material->m_size[0] = static_cast<float>(width()) * dpr;
|
||||
material->m_size[1] = static_cast<float>(height()) * dpr;
|
||||
const auto maxRadius = static_cast<float>(std::min(width(), height()) / 2);
|
||||
const auto clampRadius = [&](qreal radius) {
|
||||
return std::clamp(static_cast<float>(radius), 0.0f, maxRadius) * dpr;
|
||||
};
|
||||
material->m_radii[0] = clampRadius(m_topLeftRadius);
|
||||
material->m_radii[1] = clampRadius(m_topRightRadius);
|
||||
material->m_radii[2] = clampRadius(m_bottomRightRadius);
|
||||
material->m_radii[3] = clampRadius(m_bottomLeftRadius);
|
||||
const auto alpha = m_color.alphaF();
|
||||
material->m_color[0] = m_color.redF() * alpha;
|
||||
material->m_color[1] = m_color.greenF() * alpha;
|
||||
material->m_color[2] = m_color.blueF() * alpha;
|
||||
material->m_color[3] = alpha;
|
||||
node->markDirty(QSGNode::DirtyMaterial);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
QSGMaterialType* RoundedMaskMaterial::type() const {
|
||||
static QSGMaterialType s_type;
|
||||
return &s_type;
|
||||
}
|
||||
|
||||
QSGMaterialShader* RoundedMaskMaterial::createShader(
|
||||
QSGRendererInterface::RenderMode mode) const {
|
||||
Q_UNUSED(mode);
|
||||
return new RoundedMaskMaterialShader;
|
||||
}
|
||||
|
||||
int RoundedMaskMaterial::compare(const QSGMaterial* other) const {
|
||||
const auto* const o = static_cast<const RoundedMaskMaterial*>(other);
|
||||
if (m_texture != o->m_texture) return m_texture < o->m_texture ? -1 : 1;
|
||||
|
||||
const auto fuzzyEqual = [](float a, float b) {
|
||||
return qFuzzyCompare(a + 1.0f, b + 1.0f);
|
||||
};
|
||||
if (!std::ranges::equal(m_size, o->m_size, fuzzyEqual) ||
|
||||
!std::ranges::equal(m_radii, o->m_radii, fuzzyEqual) ||
|
||||
!std::ranges::equal(m_color, o->m_color, fuzzyEqual))
|
||||
return this < other ? -1 : 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
RoundedMaskMaterialShader::RoundedMaskMaterialShader() {
|
||||
setShaderFileName(VertexStage, u":/shaders/roundedmask.vert.qsb"_s);
|
||||
setShaderFileName(FragmentStage, u":/shaders/roundedmask.frag.qsb"_s);
|
||||
}
|
||||
|
||||
bool RoundedMaskMaterialShader::updateUniformData(
|
||||
RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) {
|
||||
Q_UNUSED(oldMaterial);
|
||||
auto* const mat = static_cast<RoundedMaskMaterial*>(newMaterial);
|
||||
QByteArray* const buf = state.uniformData();
|
||||
Q_ASSERT(buf->size() >= 112);
|
||||
|
||||
if (state.isMatrixDirty()) {
|
||||
const QMatrix4x4 m = state.combinedMatrix();
|
||||
std::memcpy(buf->data(), m.constData(), 64);
|
||||
}
|
||||
if (state.isOpacityDirty()) {
|
||||
const float opacity = state.opacity();
|
||||
std::memcpy(buf->data() + 64, &opacity, 4);
|
||||
}
|
||||
|
||||
std::memcpy(buf->data() + 72, mat->m_size, 8);
|
||||
std::memcpy(buf->data() + 80, mat->m_radii, 16);
|
||||
std::memcpy(buf->data() + 96, mat->m_color, 16);
|
||||
return true;
|
||||
}
|
||||
|
||||
void RoundedMaskMaterialShader::updateSampledImage(
|
||||
RenderState& state,
|
||||
int binding,
|
||||
QSGTexture** texture,
|
||||
QSGMaterial* newMaterial,
|
||||
QSGMaterial* oldMaterial) {
|
||||
Q_UNUSED(oldMaterial);
|
||||
if (binding != 1) return;
|
||||
|
||||
const auto* const mat =
|
||||
static_cast<const RoundedMaskMaterial*>(newMaterial);
|
||||
auto* const tex = mat->m_texture ? mat->m_texture : mat->m_fallback.data();
|
||||
tex->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
|
||||
*texture = tex;
|
||||
}
|
||||
|
||||
RoundedMaskNode::RoundedMaskNode() {
|
||||
auto* const geometry =
|
||||
new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4);
|
||||
geometry->setDrawingMode(QSGGeometry::DrawTriangleStrip);
|
||||
setGeometry(geometry);
|
||||
setFlag(OwnsGeometry);
|
||||
|
||||
auto* const material = new RoundedMaskMaterial;
|
||||
material->setFlag(QSGMaterial::Blending);
|
||||
setMaterial(material);
|
||||
setFlag(OwnsMaterial);
|
||||
setFlag(UsePreprocess);
|
||||
}
|
||||
|
||||
void RoundedMaskNode::preprocess() {
|
||||
auto* const texture = qobject_cast<QSGDynamicTexture*>(
|
||||
static_cast<RoundedMaskMaterial*>(material())->m_texture);
|
||||
if (texture && texture->updateTexture()) markDirty(DirtyMaterial);
|
||||
}
|
||||
|
||||
} // namespace ZShell::components
|
||||
@@ -0,0 +1,128 @@
|
||||
#pragma once
|
||||
|
||||
#include <qcolor.h>
|
||||
#include <qpointer.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qquickitem.h>
|
||||
#include <qscopedpointer.h>
|
||||
#include <qsgmaterial.h>
|
||||
#include <qsgmaterialshader.h>
|
||||
#include <qsgnode.h>
|
||||
#include <qsgtexture.h>
|
||||
#include <qsgtextureprovider.h>
|
||||
|
||||
namespace ZShell::components {
|
||||
|
||||
class RoundedMaskNode;
|
||||
|
||||
class RoundedMask : public QQuickItem {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(
|
||||
QQuickItem* source READ source WRITE setSource NOTIFY sourceChanged)
|
||||
Q_PROPERTY(
|
||||
qreal topLeftRadius READ topLeftRadius WRITE setTopLeftRadius NOTIFY
|
||||
topLeftRadiusChanged)
|
||||
Q_PROPERTY(
|
||||
qreal topRightRadius READ topRightRadius WRITE setTopRightRadius NOTIFY
|
||||
topRightRadiusChanged)
|
||||
Q_PROPERTY(
|
||||
qreal bottomRightRadius READ bottomRightRadius WRITE
|
||||
setBottomRightRadius NOTIFY bottomRightRadiusChanged)
|
||||
Q_PROPERTY(
|
||||
qreal bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius
|
||||
NOTIFY bottomLeftRadiusChanged)
|
||||
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
|
||||
|
||||
public:
|
||||
explicit RoundedMask(QQuickItem* parent = nullptr);
|
||||
|
||||
[[nodiscard]] QQuickItem* source() const;
|
||||
void setSource(QQuickItem* source);
|
||||
|
||||
[[nodiscard]] qreal topLeftRadius() const;
|
||||
void setTopLeftRadius(qreal radius);
|
||||
|
||||
[[nodiscard]] qreal topRightRadius() const;
|
||||
void setTopRightRadius(qreal radius);
|
||||
|
||||
[[nodiscard]] qreal bottomRightRadius() const;
|
||||
void setBottomRightRadius(qreal radius);
|
||||
|
||||
[[nodiscard]] qreal bottomLeftRadius() const;
|
||||
void setBottomLeftRadius(qreal radius);
|
||||
|
||||
[[nodiscard]] QColor color() const;
|
||||
void setColor(const QColor& color);
|
||||
|
||||
signals:
|
||||
void sourceChanged();
|
||||
void topLeftRadiusChanged();
|
||||
void topRightRadiusChanged();
|
||||
void bottomRightRadiusChanged();
|
||||
void bottomLeftRadiusChanged();
|
||||
void colorChanged();
|
||||
|
||||
protected:
|
||||
QSGNode* updatePaintNode(
|
||||
QSGNode* oldNode, UpdatePaintNodeData* data) override;
|
||||
void geometryChange(
|
||||
const QRectF& newGeometry, const QRectF& oldGeometry) override;
|
||||
void releaseResources() override;
|
||||
|
||||
private slots:
|
||||
void invalidateSceneGraph();
|
||||
|
||||
private:
|
||||
void watchProvider(QSGTextureProvider* provider);
|
||||
bool updateRadius(qreal& member, qreal radius);
|
||||
|
||||
QPointer<QQuickItem> m_source;
|
||||
qreal m_topLeftRadius = 0;
|
||||
qreal m_topRightRadius = 0;
|
||||
qreal m_bottomRightRadius = 0;
|
||||
qreal m_bottomLeftRadius = 0;
|
||||
QColor m_color = Qt::transparent;
|
||||
QSGTextureProvider* m_provider = nullptr;
|
||||
RoundedMaskNode* m_node = nullptr;
|
||||
};
|
||||
|
||||
class RoundedMaskMaterial : public QSGMaterial {
|
||||
public:
|
||||
[[nodiscard]] QSGMaterialType* type() const override;
|
||||
[[nodiscard]] QSGMaterialShader* createShader(
|
||||
QSGRendererInterface::RenderMode mode) const override;
|
||||
int compare(const QSGMaterial* other) const override;
|
||||
|
||||
QSGTexture* m_texture = nullptr;
|
||||
QScopedPointer<QSGTexture> m_fallback;
|
||||
float m_size[2] = {};
|
||||
float m_radii[4] = {};
|
||||
float m_color[4] = {};
|
||||
};
|
||||
|
||||
class RoundedMaskMaterialShader : public QSGMaterialShader {
|
||||
public:
|
||||
RoundedMaskMaterialShader();
|
||||
|
||||
bool updateUniformData(
|
||||
RenderState& state,
|
||||
QSGMaterial* newMaterial,
|
||||
QSGMaterial* oldMaterial) override;
|
||||
void updateSampledImage(
|
||||
RenderState& state,
|
||||
int binding,
|
||||
QSGTexture** texture,
|
||||
QSGMaterial* newMaterial,
|
||||
QSGMaterial* oldMaterial) override;
|
||||
};
|
||||
|
||||
class RoundedMaskNode : public QSGGeometryNode {
|
||||
public:
|
||||
RoundedMaskNode();
|
||||
|
||||
void preprocess() override;
|
||||
};
|
||||
|
||||
} // namespace ZShell::components
|
||||
@@ -0,0 +1,26 @@
|
||||
#version 440
|
||||
|
||||
layout(location = 0) in vec2 qt_TexCoord0;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
layout(std140, binding = 0) uniform buf {
|
||||
mat4 qt_Matrix;
|
||||
float qt_Opacity;
|
||||
vec2 size;
|
||||
vec4 radii;
|
||||
vec4 color;
|
||||
};
|
||||
|
||||
layout(binding = 1) uniform sampler2D source;
|
||||
|
||||
void main() {
|
||||
vec2 p = qt_TexCoord0 * size - size * 0.5;
|
||||
float radius = p.x < 0.0 ? (p.y < 0.0 ? radii.x : radii.w) : (p.y < 0.0 ? radii.y : radii.z);
|
||||
vec2 q = abs(p) - (size * 0.5 - radius);
|
||||
float d = length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius;
|
||||
float mask = 1.0 - smoothstep(-0.5, 0.5, d);
|
||||
|
||||
vec4 content = texture(source, qt_TexCoord0);
|
||||
vec4 blended = content + color * (1.0 - content.a);
|
||||
fragColor = blended * mask * qt_Opacity;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#version 440
|
||||
|
||||
layout(location = 0) in vec4 qt_VertexPosition;
|
||||
layout(location = 1) in vec2 qt_VertexTexCoord;
|
||||
|
||||
layout(location = 0) out vec2 qt_TexCoord0;
|
||||
|
||||
layout(std140, binding = 0) uniform buf {
|
||||
mat4 qt_Matrix;
|
||||
float qt_Opacity;
|
||||
vec2 size;
|
||||
vec4 radii;
|
||||
vec4 color;
|
||||
};
|
||||
|
||||
void main() {
|
||||
gl_Position = qt_Matrix * qt_VertexPosition;
|
||||
qt_TexCoord0 = qt_VertexTexCoord;
|
||||
}
|
||||
Reference in New Issue
Block a user