diff --git a/examples/iotdashboard/Box.cpp b/examples/iotdashboard/Box.cpp index b77d7240..7371a343 100644 --- a/examples/iotdashboard/Box.cpp +++ b/examples/iotdashboard/Box.cpp @@ -8,14 +8,9 @@ #include -QSK_SUBCONTROL( Box, Panel ) - Box::Box( const QString& title, QQuickItem* parent ) - : QskLinearBox( Qt::Vertical, parent ) + : ShadowedBox( Qt::Vertical, parent ) { - setPanel( true ); - setSubcontrolProxy( QskBox::Panel, Box::Panel ); - if ( !title.isEmpty() ) { auto label = new QskTextLabel( title, this ); diff --git a/examples/iotdashboard/Box.h b/examples/iotdashboard/Box.h index 42aa5ce1..883917f8 100644 --- a/examples/iotdashboard/Box.h +++ b/examples/iotdashboard/Box.h @@ -5,16 +5,12 @@ #pragma once -#include +#include "ShadowedBox.h" -class QskTextLabel; - -class Box : public QskLinearBox +class Box : public ShadowedBox { Q_OBJECT public: - QSK_SUBCONTROLS( Panel ) - Box( const QString& title, QQuickItem* parent = nullptr ); }; diff --git a/examples/iotdashboard/BoxWithButtons.cpp b/examples/iotdashboard/BoxWithButtons.cpp index 0ea0e9c2..8b5fc5b7 100644 --- a/examples/iotdashboard/BoxWithButtons.cpp +++ b/examples/iotdashboard/BoxWithButtons.cpp @@ -36,7 +36,6 @@ BoxWithButtons::BoxWithButtons( const QString& title, const QString& value, bool isBright, QQuickItem* parent ) : Box( QString(), parent ) { - setPanel( true ); setSubcontrolProxy( QskBox::Panel, Panel ); setSizePolicy( Qt::Vertical, QskSizePolicy::Maximum ); diff --git a/examples/iotdashboard/ShadowedBox.cpp b/examples/iotdashboard/ShadowedBox.cpp new file mode 100644 index 00000000..1422d087 --- /dev/null +++ b/examples/iotdashboard/ShadowedBox.cpp @@ -0,0 +1,179 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "ShadowedBox.h" +#include "nodes/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 ); + + const auto r = box->subControlRect( ShadowedBox::Panel ); + if ( r.isEmpty() ) + return nullptr; + + switch ( nodeRole ) + { + case ShadowRole: + { + auto shadowNode = static_cast< BoxShadowNode* >( node ); + if ( shadowNode == nullptr ) + shadowNode = new BoxShadowNode(); + + const auto& shadowMetrics = box->shadow(); + + shadowNode->setRect( shadowMetrics.shadowRect( r ) ); + shadowNode->setShape( box->shape() ); + shadowNode->setBlurRadius( shadowMetrics.blurRadius() ); + shadowNode->setColor( box->shadowColor() ); + shadowNode->setClipRect( r ); + + shadowNode->updateGeometry(); + + return shadowNode; + } + case PanelRole: + { + auto boxNode = static_cast< QskBoxNode* >( node ); + if ( boxNode == nullptr ) + boxNode = new QskBoxNode(); + + const auto r = box->subControlRect( ShadowedBox::Panel ); + + boxNode->setBoxData( r, box->shape(), box->borderWidth(), + box->borderColor(), box->gradient() ); + + return boxNode; + } + } + + return nullptr; + } + }; +} + +QSK_SUBCONTROL( ShadowedBox, Panel ) + +ShadowedBox::ShadowedBox(Qt::Orientation orientation, QQuickItem* parentItem ) + : QskLinearBox( orientation, parentItem ) +{ + setFlag( QQuickItem::ItemHasContents, true ); + setSkinlet( new Skinlet() ); + + // ### move to Skin: + setGradient( Qt::white ); + setShadow( { 0, 10 } ); + setShadowColor( 0xe5e5e5 ); + setShape( 6 ); +} + +ShadowedBox::~ShadowedBox() +{ +} + +void ShadowedBox::setShadow( const QskShadowMetrics& shadow ) +{ + m_shadow = shadow; + update(); +} + +const QskShadowMetrics& ShadowedBox::shadow() const +{ + return m_shadow; +} + +void ShadowedBox::setShadowColor( const QColor& color ) +{ + m_shadowColor = color; + update(); +} + +QColor ShadowedBox::shadowColor() const +{ + return m_shadowColor; +} + +QRectF ShadowedBox::layoutRectForSize( const QSizeF &size ) const +{ + auto padding = paddingHint( Panel ); + return { padding.left() / 2, padding.top() / 2, + size.width() - padding.right(), size.height() - padding.bottom() }; +} + +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; +} + +void ShadowedBox::setBorderWidth( qreal width ) +{ + m_borderWidth = qMax( width, 0.0 ); + update(); +} + +qreal ShadowedBox::borderWidth() const +{ + return m_borderWidth; +} + +void ShadowedBox::setBorderColor( const QColor& color ) +{ + m_borderColor = color; + update(); +} + +QColor ShadowedBox::borderColor() const +{ + return m_borderColor; +} + +#include "moc_ShadowedBox.cpp" diff --git a/examples/iotdashboard/ShadowedBox.h b/examples/iotdashboard/ShadowedBox.h new file mode 100644 index 00000000..e1e243dd --- /dev/null +++ b/examples/iotdashboard/ShadowedBox.h @@ -0,0 +1,53 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#pragma once + +#include +#include +#include + +class QskGradient; + +class ShadowedBox : public QskLinearBox +{ + Q_OBJECT + + public: + QSK_SUBCONTROLS( Panel ) + + ShadowedBox( Qt::Orientation orientation, QQuickItem* parent = nullptr ); + ~ShadowedBox() override; + + void setShadow( const QskShadowMetrics& ); + const QskShadowMetrics& shadow() const; + + void setGradient( const QskGradient& ); + const QskGradient& gradient() const; + + void setShadowColor( const QColor& ); + QColor shadowColor() const; + + QRectF layoutRectForSize( const QSizeF& size ) const override; + + void setShape( const QskBoxShapeMetrics& ); + const QskBoxShapeMetrics& shape() const; + + void setBorderWidth( qreal width ); + qreal borderWidth() const; + + void setBorderColor( const QColor& ); + QColor borderColor() const; + + private: + QskShadowMetrics m_shadow; + QColor m_shadowColor = Qt::black; + + QskGradient m_gradient; + QskBoxShapeMetrics m_shape; + + qreal m_borderWidth = 0.0; + QColor m_borderColor = Qt::black; +}; diff --git a/examples/iotdashboard/Skin.cpp b/examples/iotdashboard/Skin.cpp index 9f531a40..049a2732 100644 --- a/examples/iotdashboard/Skin.cpp +++ b/examples/iotdashboard/Skin.cpp @@ -16,8 +16,9 @@ #include "MenuBar.h" #include "PieChartPainted.h" #include "RoundedIcon.h" -#include "TopBar.h" #include "RoundButton.h" +#include "ShadowedBox.h" +#include "TopBar.h" #include "UsageBox.h" #include "UsageDiagram.h" @@ -125,10 +126,8 @@ void Skin::initHints( const Palette& palette ) ed.setFontRole( TimeLabel::Text, QskSkin::HugeFont ); ed.setColor( TimeLabel::Text, "#6776FF" ); - // boxes (including shadow): - ed.setPadding( Box::Panel, 15 ); - + ed.setPadding( ShadowedBox::Panel, 15 ); // content in boxes (indoor temperature, humidity etc.): ed.setFontRole( UsageBox::Separator, QskSkin::SmallFont ); diff --git a/examples/iotdashboard/iotdashboard.pro b/examples/iotdashboard/iotdashboard.pro index 4d713911..2064a376 100644 --- a/examples/iotdashboard/iotdashboard.pro +++ b/examples/iotdashboard/iotdashboard.pro @@ -18,6 +18,7 @@ SOURCES += \ PieChartPainted.cpp \ PieChartSkinlet.cpp \ RoundedIcon.cpp \ + ShadowedBox.cpp \ Skin.cpp \ TopBar.cpp \ RoundButton.cpp \ @@ -28,7 +29,8 @@ SOURCES += \ SOURCES += \ nodes/DiagramDataNode.cpp \ - nodes/DiagramSegmentsNode.cpp + nodes/DiagramSegmentsNode.cpp \ + nodes/BoxShadowNode.cpp HEADERS += \ Box.h \ @@ -47,6 +49,7 @@ HEADERS += \ PieChartPainted.h \ PieChartSkinlet.h \ RoundedIcon.h \ + ShadowedBox.h \ Skin.h \ TopBar.h \ RoundButton.h \ @@ -55,21 +58,8 @@ HEADERS += \ HEADERS += \ nodes/DiagramDataNode.h \ - nodes/DiagramSegmentsNode.h - -HEADERS += \ - kirigami/shadowedrectangle.h \ - kirigami/scenegraph/paintedrectangleitem.h \ - kirigami/scenegraph/shadowedborderrectanglematerial.h \ - kirigami/scenegraph/shadowedrectanglematerial.h \ - kirigami/scenegraph/shadowedrectanglenode.h - -SOURCES += \ - kirigami/shadowedrectangle.cpp \ - kirigami/scenegraph/paintedrectangleitem.cpp \ - kirigami/scenegraph/shadowedborderrectanglematerial.cpp \ - kirigami/scenegraph/shadowedrectanglematerial.cpp \ - kirigami/scenegraph/shadowedrectanglenode.cpp + nodes/DiagramSegmentsNode.h \ + nodes/BoxShadowNode.h RESOURCES += \ images.qrc \ diff --git a/examples/iotdashboard/nodes/BoxShadowNode.cpp b/examples/iotdashboard/nodes/BoxShadowNode.cpp new file mode 100644 index 00000000..b7a0433f --- /dev/null +++ b/examples/iotdashboard/nodes/BoxShadowNode.cpp @@ -0,0 +1,257 @@ +/****************************************************************************** + * 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 +#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_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 + || state.isCachedMaterialDataDirty( )) + { + 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 ); +} diff --git a/examples/iotdashboard/nodes/BoxShadowNode.h b/examples/iotdashboard/nodes/BoxShadowNode.h new file mode 100644 index 00000000..bd84a5eb --- /dev/null +++ b/examples/iotdashboard/nodes/BoxShadowNode.h @@ -0,0 +1,32 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#pragma once + +#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 setBlurRadius( qreal ); + + void setClipRect( const QRectF& ); + + void updateGeometry(); + + private: + Q_DECLARE_PRIVATE( BoxShadowNode ) +}; diff --git a/examples/iotdashboard/shaders.qrc b/examples/iotdashboard/shaders.qrc new file mode 100644 index 00000000..fa597692 --- /dev/null +++ b/examples/iotdashboard/shaders.qrc @@ -0,0 +1,7 @@ + + + + shaders/boxshadow.vert + shaders/boxshadow.frag + + diff --git a/examples/iotdashboard/shaders/boxshadow.frag b/examples/iotdashboard/shaders/boxshadow.frag new file mode 100644 index 00000000..b24ff185 --- /dev/null +++ b/examples/iotdashboard/shaders/boxshadow.frag @@ -0,0 +1,42 @@ +uniform lowp float opacity; +uniform lowp float blurExtent; +uniform lowp vec4 radius; +uniform lowp vec4 color; +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; +} + +void main() +{ + lowp vec4 col = vec4(0.0); + + if ( opacity > 0.0 ) + { + const lowp float minRadius = 0.05; + + lowp float e2 = 0.5 * blurExtent; + lowp float r = 2.0 * effectiveRadius( radius, coord ); + + lowp float f = minRadius / max( r, minRadius ); + + r += e2 * f; + + lowp vec2 d = r + blurExtent - aspect * ( 1.0 - abs( 2.0 * coord ) ); + lowp float l = min( max(d.x, d.y), 0.0) + length( max(d, 0.0) ); + + lowp float shadow = l - r; + + lowp float v = smoothstep( -e2, e2, shadow ); + col = mix( color, vec4(0.0), v ) * opacity; + } + + gl_FragColor = col; +} diff --git a/examples/iotdashboard/shaders/boxshadow.vert b/examples/iotdashboard/shaders/boxshadow.vert new file mode 100644 index 00000000..5a55b751 --- /dev/null +++ b/examples/iotdashboard/shaders/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 = in_coord; + gl_Position = matrix * in_vertex; +}