diff --git a/playground/playground.pro b/playground/playground.pro index 3fc1d24b..e1fef989 100644 --- a/playground/playground.pro +++ b/playground/playground.pro @@ -5,6 +5,7 @@ SUBDIRS += \ dialogbuttons \ invoker \ inputpanel \ + shadows \ images qtHaveModule(webengine) { diff --git a/playground/shadows/BoxShadowNode.cpp b/playground/shadows/BoxShadowNode.cpp new file mode 100644 index 00000000..1e86fac7 --- /dev/null +++ b/playground/shadows/BoxShadowNode.cpp @@ -0,0 +1,275 @@ +#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 + || state.isCachedMaterialDataDirty( )) + { + 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 ); +} diff --git a/playground/shadows/BoxShadowNode.h b/playground/shadows/BoxShadowNode.h new file mode 100644 index 00000000..a6584d70 --- /dev/null +++ b/playground/shadows/BoxShadowNode.h @@ -0,0 +1,28 @@ +#ifndef BOX_SHADOW_NODE_H +#define BOX_SHADOW_NODE_H + +#include + +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 setShadow( qreal extent, qreal dx, qreal dy ); + + void updateGeometry(); + +private: + Q_DECLARE_PRIVATE( BoxShadowNode ) +}; + +#endif diff --git a/playground/shadows/ShadowedBox.cpp b/playground/shadows/ShadowedBox.cpp new file mode 100644 index 00000000..07dce406 --- /dev/null +++ b/playground/shadows/ShadowedBox.cpp @@ -0,0 +1,138 @@ +#include "ShadowedBox.h" +#include "BoxShadowNode.h" + +#include +#include +#include +#include +#include + +namespace +{ + class Skinlet : public QskSkinlet + { + public: + enum NodeRole { ShadowRole, PanelRole }; + + Skinlet() + { + setNodeRoles( { ShadowRole, PanelRole } ); + } + + QRectF subControlRect( const QskSkinnable*, + const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const override + { + if ( subControl == ShadowedBox::Panel ) + { + return contentsRect; + } + + return QRectF(); + } + + QSGNode* updateSubNode( const QskSkinnable* skinnable, + quint8 nodeRole, QSGNode* node ) const override + { + const auto box = static_cast< const ShadowedBox* >( skinnable ); + + switch ( nodeRole ) + { + case ShadowRole: + { + auto shadowNode = static_cast< BoxShadowNode* >( node ); + if ( shadowNode == nullptr ) + shadowNode = new BoxShadowNode(); + + const auto& s = box->shadow(); + const qreal dx = s.extent + s.xOffset; + const qreal dy = s.extent + s.yOffset; + + auto r = box->subControlRect( ShadowedBox::Panel ); + r.adjust( -dx, -dy, dx, dy ); + + shadowNode->setRect( r ); + shadowNode->setShape( box->shape() ); + shadowNode->setShadow( s.extent, s.xOffset, s.yOffset ); + shadowNode->setColor( box->shadowColor() ); + shadowNode->updateGeometry(); + + return shadowNode; + } + case PanelRole: + { + auto boxNode = static_cast< QskBoxNode* >( node ); + if ( boxNode == nullptr ) + boxNode = new QskBoxNode(); + + const QRectF r = box->subControlRect( ShadowedBox::Panel ); + + boxNode->setBoxData( r, box->shape(), QskBoxBorderMetrics(), + QskBoxBorderColors(), box->gradient() ); + + return boxNode; + } + } + + return nullptr; + } + }; +} + +QSK_SUBCONTROL( ShadowedBox, Panel ) + +ShadowedBox::ShadowedBox(QQuickItem *parentItem) + : QskControl( parentItem ) +{ + setFlag( QQuickItem::ItemHasContents, true ); + setSkinlet( new Skinlet() ); +} + +ShadowedBox::~ShadowedBox() +{ +} + +void ShadowedBox::setShadow( const Shadow& shadow ) +{ + m_shadow = shadow; + update(); +} + +const ShadowedBox::Shadow& ShadowedBox::shadow() const +{ + return m_shadow; +} + +void ShadowedBox::setShadowColor( const QColor& color ) +{ + m_shadowColor = color; + update(); +} + +QColor ShadowedBox::shadowColor() const +{ + return m_shadowColor; +} + +void ShadowedBox::setGradient( const QskGradient& gradient ) +{ + m_gradient = gradient; + update(); +} + +const QskGradient& ShadowedBox::gradient() const +{ + return m_gradient; +} + +void ShadowedBox::setShape( const QskBoxShapeMetrics& shape ) +{ + m_shape = shape; + update(); +} + +const QskBoxShapeMetrics& ShadowedBox::shape() const +{ + return m_shape; +} + +#include "moc_ShadowedBox.cpp" diff --git a/playground/shadows/ShadowedBox.h b/playground/shadows/ShadowedBox.h new file mode 100644 index 00000000..975732b5 --- /dev/null +++ b/playground/shadows/ShadowedBox.h @@ -0,0 +1,46 @@ +#ifndef SHADOWED_BOX_H +#define SHADOWED_BOX_H + +#include +#include + +class QskGradient; + +class ShadowedBox : public QskControl +{ + Q_OBJECT + +public: + QSK_SUBCONTROLS( Panel ) + + class Shadow + { + public: + qreal extent = 0.0; + qreal xOffset = 0.0; + qreal yOffset = 0.0; + }; + + ShadowedBox(QQuickItem *parent = nullptr); + ~ShadowedBox() override; + + void setShadow( const Shadow& ); + const Shadow& shadow() const; + + void setGradient( const QskGradient& ); + const QskGradient& gradient() const; + + void setShadowColor( const QColor& ); + QColor shadowColor() const; + + void setShape( const QskBoxShapeMetrics& ); + const QskBoxShapeMetrics& shape() const; + +private: + Shadow m_shadow; + QColor m_shadowColor = Qt::black; + QskGradient m_gradient; + QskBoxShapeMetrics m_shape; +}; + +#endif diff --git a/playground/shadows/boxshadow.frag b/playground/shadows/boxshadow.frag new file mode 100644 index 00000000..5224bed0 --- /dev/null +++ b/playground/shadows/boxshadow.frag @@ -0,0 +1,72 @@ +/* + Heavily inspired by code from the kirigami project: + Copyright 2020 Arjen Hiemstra + License: LGPL-2.0-or-later + + https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm + Copyright: 2017 Inigo Quilez + License: MIT + */ + +uniform lowp float opacity; +uniform lowp float extent; +uniform lowp vec4 radius; +uniform lowp vec4 color; +uniform lowp vec2 offset; +uniform lowp vec2 aspect; + +varying lowp vec2 coord; + +lowp float effectiveRadius( in lowp vec4 radii, in lowp 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; +} + +bool isInside( in lowp vec2 point, in lowp vec2 size, in lowp vec4 radii ) +{ + lowp float r = effectiveRadius( radii, point ); + lowp vec2 d = abs(point) - size + r; + + lowp float l = min( max(d.x, d.y), 0.0) + length( max(d, 0.0) ) - r; + return l <= 0.0; +} + +lowp float shadowAt( + in lowp vec2 point, in lowp vec2 size, in lowp vec4 radii ) +{ + lowp float r = effectiveRadius( radii, point ); + lowp vec2 d = abs(point) - size + r; + + return min( max(d.x, d.y), 0.0) + length( max(d, 0.0) ) - r; +} + +void main() +{ + lowp vec4 col = vec4(0.0); + + if ( extent > 0.0 && opacity > 0.0 ) + { + lowp float t = 1.0 + 2.0 * length( offset ) + extent; + + if ( !isInside( coord, aspect / t, radius / t) ) + { + const lowp float minRadius = 0.05; + lowp float e2 = 0.5 * extent; + + lowp vec4 f = minRadius / max( radius, minRadius ); + lowp vec4 r = radius + e2 * f; + + lowp float shadow = shadowAt( + coord - 2.0 * offset / t, aspect / t, r / t); + + lowp float v = smoothstep( -e2, e2, shadow ); + + col = mix( color, vec4(0.0), v ) * opacity; + } + } + + gl_FragColor = col; +} diff --git a/playground/shadows/boxshadow.vert b/playground/shadows/boxshadow.vert new file mode 100644 index 00000000..96d89780 --- /dev/null +++ b/playground/shadows/boxshadow.vert @@ -0,0 +1,13 @@ +uniform highp mat4 matrix; +uniform lowp vec2 aspect; + +attribute highp vec4 in_vertex; +attribute mediump vec2 in_coord; + +varying mediump vec2 coord; + +void main() +{ + coord = ( -1.0 + 2.0 * in_coord ) * aspect; + gl_Position = matrix * in_vertex; +} diff --git a/playground/shadows/main.cpp b/playground/shadows/main.cpp new file mode 100644 index 00000000..0e9b9821 --- /dev/null +++ b/playground/shadows/main.cpp @@ -0,0 +1,59 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "ShadowedBox.h" + +#include +#include +#include +#include +#include +#include + +class Box : public ShadowedBox +{ + public: + Box( QQuickItem* parent = nullptr ) + : ShadowedBox( parent ) + { + Shadow shadow; + shadow.extent = 10; + shadow.xOffset = 20.0; + shadow.yOffset = 20.0; + + setShadow( shadow ); + setShadowColor( Qt::black ); + setGradient( Qt::darkRed ); + setShape( QskBoxShapeMetrics( 5, 10, 15, 20 ) ); + } +}; + +int main( int argc, char* argv[] ) +{ +#ifdef ITEM_STATISTICS + QskObjectCounter counter( true ); +#endif + + QGuiApplication app( argc, argv ); + + auto layout = new QskLinearBox(); + layout->setPanel( true ); +#if 1 + layout->setGradientHint( QskBox::Panel, + QskGradient( Qt::Vertical, QskRgb::WhiteSmoke, QskRgb::MistyRose ) ); +#else + layout->setGradientHint( QskBox::Panel, Qt::white ); +#endif + layout->setPadding( 60 ); + (void ) new Box( layout ); + + QskWindow window; + window.setColor( QskRgb::PapayaWhip ); + window.addItem( layout ); + window.resize( 600, 600 ); + window.show(); + + return app.exec(); +} diff --git a/playground/shadows/shaders.qrc b/playground/shadows/shaders.qrc new file mode 100644 index 00000000..05ff312f --- /dev/null +++ b/playground/shadows/shaders.qrc @@ -0,0 +1,7 @@ + + + + boxshadow.vert + boxshadow.frag + + diff --git a/playground/shadows/shadows.pro b/playground/shadows/shadows.pro new file mode 100644 index 00000000..0f7d9ea4 --- /dev/null +++ b/playground/shadows/shadows.pro @@ -0,0 +1,14 @@ +CONFIG += qskexample +QT += quick_private + +RESOURCES += \ + shaders.qrc + +HEADERS += \ + ShadowedBox.h \ + BoxShadowNode.h + +SOURCES += \ + ShadowedBox.cpp \ + BoxShadowNode.cpp \ + main.cpp