QskBoxShadowNode introduced ( from playground/shadows )
- shader code migrated from glsl to vulkan-glsl - #ifdef "Geschnetzel" introduced to support old and rhi graphic APIs - iotdashboard, shadows examples adjusted
This commit is contained in:
parent
e5af877246
commit
0ed9afe2b4
@ -6,15 +6,13 @@
|
||||
#include "LightDisplaySkinlet.h"
|
||||
#include "LightDisplay.h"
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
|
||||
#include "nodes/BoxShadowNode.h"
|
||||
#endif
|
||||
|
||||
#include "nodes/RadialTickmarksNode.h"
|
||||
|
||||
#include <QskArcMetrics.h>
|
||||
#include <QskTextOptions.h>
|
||||
#include <QskScaleTickmarks.h>
|
||||
#include <QskBoxShadowNode.h>
|
||||
#include <QskSGNode.h>
|
||||
|
||||
#include <QFontMetrics>
|
||||
#include <QtMath>
|
||||
@ -144,9 +142,7 @@ QSGNode* LightDisplaySkinlet::updateSubNode(
|
||||
if ( grooveRect.isEmpty() )
|
||||
return nullptr;
|
||||
|
||||
auto shadowNode = static_cast< BoxShadowNode* >( node );
|
||||
if ( shadowNode == nullptr )
|
||||
shadowNode = new BoxShadowNode();
|
||||
auto shadowNode = QskSGNode::ensureNode< QskBoxShadowNode >( node );
|
||||
|
||||
const auto& shadowMetrics = display->shadow();
|
||||
|
||||
|
@ -5,11 +5,10 @@
|
||||
|
||||
#include "ShadowedBox.h"
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
|
||||
#include "nodes/BoxShadowNode.h"
|
||||
#endif
|
||||
|
||||
#include <QskBoxNode.h>
|
||||
#include <QskBoxShadowNode.h>
|
||||
#include <QskSGNode.h>
|
||||
#include <QskBoxBorderMetrics.h>
|
||||
#include <QskBoxBorderColors.h>
|
||||
#include <QskGradient.h>
|
||||
@ -50,15 +49,12 @@ namespace
|
||||
|
||||
switch ( nodeRole )
|
||||
{
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
|
||||
case ShadowRole:
|
||||
{
|
||||
auto shadowNode = static_cast< BoxShadowNode* >( node );
|
||||
if ( shadowNode == nullptr )
|
||||
shadowNode = new BoxShadowNode();
|
||||
|
||||
const auto& shadowMetrics = box->shadow();
|
||||
|
||||
auto shadowNode = QskSGNode::ensureNode< QskBoxShadowNode >( node );
|
||||
|
||||
shadowNode->setRect( shadowMetrics.shadowRect( r ) );
|
||||
shadowNode->setShape( box->shape() );
|
||||
shadowNode->setBlurRadius( shadowMetrics.blurRadius() );
|
||||
@ -69,15 +65,11 @@ namespace
|
||||
|
||||
return shadowNode;
|
||||
}
|
||||
#endif
|
||||
case PanelRole:
|
||||
{
|
||||
auto boxNode = static_cast< QskBoxNode* >( node );
|
||||
if ( boxNode == nullptr )
|
||||
boxNode = new QskBoxNode();
|
||||
|
||||
const auto r = box->subControlRect( ShadowedBox::Panel );
|
||||
|
||||
auto boxNode = QskSGNode::ensureNode< QskBoxNode >( node );
|
||||
boxNode->setBoxData( r, box->shape(), box->borderWidth(),
|
||||
box->borderColor(), box->gradient() );
|
||||
|
||||
|
@ -64,16 +64,6 @@ HEADERS += \
|
||||
nodes/DiagramSegmentsNode.h \
|
||||
nodes/RadialTickmarksNode.h
|
||||
|
||||
lessThan(QT_MAJOR_VERSION, 6) {
|
||||
|
||||
# the shader for the drop shadows has not yet been migrated
|
||||
# to work with Qt 6
|
||||
|
||||
SOURCES += nodes/BoxShadowNode.cpp
|
||||
HEADERS += nodes/BoxShadowNode.h
|
||||
}
|
||||
|
||||
RESOURCES += \
|
||||
images.qrc \
|
||||
fonts.qrc \
|
||||
shaders.qrc
|
||||
fonts.qrc
|
||||
|
@ -1,256 +0,0 @@
|
||||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#include "BoxShadowNode.h"
|
||||
#include "QskBoxShapeMetrics.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QSGMaterialShader>
|
||||
#include <QSGMaterial>
|
||||
|
||||
#include <private/qsgnode_p.h>
|
||||
|
||||
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_matrixId = -1;
|
||||
int m_opacityId = -1;
|
||||
int m_aspectId = -1;
|
||||
int m_blurExtentId = -1;
|
||||
int m_radiusId = -1;
|
||||
int m_colorId = -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 blurExtent = 0.0;
|
||||
QVector4D radius = QVector4D{ 0.0, 0.0, 0.0, 0.0 };
|
||||
QColor color = Qt::black;
|
||||
};
|
||||
|
||||
Shader::Shader()
|
||||
{
|
||||
const QString root( ":/iotdashboard/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_matrixId = p->uniformLocation( "matrix" );
|
||||
m_aspectId = p->uniformLocation( "aspect" );
|
||||
m_opacityId = p->uniformLocation( "opacity" );
|
||||
m_blurExtentId = p->uniformLocation( "blurExtent" );
|
||||
m_radiusId = p->uniformLocation( "radius" );
|
||||
m_colorId = p->uniformLocation( "color" );
|
||||
}
|
||||
|
||||
void Shader::updateState( const QSGMaterialShader::RenderState& state,
|
||||
QSGMaterial* newMaterial, QSGMaterial* oldMaterial )
|
||||
{
|
||||
auto p = program();
|
||||
|
||||
if ( state.isMatrixDirty() )
|
||||
p->setUniformValue( m_matrixId, state.combinedMatrix() );
|
||||
|
||||
if ( state.isOpacityDirty() )
|
||||
p->setUniformValue( m_opacityId, state.opacity() );
|
||||
|
||||
if ( oldMaterial == nullptr || newMaterial->compare( oldMaterial ) != 0 )
|
||||
{
|
||||
auto material = static_cast< const Material* >( newMaterial );
|
||||
|
||||
p->setUniformValue( m_aspectId, material->aspect );
|
||||
p->setUniformValue( m_blurExtentId, material->blurExtent);
|
||||
p->setUniformValue( m_radiusId, material->radius );
|
||||
p->setUniformValue( m_colorId, material->color );
|
||||
}
|
||||
}
|
||||
|
||||
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->aspect == aspect
|
||||
&& qFuzzyCompare(material->blurExtent, blurExtent)
|
||||
&& 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 aspect( 1.0, 1.0 );
|
||||
|
||||
if ( rect.width() >= rect.height() )
|
||||
aspect.setX( rect.width() / rect.height() );
|
||||
else
|
||||
aspect.setY( rect.height() / rect.width() );
|
||||
|
||||
if ( d->material.aspect != aspect )
|
||||
{
|
||||
d->material.aspect = aspect;
|
||||
markDirty( QSGNode::DirtyMaterial );
|
||||
}
|
||||
}
|
||||
|
||||
void BoxShadowNode::setShape( const QskBoxShapeMetrics& shape )
|
||||
{
|
||||
Q_D( BoxShadowNode );
|
||||
|
||||
const float t = 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::setBlurRadius( qreal blurRadius )
|
||||
{
|
||||
Q_D( BoxShadowNode );
|
||||
|
||||
if ( blurRadius <= 0.0 )
|
||||
blurRadius = 0.0;
|
||||
|
||||
const float t = 0.5 * std::min( d->rect.width(), d->rect.height() );
|
||||
const float uniformExtent = blurRadius / t;
|
||||
|
||||
if ( !qFuzzyCompare( d->material.blurExtent, uniformExtent ) )
|
||||
{
|
||||
d->material.blurExtent = uniformExtent;
|
||||
markDirty( QSGNode::DirtyMaterial );
|
||||
}
|
||||
}
|
||||
|
||||
void BoxShadowNode::setClipRect( const QRectF& rect )
|
||||
{
|
||||
Q_UNUSED( rect )
|
||||
}
|
||||
|
||||
void BoxShadowNode::updateGeometry()
|
||||
{
|
||||
Q_D( BoxShadowNode );
|
||||
|
||||
QSGGeometry::updateTexturedRectGeometry(
|
||||
&d->geometry, d->rect, QRectF( -0.5, -0.5, 1.0, 1.0 ) );
|
||||
|
||||
markDirty( QSGNode::DirtyGeometry );
|
||||
}
|
@ -7,13 +7,7 @@ SUBDIRS += \
|
||||
inputpanel \
|
||||
images
|
||||
|
||||
lessThan(QT_MAJOR_VERSION, 6) {
|
||||
|
||||
# the shader for the drop shadows has not yet been migrated
|
||||
# to work with Qt 6
|
||||
|
||||
SUBDIRS += shadows
|
||||
}
|
||||
SUBDIRS += shadows
|
||||
|
||||
qtHaveModule(webengine) {
|
||||
|
||||
|
@ -1,261 +0,0 @@
|
||||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#include "BoxShadowNode.h"
|
||||
#include "QskBoxShapeMetrics.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QSGMaterialShader>
|
||||
#include <QSGMaterial>
|
||||
|
||||
#include <private/qsgnode_p.h>
|
||||
|
||||
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_matrixId = -1;
|
||||
int m_opacityId = -1;
|
||||
int m_aspectId = -1;
|
||||
int m_blurExtentId = -1;
|
||||
int m_radiusId = -1;
|
||||
int m_colorId = -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 blurExtent = 0.0;
|
||||
QVector4D radius = QVector4D{ 0.0, 0.0, 0.0, 0.0 };
|
||||
QColor color = Qt::black;
|
||||
};
|
||||
|
||||
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_matrixId = p->uniformLocation( "matrix" );
|
||||
m_aspectId = p->uniformLocation( "aspect" );
|
||||
m_opacityId = p->uniformLocation( "opacity" );
|
||||
m_blurExtentId = p->uniformLocation( "blurExtent" );
|
||||
m_radiusId = p->uniformLocation( "radius" );
|
||||
m_colorId = p->uniformLocation( "color" );
|
||||
}
|
||||
|
||||
void Shader::updateState( const QSGMaterialShader::RenderState& state,
|
||||
QSGMaterial* newMaterial, QSGMaterial* oldMaterial )
|
||||
{
|
||||
auto p = program();
|
||||
|
||||
if ( state.isMatrixDirty() )
|
||||
p->setUniformValue( m_matrixId, state.combinedMatrix() );
|
||||
|
||||
if ( state.isOpacityDirty() )
|
||||
p->setUniformValue( m_opacityId, state.opacity() );
|
||||
|
||||
bool updateMaterial = ( oldMaterial == nullptr )
|
||||
|| newMaterial->compare( oldMaterial ) != 0;
|
||||
|
||||
updateMaterial |= state.isCachedMaterialDataDirty();
|
||||
|
||||
if ( updateMaterial )
|
||||
{
|
||||
auto material = static_cast< const Material* >( newMaterial );
|
||||
|
||||
p->setUniformValue( m_aspectId, material->aspect );
|
||||
p->setUniformValue( m_blurExtentId, material->blurExtent);
|
||||
p->setUniformValue( m_radiusId, material->radius );
|
||||
p->setUniformValue( m_colorId, material->color );
|
||||
}
|
||||
}
|
||||
|
||||
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->aspect == aspect
|
||||
&& qFuzzyCompare(material->blurExtent, blurExtent)
|
||||
&& 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 aspect( 1.0, 1.0 );
|
||||
|
||||
if ( rect.width() >= rect.height() )
|
||||
aspect.setX( rect.width() / rect.height() );
|
||||
else
|
||||
aspect.setY( rect.height() / rect.width() );
|
||||
|
||||
if ( d->material.aspect != aspect )
|
||||
{
|
||||
d->material.aspect = aspect;
|
||||
markDirty( QSGNode::DirtyMaterial );
|
||||
}
|
||||
}
|
||||
|
||||
void BoxShadowNode::setShape( const QskBoxShapeMetrics& shape )
|
||||
{
|
||||
Q_D( BoxShadowNode );
|
||||
|
||||
const float t = 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::setBlurRadius( qreal blurRadius )
|
||||
{
|
||||
Q_D( BoxShadowNode );
|
||||
|
||||
if ( blurRadius <= 0.0 )
|
||||
blurRadius = 0.0;
|
||||
|
||||
const float t = 0.5 * std::min( d->rect.width(), d->rect.height() );
|
||||
const float uniformExtent = blurRadius / t;
|
||||
|
||||
if ( !qFuzzyCompare( d->material.blurExtent, uniformExtent ) )
|
||||
{
|
||||
d->material.blurExtent = uniformExtent;
|
||||
markDirty( QSGNode::DirtyMaterial );
|
||||
}
|
||||
}
|
||||
|
||||
void BoxShadowNode::setClipRect( const QRectF& rect )
|
||||
{
|
||||
Q_UNUSED( rect )
|
||||
}
|
||||
|
||||
void BoxShadowNode::updateGeometry()
|
||||
{
|
||||
Q_D( BoxShadowNode );
|
||||
|
||||
QSGGeometry::updateTexturedRectGeometry(
|
||||
&d->geometry, d->rect, QRectF( -0.5, -0.5, 1.0, 1.0 ) );
|
||||
|
||||
markDirty( QSGNode::DirtyGeometry );
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QSGGeometryNode>
|
||||
|
||||
class QColor;
|
||||
class QskBoxShapeMetrics;
|
||||
|
||||
class BoxShadowNodePrivate;
|
||||
|
||||
class BoxShadowNode : public QSGGeometryNode
|
||||
{
|
||||
public:
|
||||
BoxShadowNode();
|
||||
~BoxShadowNode() override;
|
||||
|
||||
void setRect( const QRectF& );
|
||||
void setShape( const QskBoxShapeMetrics& );
|
||||
void setColor( const QColor& );
|
||||
void setBlurRadius( qreal );
|
||||
|
||||
void setClipRect( const QRectF& );
|
||||
|
||||
void updateGeometry();
|
||||
|
||||
private:
|
||||
Q_DECLARE_PRIVATE( BoxShadowNode )
|
||||
};
|
@ -4,9 +4,10 @@
|
||||
*****************************************************************************/
|
||||
|
||||
#include "ShadowedBox.h"
|
||||
#include "BoxShadowNode.h"
|
||||
|
||||
#include <QskBoxNode.h>
|
||||
#include <QskBoxShadowNode.h>
|
||||
#include <QskSGNode.h>
|
||||
#include <QskBoxBorderMetrics.h>
|
||||
#include <QskBoxBorderColors.h>
|
||||
#include <QskGradient.h>
|
||||
@ -49,12 +50,10 @@ namespace
|
||||
{
|
||||
case ShadowRole:
|
||||
{
|
||||
auto shadowNode = static_cast< BoxShadowNode* >( node );
|
||||
if ( shadowNode == nullptr )
|
||||
shadowNode = new BoxShadowNode();
|
||||
|
||||
const auto& shadowMetrics = box->shadow();
|
||||
|
||||
auto shadowNode = QskSGNode::ensureNode< QskBoxShadowNode >( node );
|
||||
|
||||
shadowNode->setRect( shadowMetrics.shadowRect( r ) );
|
||||
shadowNode->setShape( box->shape() );
|
||||
shadowNode->setBlurRadius( shadowMetrics.blurRadius() );
|
||||
|
@ -1,7 +0,0 @@
|
||||
<!DOCTYPE RCC>
|
||||
<RCC version="1.0">
|
||||
<qresource prefix="/qskinny/shaders">
|
||||
<file>boxshadow.vert</file>
|
||||
<file>boxshadow.frag</file>
|
||||
</qresource>
|
||||
</RCC>
|
@ -1,14 +1,8 @@
|
||||
CONFIG += qskexample
|
||||
QT += quick_private
|
||||
|
||||
RESOURCES += \
|
||||
shaders.qrc
|
||||
|
||||
HEADERS += \
|
||||
ShadowedBox.h \
|
||||
BoxShadowNode.h
|
||||
|
||||
SOURCES += \
|
||||
ShadowedBox.cpp \
|
||||
BoxShadowNode.cpp \
|
||||
main.cpp
|
||||
|
365
src/nodes/QskBoxShadowNode.cpp
Normal file
365
src/nodes/QskBoxShadowNode.cpp
Normal file
@ -0,0 +1,365 @@
|
||||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the QSkinny License, Version 1.0
|
||||
*****************************************************************************/
|
||||
|
||||
#include "QskBoxShadowNode.h"
|
||||
#include "QskBoxShapeMetrics.h"
|
||||
|
||||
#include <qcolor.h>
|
||||
#include <qsgmaterialshader.h>
|
||||
#include <qsgmaterial.h>
|
||||
|
||||
QSK_QT_PRIVATE_BEGIN
|
||||
#include <private/qsgnode_p.h>
|
||||
QSK_QT_PRIVATE_END
|
||||
|
||||
// QSGMaterialRhiShader became QSGMaterialShader in Qt6
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
|
||||
#include <QSGMaterialRhiShader>
|
||||
using RhiShader = QSGMaterialRhiShader;
|
||||
#else
|
||||
using RhiShader = QSGMaterialShader;
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
class Material final : public QSGMaterial
|
||||
{
|
||||
public:
|
||||
Material();
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
|
||||
QSGMaterialShader* createShader() const override;
|
||||
#else
|
||||
QSGMaterialShader* createShader( QSGRendererInterface::RenderMode ) const override;
|
||||
#endif
|
||||
|
||||
QSGMaterialType* type() const override;
|
||||
|
||||
int compare( const QSGMaterial* other ) const override;
|
||||
|
||||
QVector2D m_aspect = QVector2D{ 1, 1 };
|
||||
QVector4D m_radius = QVector4D{ 0, 0, 0, 0 };
|
||||
QVector4D m_color = QVector4D{ 0, 0, 0, 1 };
|
||||
float m_blurExtent = 0.0;
|
||||
};
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
class ShaderRhi final : public RhiShader
|
||||
{
|
||||
public:
|
||||
ShaderRhi()
|
||||
{
|
||||
const QString root( ":/qskinny/shaders/" );
|
||||
|
||||
setShaderFileName( VertexStage, root + "boxshadow.vert.qsb" );
|
||||
setShaderFileName( FragmentStage, root + "boxshadow.frag.qsb" );
|
||||
}
|
||||
|
||||
bool updateUniformData( RenderState& state,
|
||||
QSGMaterial* newMaterial, QSGMaterial* oldMaterial ) override
|
||||
{
|
||||
const auto matOld = static_cast< Material* >( oldMaterial );
|
||||
const auto matNew = static_cast< Material* >( newMaterial );
|
||||
|
||||
Q_ASSERT( state.uniformData()->size() >= 112 );
|
||||
|
||||
auto data = state.uniformData()->data();
|
||||
bool changed = false;
|
||||
|
||||
if ( state.isMatrixDirty() )
|
||||
{
|
||||
const auto matrix = state.combinedMatrix();
|
||||
memcpy( data + 0, matrix.constData(), 64 );
|
||||
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if ( matOld == nullptr || matNew->m_color != matOld->m_color )
|
||||
{
|
||||
memcpy( data + 64, &matNew->m_color, 16 );
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if ( matOld == nullptr || matNew->m_radius != matOld->m_radius )
|
||||
{
|
||||
memcpy( data + 80, &matNew->m_radius, 16 );
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if ( matOld == nullptr || matNew->m_aspect != matOld->m_aspect )
|
||||
{
|
||||
memcpy( data + 96, &matNew->m_aspect, 8 );
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if ( matOld == nullptr || matNew->m_blurExtent != matOld->m_blurExtent )
|
||||
{
|
||||
memcpy( data + 104, &matNew->m_blurExtent, 4 );
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if ( state.isOpacityDirty() )
|
||||
{
|
||||
const float opacity = state.opacity();
|
||||
memcpy( data + 108, &opacity, 4 );
|
||||
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
|
||||
|
||||
namespace
|
||||
{
|
||||
// the old type of shader - spcific for OpenGL
|
||||
|
||||
class ShaderGL final : public QSGMaterialShader
|
||||
{
|
||||
public:
|
||||
ShaderGL()
|
||||
{
|
||||
const QString root( ":/qskinny/shaders/" );
|
||||
|
||||
setShaderSourceFile( QOpenGLShader::Vertex, root + "boxshadow.vert" );
|
||||
setShaderSourceFile( QOpenGLShader::Fragment, root + "boxshadow.frag" );
|
||||
}
|
||||
|
||||
char const* const* attributeNames() const override
|
||||
{
|
||||
static char const* const names[] = { "in_vertex", "in_coord", nullptr };
|
||||
return names;
|
||||
}
|
||||
|
||||
void initialize() override
|
||||
{
|
||||
QSGMaterialShader::initialize();
|
||||
|
||||
auto p = program();
|
||||
|
||||
m_matrixId = p->uniformLocation( "matrix" );
|
||||
m_aspectId = p->uniformLocation( "aspect" );
|
||||
m_opacityId = p->uniformLocation( "opacity" );
|
||||
m_blurExtentId = p->uniformLocation( "blurExtent" );
|
||||
m_radiusId = p->uniformLocation( "radius" );
|
||||
m_colorId = p->uniformLocation( "color" );
|
||||
}
|
||||
|
||||
void updateState( const QSGMaterialShader::RenderState& state,
|
||||
QSGMaterial* newMaterial, QSGMaterial* oldMaterial) override
|
||||
{
|
||||
auto p = program();
|
||||
|
||||
if ( state.isMatrixDirty() )
|
||||
p->setUniformValue( m_matrixId, state.combinedMatrix() );
|
||||
|
||||
if ( state.isOpacityDirty() )
|
||||
p->setUniformValue( m_opacityId, state.opacity() );
|
||||
|
||||
bool updateMaterial = ( oldMaterial == nullptr )
|
||||
|| newMaterial->compare( oldMaterial ) != 0;
|
||||
|
||||
updateMaterial |= state.isCachedMaterialDataDirty();
|
||||
|
||||
if ( updateMaterial )
|
||||
{
|
||||
auto material = static_cast< const Material* >( newMaterial );
|
||||
|
||||
p->setUniformValue( m_aspectId, material->m_aspect );
|
||||
p->setUniformValue( m_blurExtentId, material->m_blurExtent);
|
||||
p->setUniformValue( m_radiusId, material->m_radius );
|
||||
p->setUniformValue( m_colorId, material->m_color );
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int m_matrixId = -1;
|
||||
int m_opacityId = -1;
|
||||
int m_aspectId = -1;
|
||||
int m_blurExtentId = -1;
|
||||
int m_radiusId = -1;
|
||||
int m_colorId = -1;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Material::Material()
|
||||
{
|
||||
setFlag( QSGMaterial::Blending, true );
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
|
||||
setFlag( QSGMaterial::SupportsRhiShader, true );
|
||||
#endif
|
||||
}
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
|
||||
|
||||
QSGMaterialShader* Material::createShader() const
|
||||
{
|
||||
if ( !( flags() & QSGMaterial::RhiShaderWanted ) )
|
||||
return new ShaderGL();
|
||||
|
||||
return new ShaderRhi();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
QSGMaterialShader* Material::createShader( QSGRendererInterface::RenderMode ) const
|
||||
{
|
||||
return new ShaderRhi();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
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->m_color != m_color
|
||||
|| material->m_aspect != m_aspect
|
||||
|| !qFuzzyCompare(material->m_blurExtent, m_blurExtent)
|
||||
|| !qFuzzyCompare(material->m_radius, m_radius) )
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return QSGMaterial::compare( other );
|
||||
}
|
||||
|
||||
class QskBoxShadowNodePrivate final : public QSGGeometryNodePrivate
|
||||
{
|
||||
public:
|
||||
QskBoxShadowNodePrivate()
|
||||
: geometry( QSGGeometry::defaultAttributes_TexturedPoint2D(), 4 )
|
||||
{
|
||||
}
|
||||
|
||||
QSGGeometry geometry;
|
||||
Material material;
|
||||
|
||||
QRectF rect;
|
||||
};
|
||||
|
||||
QskBoxShadowNode::QskBoxShadowNode()
|
||||
: QSGGeometryNode( *new QskBoxShadowNodePrivate )
|
||||
{
|
||||
Q_D( QskBoxShadowNode );
|
||||
|
||||
setGeometry( &d->geometry );
|
||||
setMaterial( &d->material );
|
||||
}
|
||||
|
||||
QskBoxShadowNode::~QskBoxShadowNode()
|
||||
{
|
||||
}
|
||||
|
||||
void QskBoxShadowNode::setRect( const QRectF& rect )
|
||||
{
|
||||
Q_D( QskBoxShadowNode );
|
||||
|
||||
if ( rect == d->rect )
|
||||
return;
|
||||
|
||||
d->rect = rect;
|
||||
|
||||
QVector2D aspect( 1.0, 1.0 );
|
||||
|
||||
if ( rect.width() >= rect.height() )
|
||||
aspect.setX( rect.width() / rect.height() );
|
||||
else
|
||||
aspect.setY( rect.height() / rect.width() );
|
||||
|
||||
if ( d->material.m_aspect != aspect )
|
||||
{
|
||||
d->material.m_aspect = aspect;
|
||||
markDirty( QSGNode::DirtyMaterial );
|
||||
}
|
||||
}
|
||||
|
||||
void QskBoxShadowNode::setShape( const QskBoxShapeMetrics& shape )
|
||||
{
|
||||
Q_D( QskBoxShadowNode );
|
||||
|
||||
const float t = 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.m_radius != uniformRadius )
|
||||
{
|
||||
d->material.m_radius = uniformRadius;
|
||||
|
||||
markDirty( QSGNode::DirtyMaterial );
|
||||
}
|
||||
}
|
||||
|
||||
void QskBoxShadowNode::setColor( const QColor& color )
|
||||
{
|
||||
Q_D( QskBoxShadowNode );
|
||||
|
||||
const auto a = color.alphaF();
|
||||
|
||||
const QVector4D c( color.redF() * a, color.greenF() * a, color.blueF() * a, a );
|
||||
|
||||
if ( d->material.m_color != c )
|
||||
{
|
||||
d->material.m_color = c;
|
||||
markDirty( QSGNode::DirtyMaterial );
|
||||
}
|
||||
}
|
||||
|
||||
void QskBoxShadowNode::setBlurRadius( qreal blurRadius )
|
||||
{
|
||||
Q_D( QskBoxShadowNode );
|
||||
|
||||
if ( blurRadius <= 0.0 )
|
||||
blurRadius = 0.0;
|
||||
|
||||
const float t = 0.5 * std::min( d->rect.width(), d->rect.height() );
|
||||
const float uniformExtent = blurRadius / t;
|
||||
|
||||
if ( !qFuzzyCompare( d->material.m_blurExtent, uniformExtent ) )
|
||||
{
|
||||
d->material.m_blurExtent = uniformExtent;
|
||||
markDirty( QSGNode::DirtyMaterial );
|
||||
}
|
||||
}
|
||||
|
||||
void QskBoxShadowNode::setClipRect( const QRectF& )
|
||||
{
|
||||
/*
|
||||
Usually only the parts, that are not covered by the related rectangle
|
||||
should be painted. TODO ...
|
||||
*/
|
||||
}
|
||||
|
||||
void QskBoxShadowNode::updateGeometry()
|
||||
{
|
||||
Q_D( QskBoxShadowNode );
|
||||
|
||||
QSGGeometry::updateTexturedRectGeometry(
|
||||
&d->geometry, d->rect, QRectF( -0.5, -0.5, 1.0, 1.0 ) );
|
||||
|
||||
markDirty( QSGNode::DirtyGeometry );
|
||||
}
|
@ -1,32 +1,35 @@
|
||||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
* This file may be used under the terms of the QSkinny License, Version 1.0
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
#ifndef QSK_BOX_SHADOW_NODE_H
|
||||
#define QSK_BOX_SHADOW_NODE_H
|
||||
|
||||
#include "QskGlobal.h"
|
||||
#include <QSGGeometryNode>
|
||||
|
||||
class QColor;
|
||||
class QskBoxShapeMetrics;
|
||||
|
||||
class BoxShadowNodePrivate;
|
||||
class QskBoxShadowNodePrivate;
|
||||
|
||||
class BoxShadowNode : public QSGGeometryNode
|
||||
class QSK_EXPORT QskBoxShadowNode : public QSGGeometryNode
|
||||
{
|
||||
public:
|
||||
BoxShadowNode();
|
||||
~BoxShadowNode() override;
|
||||
QskBoxShadowNode();
|
||||
~QskBoxShadowNode() override;
|
||||
|
||||
void setRect( const QRectF& );
|
||||
void setShape( const QskBoxShapeMetrics& );
|
||||
void setColor( const QColor& );
|
||||
void setBlurRadius( qreal );
|
||||
|
||||
void updateGeometry();
|
||||
void setClipRect( const QRectF& );
|
||||
|
||||
void updateGeometry();
|
||||
|
||||
private:
|
||||
Q_DECLARE_PRIVATE( BoxShadowNode )
|
||||
Q_DECLARE_PRIVATE( QskBoxShadowNode )
|
||||
};
|
||||
|
||||
#endif
|
9
src/nodes/shaders.qrc
Normal file
9
src/nodes/shaders.qrc
Normal file
@ -0,0 +1,9 @@
|
||||
<!DOCTYPE RCC>
|
||||
<RCC version="1.0">
|
||||
<qresource prefix="/qskinny/">
|
||||
<file>shaders/boxshadow.vert.qsb</file>
|
||||
<file>shaders/boxshadow.frag.qsb</file>
|
||||
<file>shaders/boxshadow.vert</file>
|
||||
<file>shaders/boxshadow.frag</file>
|
||||
</qresource>
|
||||
</RCC>
|
49
src/nodes/shaders/boxshadow-vulkan.frag
Normal file
49
src/nodes/shaders/boxshadow-vulkan.frag
Normal file
@ -0,0 +1,49 @@
|
||||
#version 440
|
||||
|
||||
layout(location = 0) in vec2 coord;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
layout(std140, binding = 0) uniform buf
|
||||
{
|
||||
mat4 matrix;
|
||||
vec4 color;
|
||||
vec4 radius;
|
||||
vec2 aspect;
|
||||
float blurExtent;
|
||||
float opacity;
|
||||
} ubuf;
|
||||
|
||||
float effectiveRadius( in vec4 radii, in vec2 point )
|
||||
{
|
||||
if ( point.x > 0.0 )
|
||||
return ( point.y > 0.0) ? radii.x : radii.y;
|
||||
else
|
||||
return ( point.y > 0.0) ? radii.z : radii.w;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 col = vec4(0.0);
|
||||
|
||||
if ( ubuf.opacity > 0.0 )
|
||||
{
|
||||
const float minRadius = 0.05;
|
||||
|
||||
float e2 = 0.5 * ubuf.blurExtent;
|
||||
float r = 2.0 * effectiveRadius( ubuf.radius, coord );
|
||||
|
||||
float f = minRadius / max( r, minRadius );
|
||||
|
||||
r += e2 * f;
|
||||
|
||||
vec2 d = r + ubuf.blurExtent - ubuf.aspect * ( 1.0 - abs( 2.0 * coord ) );
|
||||
float l = min( max(d.x, d.y), 0.0) + length( max(d, 0.0) );
|
||||
|
||||
float shadow = l - r;
|
||||
|
||||
float v = smoothstep( -e2, e2, shadow );
|
||||
col = mix( ubuf.color, vec4(0.0), v ) * ubuf.opacity;
|
||||
}
|
||||
|
||||
fragColor = col;
|
||||
}
|
24
src/nodes/shaders/boxshadow-vulkan.vert
Normal file
24
src/nodes/shaders/boxshadow-vulkan.vert
Normal file
@ -0,0 +1,24 @@
|
||||
#version 440
|
||||
|
||||
layout(location = 0) in vec4 in_vertex;
|
||||
layout(location = 1) in vec2 in_coord;
|
||||
|
||||
layout(location = 0) out vec2 coord;
|
||||
|
||||
layout(std140, binding = 0) uniform buf
|
||||
{
|
||||
mat4 matrix;
|
||||
vec4 color;
|
||||
vec4 radius;
|
||||
vec2 aspect;
|
||||
float blurExtent;
|
||||
float opacity;
|
||||
} ubuf;
|
||||
|
||||
out gl_PerVertex { vec4 gl_Position; };
|
||||
|
||||
void main()
|
||||
{
|
||||
coord = in_coord;
|
||||
gl_Position = ubuf.matrix * in_vertex;
|
||||
}
|
BIN
src/nodes/shaders/boxshadow.frag.qsb
Normal file
BIN
src/nodes/shaders/boxshadow.frag.qsb
Normal file
Binary file not shown.
BIN
src/nodes/shaders/boxshadow.vert.qsb
Normal file
BIN
src/nodes/shaders/boxshadow.vert.qsb
Normal file
Binary file not shown.
4
src/nodes/shaders/vulkan2qsb.sh
Executable file
4
src/nodes/shaders/vulkan2qsb.sh
Executable file
@ -0,0 +1,4 @@
|
||||
#! /bin/sh
|
||||
|
||||
qsb --glsl 100es,120,150 --hlsl 50 --msl 12 -b -o boxshadow.vert.qsb boxshadow-vulkan.vert
|
||||
qsb --glsl 100es,120,150 --hlsl 50 --msl 12 -b -o boxshadow.frag.qsb boxshadow-vulkan.frag
|
@ -103,6 +103,7 @@ HEADERS += \
|
||||
nodes/QskBoxClipNode.h \
|
||||
nodes/QskBoxRenderer.h \
|
||||
nodes/QskBoxRendererColorMap.h \
|
||||
nodes/QskBoxShadowNode.h \
|
||||
nodes/QskGraphicNode.h \
|
||||
nodes/QskPaintedNode.h \
|
||||
nodes/QskPlainTextRenderer.h \
|
||||
@ -123,6 +124,7 @@ SOURCES += \
|
||||
nodes/QskBoxRendererRect.cpp \
|
||||
nodes/QskBoxRendererEllipse.cpp \
|
||||
nodes/QskBoxRendererDEllipse.cpp \
|
||||
nodes/QskBoxShadowNode.cpp \
|
||||
nodes/QskGraphicNode.cpp \
|
||||
nodes/QskPaintedNode.cpp \
|
||||
nodes/QskPlainTextRenderer.cpp \
|
||||
@ -135,6 +137,9 @@ SOURCES += \
|
||||
nodes/QskTickmarksNode.cpp \
|
||||
nodes/QskVertex.cpp
|
||||
|
||||
RESOURCES += \
|
||||
nodes/shaders.qrc
|
||||
|
||||
HEADERS += \
|
||||
controls/QskAbstractButton.h \
|
||||
controls/QskAnimationHint.h \
|
||||
@ -364,6 +369,7 @@ SOURCES += \
|
||||
inputpanel/QskInputPredictionBar.cpp \
|
||||
inputpanel/QskVirtualKeyboard.cpp
|
||||
|
||||
|
||||
pinyin {
|
||||
|
||||
unix {
|
||||
|
Loading…
x
Reference in New Issue
Block a user