#include "BoxShadowNode.h" #include "QskBoxShapeMetrics.h" #include #include #include #include namespace { class Shader final : public QSGMaterialShader { public: Shader(); char const* const* attributeNames() const override; void initialize() override; void updateState( const QSGMaterialShader::RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) override; private: int m_matrixLocation = -1; int m_opacityLocation = -1; int m_aspectLocation = -1; int m_extentLocation = -1; int m_radiusLocation = -1; int m_colorLocation = -1; int m_offsetLocation = -1; }; class Material final : public QSGMaterial { public: Material(); QSGMaterialShader* createShader() const override; QSGMaterialType* type() const override; int compare( const QSGMaterial* other ) const override; QVector2D aspect = QVector2D{1.0, 1.0}; float extent = 0.0; QVector4D radius = QVector4D{0.0, 0.0, 0.0, 0.0}; QColor color = Qt::black; QVector2D offset; }; Shader::Shader() { const QString root( ":/qskinny/shaders/" ); setShaderSourceFile( QOpenGLShader::Vertex, root + "boxshadow.vert" ); setShaderSourceFile( QOpenGLShader::Fragment, root + "boxshadow.frag" ); } char const* const* Shader::attributeNames() const { static char const* const names[] = { "in_vertex", "in_coord", nullptr }; return names; } void Shader::initialize() { QSGMaterialShader::initialize(); auto p = program(); m_matrixLocation = p->uniformLocation( "matrix" ); m_aspectLocation = p->uniformLocation( "aspect" ); m_opacityLocation = p->uniformLocation( "opacity" ); m_extentLocation = p->uniformLocation( "extent" ); m_offsetLocation = p->uniformLocation( "offset" ); m_radiusLocation = p->uniformLocation( "radius" ); m_colorLocation = p->uniformLocation( "color" ); } void Shader::updateState( const QSGMaterialShader::RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial ) { auto p = program(); if ( state.isMatrixDirty() ) p->setUniformValue(m_matrixLocation, state.combinedMatrix() ); if ( state.isOpacityDirty() ) p->setUniformValue(m_opacityLocation, state.opacity() ); if ( oldMaterial == nullptr || newMaterial->compare( oldMaterial ) != 0 ) { auto material = static_cast< const Material* >( newMaterial ); p->setUniformValue( m_aspectLocation, material->aspect ); p->setUniformValue( m_extentLocation, material->extent ); p->setUniformValue( m_radiusLocation, material->radius ); p->setUniformValue( m_colorLocation, material->color ); p->setUniformValue( m_offsetLocation, material->offset ); } } Material::Material() { setFlag( QSGMaterial::Blending, true ); } QSGMaterialShader* Material::createShader() const { return new Shader(); } QSGMaterialType* Material::type() const { static QSGMaterialType staticType; return &staticType; } int Material::compare( const QSGMaterial* other ) const { auto material = static_cast< const Material* >( other ); if ( material->color == color && material->offset == offset && material->aspect == aspect && qFuzzyCompare(material->extent, extent) && qFuzzyCompare(material->radius, radius) ) { return 0; } return QSGMaterial::compare(other); } } class BoxShadowNodePrivate final : public QSGGeometryNodePrivate { public: BoxShadowNodePrivate() : geometry( QSGGeometry::defaultAttributes_TexturedPoint2D(), 4 ) { } QSGGeometry geometry; Material material; QRectF rect; }; BoxShadowNode::BoxShadowNode() : QSGGeometryNode( *new BoxShadowNodePrivate ) { Q_D( BoxShadowNode ); setGeometry( &d->geometry ); setMaterial( &d->material ); } BoxShadowNode::~BoxShadowNode() { } void BoxShadowNode::setRect( const QRectF& rect ) { Q_D( BoxShadowNode ); if ( rect == d->rect ) return; d->rect = rect; QVector2D newAspect( 1.0, 1.0 ); if ( rect.width() >= rect.height() ) newAspect.setX( rect.width() / rect.height() ); else newAspect.setY( rect.height() / rect.width() ); if ( d->material.aspect != newAspect) { d->material.aspect = newAspect; markDirty( QSGNode::DirtyMaterial ); } } void BoxShadowNode::setShape( const QskBoxShapeMetrics& shape ) { Q_D( BoxShadowNode ); const float t = 0.5 * std::min( d->rect.width(), d->rect.height() ); const float r1 = shape.radius( Qt::BottomRightCorner ).width(); const float r2 = shape.radius( Qt::TopRightCorner ).width(); const float r3 = shape.radius( Qt::BottomLeftCorner ).width(); const float r4 = shape.radius( Qt::TopLeftCorner ).width(); const auto uniformRadius = QVector4D( std::min( r1 / t, 1.0f ), std::min( r2 / t, 1.0f ), std::min( r3 / t, 1.0f ), std::min( r4 / t, 1.0f ) ); if ( d->material.radius != uniformRadius ) { d->material.radius = uniformRadius; markDirty( QSGNode::DirtyMaterial ); } } void BoxShadowNode::setColor( const QColor& color ) { Q_D( BoxShadowNode ); const auto a = color.alphaF(); const auto c = QColor::fromRgbF( color.redF() * a, color.greenF() * a, color.blueF() * a, a ); if ( d->material.color != c ) { d->material.color = c; markDirty(QSGNode::DirtyMaterial); } } void BoxShadowNode::setShadow( qreal extent, qreal dx, qreal dy ) { Q_D( BoxShadowNode ); if ( extent <= 0.0 ) extent = 0.0; const auto minDimension = std::min( d->rect.width(), d->rect.height() ); const float uniformExtent = ( extent / minDimension ) * 2.0; if ( !qFuzzyCompare( d->material.extent, uniformExtent ) ) { d->material.extent = uniformExtent; markDirty(QSGNode::DirtyMaterial); } const auto uniformOffset = QVector2D( dx, dy ) / minDimension; if ( d->material.offset != uniformOffset) { d->material.offset = uniformOffset; markDirty( QSGNode::DirtyMaterial ); } } void BoxShadowNode::updateGeometry() { Q_D( BoxShadowNode ); const auto sz = d->material.extent; const auto aspect = d->material.aspect; auto rect = d->rect.adjusted( -sz * aspect.x(), -sz * aspect.y(), sz * aspect.x(), sz * aspect.y() ); auto offsetLength = d->material.offset.length(); rect = rect.adjusted( -offsetLength * aspect.x(), -offsetLength * aspect.y(), offsetLength * aspect.x(), offsetLength * aspect.y() ); QSGGeometry::updateTexturedRectGeometry( &d->geometry, rect, QRectF( 0.0, 0.0, 1.0, 1.0 ) ); markDirty( QSGNode::DirtyGeometry ); }