diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -24,10 +24,15 @@ scenepositionattached.cpp mnemonicattached.cpp wheelhandler.cpp + shadowedrectangle.cpp + scenegraph/shadowedrectanglenode.cpp + scenegraph/shadowedrectanglematerial.cpp ${kirigami_QM_LOADER} ${KIRIGAMI_STATIC_FILES} ) +qt5_add_resources(SHADERS scenegraph/shaders.qrc) + add_subdirectory(libkirigami) if(STATIC_LIBRARY) @@ -55,7 +60,7 @@ endif(STATIC_LIBRARY) -add_library(kirigamiplugin ${kirigami_SRCS} ${RESOURCES}) +add_library(kirigamiplugin ${kirigami_SRCS} ${RESOURCES} ${SHADERS}) if(STATIC_LIBRARY) SET_TARGET_PROPERTIES(kirigamiplugin PROPERTIES diff --git a/src/kirigamiplugin.cpp b/src/kirigamiplugin.cpp --- a/src/kirigamiplugin.cpp +++ b/src/kirigamiplugin.cpp @@ -17,6 +17,7 @@ #include "pagepool.h" #include "scenepositionattached.h" #include "wheelhandler.h" +#include "shadowedrectangle.h" #include #include @@ -235,6 +236,9 @@ //TODO: remove qmlRegisterType(componentUrl(QStringLiteral("SwipeListItem2.qml")), uri, 2, 11, "SwipeListItem2"); + // 2.12 + qmlRegisterType(uri, 2, 12, "ShadowedRectangle"); + qmlProtectModule(uri, 2); } diff --git a/src/scenegraph/header_desktop.glsl b/src/scenegraph/header_desktop.glsl new file mode 100644 --- /dev/null +++ b/src/scenegraph/header_desktop.glsl @@ -0,0 +1,39 @@ +/* + * Copyright 2020 Arjen Hiemstra + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +// This file contains common directives needed for the shaders to work. +// It is included as the very first bit in the shader. +// Important: If a specific GLSL version is needed, it should be set in this +// file. + +// This file is intended for desktop OpenGL version 2.1 or greater. + +#version 120 + +#ifndef lowp + #define lowp +#endif + +#ifndef mediump + #define mediump +#endif + +#ifndef highp + #define highp mediump +#endif diff --git a/src/scenegraph/header_desktop_core.glsl b/src/scenegraph/header_desktop_core.glsl new file mode 100644 --- /dev/null +++ b/src/scenegraph/header_desktop_core.glsl @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: 2020 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +// This file contains common directives needed for the shaders to work. +// It is included as the very first bit in the shader. +// Important: If a specific GLSL version is needed, it should be set in this +// file. + +// This file is intended for desktop OpenGL version 4.5 or greater. + +#version 450 diff --git a/src/scenegraph/header_es.glsl b/src/scenegraph/header_es.glsl new file mode 100644 --- /dev/null +++ b/src/scenegraph/header_es.glsl @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2020 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +// This file contains common directives needed for the shaders to work. +// It is included as the very first bit in the shader. +// Important: If a specific GLSL version is needed, it should be set in this +// file. + +// This file is intended for OpenGLES version 2.0 or greater. + +#version 100 +#extension GL_OES_standard_derivatives : enable + diff --git a/src/scenegraph/shaders.qrc b/src/scenegraph/shaders.qrc new file mode 100644 --- /dev/null +++ b/src/scenegraph/shaders.qrc @@ -0,0 +1,13 @@ + + + + header_es.glsl + header_desktop.glsl + header_desktop_core.glsl + shadowedrectangle.vert + shadowedrectangle_core.vert + shadowedrectangle.frag + shadowedrectangle_core.frag + + + diff --git a/src/scenegraph/shadowedrectangle.frag b/src/scenegraph/shadowedrectangle.frag new file mode 100644 --- /dev/null +++ b/src/scenegraph/shadowedrectangle.frag @@ -0,0 +1,67 @@ +/* + * SPDX-FileCopyrightText: 2020 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +// This is based on the 2D SDF functions provided by Inigo Quilez: +// https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm + +uniform lowp float opacity; +uniform lowp float size; +uniform lowp float radius; +uniform lowp vec4 color; +uniform lowp vec4 shadowColor; +uniform lowp vec2 offset; +uniform lowp vec2 aspect; + +varying lowp vec2 uv; + +const lowp float minimum_shadow_radius = 0.05; + +// Calculate the distance to a rectangle with rounded corners. +// \param point The point to calculate the distance of. +// \param rect The rectangle to calculate the distance of. +// \param translation The amount of translation to apply to the rectangle. +// \param radius A vec4 with the radius of each corner. +lowp float sdf_rounded_rectangle(in lowp vec2 point, in lowp vec2 rect, in lowp vec2 translation, in lowp vec4 radius) +{ + radius.xy = (point.x > 0.0) ? radius.xy : radius.zw; + radius.x = (point.y > 0.0) ? radius.x : radius.y; + lowp vec2 d = abs(point - translation) - rect + radius.x; + return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - radius.x; +} + +void main() +{ + // Scaling factor that is the inverse of the amount of scaling applied to the geometry. + lowp float inverse_scale = 1.0 / (1.0 + size + length(offset) * 2.0); + + // Correction factor to round the corners of a larger shadow. + // We want to account for size in regards to shadow radius, so that a larger shadow is + // more rounded, but only if we are not already rounding the corners due to corner radius. + lowp float size_factor = 0.5 * (minimum_shadow_radius / max(radius, minimum_shadow_radius)); + + lowp float shadowRadius = radius + size * size_factor; + + lowp vec4 col = vec4(0.0); + + // Calculate the shadow's distance field. + lowp float shadow = sdf_rounded_rectangle(uv, aspect * inverse_scale, offset * inverse_scale, vec4(shadowRadius * inverse_scale)); + // Render it, interpolating the color over the distance. + col = mix(col, shadowColor * sign(size), shadowColor.a * (1.0 - smoothstep(-size * 0.5, size * 0.5, shadow))); + + // Calculate the main rectangle distance field. + lowp float rect = sdf_rounded_rectangle(uv, aspect * inverse_scale, vec2(0.0), vec4(radius * inverse_scale)); + + lowp float g = fwidth(rect); + + // First, remove anything that was rendered by the shadow if it is inside the rectangle. + // This allows us to use colors with alpha without rendering artifacts. + col = mix(col, vec4(0.0), 1.0 - smoothstep(0.001 - g, 0.001 + g, rect)); + + // Then, render it again but this time with the proper color and properly alpha blended. + col = mix(col, color, color.a * (1.0 - smoothstep(0.001 - g, 0.001 + g, rect))); + + gl_FragColor = col * opacity; +} diff --git a/src/scenegraph/shadowedrectangle.vert b/src/scenegraph/shadowedrectangle.vert new file mode 100644 --- /dev/null +++ b/src/scenegraph/shadowedrectangle.vert @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2020 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +uniform highp mat4 matrix; +uniform lowp vec2 aspect; +uniform lowp vec2 offset; + +attribute highp vec4 in_vertex; +attribute mediump vec2 in_uv; + +varying mediump vec2 uv; + +void main() { + uv = (-1.0 + 2.0 * in_uv) * aspect; + gl_Position = matrix * in_vertex; +} diff --git a/src/scenegraph/shadowedrectangle_core.frag b/src/scenegraph/shadowedrectangle_core.frag new file mode 100644 --- /dev/null +++ b/src/scenegraph/shadowedrectangle_core.frag @@ -0,0 +1,67 @@ +/* + * SPDX-FileCopyrightText: 2020 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +// This is based on the 2D SDF functions provided by Inigo Quilez: +// https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm + +uniform lowp float opacity; +uniform lowp float size; +uniform lowp float radius; +uniform lowp vec4 color; +uniform lowp vec4 shadowColor; +uniform lowp vec2 offset; +uniform lowp vec2 aspect; + +in lowp vec2 uv; + +const lowp float minimum_shadow_radius = 0.05; + +// Calculate the distance to a rectangle with rounded corners. +// \param point The point to calculate the distance of. +// \param rect The rectangle to calculate the distance of. +// \param translation The amount of translation to apply to the rectangle. +// \param radius A vec4 with the radius of each corner. +lowp float sdf_rounded_rectangle(in lowp vec2 point, in lowp vec2 rect, in lowp vec2 translation, in lowp vec4 radius) +{ + radius.xy = (point.x > 0.0) ? radius.xy : radius.zw; + radius.x = (point.y > 0.0) ? radius.x : radius.y; + lowp vec2 d = abs(point - translation) - rect + radius.x; + return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - radius.x; +} + +void main() +{ + // Scaling factor that is the inverse of the amount of scaling applied to the geometry. + lowp float inverse_scale = 1.0 / (1.0 + size + length(offset * 2.0)); + + // Correction factor to round the corners of a larger shadow. + // We want to account for size in regards to shadow radius, so that a larger shadow is + // more rounded, but only if we are not already rounding the corners due to corner radius. + lowp float size_factor = 0.5 * (minimum_shadow_radius / max(radius, minimum_shadow_radius)); + + lowp float shadowRadius = radius + size * size_factor; + + lowp vec4 col = vec4(0.0); + + // Calculate the shadow's distance field. + lowp float shadow = sdf_rounded_rectangle(uv, aspect * inverse_scale, offset * inverse_scale, vec4(shadowRadius * inverse_scale)); + // Render it, interpolating the color over the distance. + col = mix(col, shadowColor * sign(size), shadowColor.a * (1.0 - smoothstep(-size * 0.5, size * 0.5, shadow))); + + // Calculate the main rectangle distance field. + lowp float rect = sdf_rounded_rectangle(uv, aspect * inverse_scale, vec2(0.0), vec4(radius * inverse_scale)); + + lowp float g = fwidth(rect); + + // First, remove anything that was rendered by the shadow if it is inside the rectangle. + // This allows us to use colors with alpha without rendering artifacts. + col = mix(col, vec4(0.0), 1.0 - smoothstep(0.001 - g, 0.001 + g, rect)); + + // Then, render it again but this time with the proper color and properly alpha blended. + col = mix(col, color, color.a * (1.0 - smoothstep(0.001 - g, 0.001 + g, rect))); + + gl_FragColor = col * opacity; +} diff --git a/src/scenegraph/shadowedrectangle_core.vert b/src/scenegraph/shadowedrectangle_core.vert new file mode 100644 --- /dev/null +++ b/src/scenegraph/shadowedrectangle_core.vert @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2020 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +uniform mat4 matrix; +uniform vec2 aspect; +uniform vec2 offset; + +in vec4 in_vertex; +in vec2 in_uv; + +out vec2 uv; + +void main() { + uv = (-1.0 + 2.0 * in_uv) * aspect; + gl_Position = matrix * in_vertex; +} diff --git a/src/scenegraph/shadowedrectanglematerial.h b/src/scenegraph/shadowedrectanglematerial.h new file mode 100644 --- /dev/null +++ b/src/scenegraph/shadowedrectanglematerial.h @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: 2020 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#pragma once + +#include +#include + +class ShadowedRectangleMaterial : public QSGMaterial +{ +public: + ShadowedRectangleMaterial(); + + QSGMaterialShader* createShader() const override; + QSGMaterialType* type() const override; + int compare(const QSGMaterial* other) const override; + + QVector2D aspect = QVector2D{1.0, 1.0}; + float size = 0.0; + float radius = 0.0; + QColor color = Qt::white; + QColor shadowColor = Qt::black; + QVector2D offset; +}; + +class ElevatedRectangleShader : public QSGMaterialShader +{ +public: + ElevatedRectangleShader(); + + 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_sizeLocation = -1; + int m_radiusLocation = -1; + int m_colorLocation = -1; + int m_shadowColorLocation = -1; + int m_offsetLocation = -1; +}; diff --git a/src/scenegraph/shadowedrectanglematerial.cpp b/src/scenegraph/shadowedrectanglematerial.cpp new file mode 100644 --- /dev/null +++ b/src/scenegraph/shadowedrectanglematerial.cpp @@ -0,0 +1,100 @@ +/* + * SPDX-FileCopyrightText: 2020 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "shadowedrectanglematerial.h" + +#include + +ShadowedRectangleMaterial::ShadowedRectangleMaterial() +{ + setFlag(QSGMaterial::Blending, true); +} + +QSGMaterialShader* ShadowedRectangleMaterial::createShader() const +{ + return new ElevatedRectangleShader{}; +} + +QSGMaterialType* ShadowedRectangleMaterial::type() const +{ + static QSGMaterialType type; + return &type; +} + +int ShadowedRectangleMaterial::compare(const QSGMaterial *other) const +{ + auto material = static_cast(other); + + if (material->color == color + && material->shadowColor == shadowColor + && material->offset == offset + && material->aspect == aspect + && qFuzzyCompare(material->size, size) + && qFuzzyCompare(material->radius, radius)) { + return 0; + } + + return QSGMaterial::compare(other); +} + +ElevatedRectangleShader::ElevatedRectangleShader() +{ + auto header = QOpenGLContext::currentContext()->isOpenGLES() ? QStringLiteral("header_es.glsl") : QStringLiteral("header_desktop.glsl"); + + auto shaderRoot = QStringLiteral(":/org/kde/kirigami/shaders/"); + + setShaderSourceFiles(QOpenGLShader::Vertex, { + shaderRoot + header, + shaderRoot + QStringLiteral("shadowedrectangle.vert") + }); + + setShaderSourceFiles(QOpenGLShader::Fragment, { + shaderRoot + header, + shaderRoot + QStringLiteral("shadowedrectangle.frag") + }); +} + +const char *const * ElevatedRectangleShader::attributeNames() const +{ + static char const *const names[] = {"in_vertex", "in_uv", nullptr}; + return names; +} + +void ElevatedRectangleShader::initialize() +{ + QSGMaterialShader::initialize(); + m_matrixLocation = program()->uniformLocation("matrix"); + m_aspectLocation = program()->uniformLocation("aspect"); + m_opacityLocation = program()->uniformLocation("opacity"); + m_sizeLocation = program()->uniformLocation("size"); + m_radiusLocation = program()->uniformLocation("radius"); + m_colorLocation = program()->uniformLocation("color"); + m_shadowColorLocation = program()->uniformLocation("shadowColor"); + m_offsetLocation = program()->uniformLocation("offset"); +} + +void ElevatedRectangleShader::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 || newMaterial->compare(oldMaterial) != 0 || state.isCachedMaterialDataDirty()) { + auto material = static_cast(newMaterial); + p->setUniformValue(m_aspectLocation, material->aspect); + p->setUniformValue(m_sizeLocation, material->size); + p->setUniformValue(m_radiusLocation, material->radius); + p->setUniformValue(m_colorLocation, material->color); + p->setUniformValue(m_shadowColorLocation, material->shadowColor); + p->setUniformValue(m_offsetLocation, material->offset); + } +} diff --git a/src/scenegraph/shadowedrectanglenode.h b/src/scenegraph/shadowedrectanglenode.h new file mode 100644 --- /dev/null +++ b/src/scenegraph/shadowedrectanglenode.h @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2020 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#pragma once + +#include +#include +#include + +class ShadowedRectangleMaterial; + +class ShadowedRectangleNode : public QSGGeometryNode +{ +public: + ShadowedRectangleNode(); + + void setRect(const QRectF &rect); + void setSize(qreal size); + void setRadius(qreal radius); + void setColor(const QColor &color); + void setShadowColor(const QColor &color); + void setOffset(const QVector2D &offset); + + void updateGeometry(); + +private: + QSGGeometry *m_geometry; + ShadowedRectangleMaterial *m_material; + + QRectF m_rect; + qreal m_size = 0.0; + qreal m_radius = 0.0; + QVector2D m_offset = QVector2D{0.0, 0.0}; + QVector2D m_aspect = QVector2D{1.0, 1.0}; +}; diff --git a/src/scenegraph/shadowedrectanglenode.cpp b/src/scenegraph/shadowedrectanglenode.cpp new file mode 100644 --- /dev/null +++ b/src/scenegraph/shadowedrectanglenode.cpp @@ -0,0 +1,119 @@ +/* + * SPDX-FileCopyrightText: 2020 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "shadowedrectanglenode.h" +#include "shadowedrectanglematerial.h" + +QColor premultiply(const QColor &color) +{ + return QColor::fromRgbF( + color.redF() * color.alphaF(), + color.greenF() * color.greenF(), + color.blueF() * color.blueF(), + color.alphaF() + ); +} + +ShadowedRectangleNode::ShadowedRectangleNode() +{ + m_geometry = new QSGGeometry{QSGGeometry::defaultAttributes_TexturedPoint2D(), 4}; + setGeometry(m_geometry); + + m_material = new ShadowedRectangleMaterial{}; + setMaterial(m_material); + + setFlags(QSGNode::OwnsGeometry | QSGNode::OwnsMaterial); +} + +void ShadowedRectangleNode::setRect(const QRectF& rect) +{ + if (rect == m_rect) { + return; + } + + m_rect = rect; + + QVector2D newAspect{1.0, 1.0}; + if (m_rect.width() >= m_rect.height()) { + newAspect.setX(m_rect.width() / m_rect.height()); + } else { + newAspect.setY(m_rect.height() / m_rect.width()); + } + + if (m_material->aspect != newAspect) { + m_material->aspect = newAspect; + markDirty(QSGNode::DirtyMaterial); + m_aspect = newAspect; + } +} + +void ShadowedRectangleNode::setSize(qreal size) +{ + auto minDimension = std::min(m_rect.width(), m_rect.height()); + float uniformSize = (size / minDimension) * 2.0; + + if (!qFuzzyCompare(m_material->size, uniformSize)) { + m_material->size = uniformSize; + markDirty(QSGNode::DirtyMaterial); + m_size = size; + } +} + +void ShadowedRectangleNode::setRadius(qreal radius) +{ + auto minDimension = std::min(m_rect.width(), m_rect.height()); + float uniformRadius = radius * 2.0 / minDimension; + + if (!qFuzzyCompare(m_material->radius, uniformRadius)) { + m_material->radius = std::min(uniformRadius, 1.0f); + markDirty(QSGNode::DirtyMaterial); + m_radius = radius; + } +} + +void ShadowedRectangleNode::setColor(const QColor &color) +{ + if (m_material->color != color) { + m_material->color = premultiply(color); + markDirty(QSGNode::DirtyMaterial); + } +} + +void ShadowedRectangleNode::setShadowColor(const QColor& color) +{ + if (m_material->shadowColor != color) { + m_material->shadowColor = premultiply(color); + markDirty(QSGNode::DirtyMaterial); + } +} + +void ShadowedRectangleNode::setOffset(const QVector2D& offset) +{ + auto minDimension = std::min(m_rect.width(), m_rect.height()); + auto uniformOffset = offset / minDimension; + + if (m_material->offset != uniformOffset) { + m_material->offset = uniformOffset; + markDirty(QSGNode::DirtyMaterial); + m_offset = offset; + } +} + +void ShadowedRectangleNode::updateGeometry() +{ + auto rect = m_rect.adjusted(-m_size * m_aspect.x(), -m_size * m_aspect.y(), + m_size * m_aspect.x(), m_size * m_aspect.y()); + + auto offsetLength = m_offset.length(); + + rect = rect.adjusted(-offsetLength * m_aspect.x(), -offsetLength * m_aspect.y(), + offsetLength * m_aspect.x(), offsetLength * m_aspect.y()); + + QSGGeometry::updateTexturedRectGeometry(m_geometry, rect, QRectF{0.0, 0.0, 1.0, 1.0}); + markDirty(QSGNode::DirtyGeometry); +} + + diff --git a/src/shadowedrectangle.h b/src/shadowedrectangle.h new file mode 100644 --- /dev/null +++ b/src/shadowedrectangle.h @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2020 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#pragma once + +#include +#include + +class ShadowedRectangle : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(qreal size READ size WRITE setSize NOTIFY sizeChanged) + Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged) + Q_PROPERTY(qreal xOffset READ xOffset WRITE setXOffset NOTIFY xOffsetChanged) + Q_PROPERTY(qreal yOffset READ yOffset WRITE setYOffset NOTIFY yOffsetChanged) + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) + Q_PROPERTY(QColor shadowColor READ shadowColor WRITE setShadowColor NOTIFY shadowColorChanged) + +public: + ShadowedRectangle(QQuickItem *parent = nullptr); + ~ShadowedRectangle() override; + + qreal size() const; + void setSize(qreal newSize); + Q_SIGNAL void sizeChanged(); + + qreal radius() const; + void setRadius(qreal newRadius); + Q_SIGNAL void radiusChanged(); + + qreal xOffset() const; + void setXOffset(qreal newXOffset); + Q_SIGNAL void xOffsetChanged(); + + qreal yOffset() const; + void setYOffset(qreal newYOffset); + Q_SIGNAL void yOffsetChanged(); + + QColor color() const; + void setColor(const QColor &newColor); + Q_SIGNAL void colorChanged(); + + QColor shadowColor() const; + void setShadowColor(const QColor &newShadowColor); + Q_SIGNAL void shadowColorChanged(); + +protected: + QSGNode *updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *data) override; + +private: + class Private; + const std::unique_ptr d; +}; diff --git a/src/shadowedrectangle.cpp b/src/shadowedrectangle.cpp new file mode 100644 --- /dev/null +++ b/src/shadowedrectangle.cpp @@ -0,0 +1,146 @@ +/* + * SPDX-FileCopyrightText: 2020 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "shadowedrectangle.h" + +#include "scenegraph/shadowedrectanglenode.h" + +class ShadowedRectangle::Private +{ +public: + qreal size = 0.0; + qreal radius = 0.0; + qreal xOffset = 0.0; + qreal yOffset = 0.0; + QColor color = Qt::white; + QColor shadowColor = Qt::black; +}; + +ShadowedRectangle::ShadowedRectangle(QQuickItem *parentItem) + : QQuickItem(parentItem), d(new Private) +{ + setFlag(QQuickItem::ItemHasContents, true); +} + +ShadowedRectangle::~ShadowedRectangle() +{ +} + +qreal ShadowedRectangle::size() const +{ + return d->size; +} + +void ShadowedRectangle::setSize(qreal newSize) +{ + if (newSize == d->size) { + return; + } + + d->size = newSize; + update(); + Q_EMIT sizeChanged(); +} + +qreal ShadowedRectangle::radius() const +{ + return d->radius; +} + +void ShadowedRectangle::setRadius(qreal newRadius) +{ + if (newRadius == d->radius) { + return; + } + + d->radius = newRadius; + update(); + Q_EMIT radiusChanged(); +} + +qreal ShadowedRectangle::xOffset() const +{ + return d->xOffset; +} + +void ShadowedRectangle::setXOffset(qreal newXOffset) +{ + if (newXOffset == d->xOffset) { + return; + } + + d->xOffset = newXOffset; + update(); + Q_EMIT xOffsetChanged(); +} + +qreal ShadowedRectangle::yOffset() const +{ + return d->yOffset; +} + +void ShadowedRectangle::setYOffset(qreal newYOffset) +{ + if (newYOffset == d->yOffset) { + return; + } + + d->yOffset = newYOffset; + update(); + Q_EMIT yOffsetChanged(); +} + +QColor ShadowedRectangle::color() const +{ + return d->color; +} + +void ShadowedRectangle::setColor(const QColor & newColor) +{ + if (newColor == d->color) { + return; + } + + d->color = newColor; + update(); + Q_EMIT colorChanged(); +} + +QColor ShadowedRectangle::shadowColor() const +{ + return d->shadowColor; +} + +void ShadowedRectangle::setShadowColor(const QColor &newShadowColor) +{ + if (newShadowColor == d->shadowColor) { + return; + } + + d->shadowColor = newShadowColor; + update(); + Q_EMIT shadowColorChanged(); +} + +QSGNode *ShadowedRectangle::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *data) +{ + Q_UNUSED(data); + + if (!node) { + node = new ShadowedRectangleNode; + } + + auto elevatedNode = static_cast(node); + elevatedNode->setRect(boundingRect()); + elevatedNode->setSize(d->size); + elevatedNode->setRadius(d->radius); + elevatedNode->setOffset(QVector2D{float(d->xOffset), float(d->yOffset)}); + elevatedNode->setColor(d->color); + elevatedNode->setShadowColor(d->shadowColor); + elevatedNode->updateGeometry(); + + return elevatedNode; +} diff --git a/tests/ShadowedRectangleTest.qml b/tests/ShadowedRectangleTest.qml new file mode 100644 --- /dev/null +++ b/tests/ShadowedRectangleTest.qml @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: 2020 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +import QtQuick 2.12 +import QtQuick.Controls 2.12 + +import org.kde.kirigami 2.12 as Kirigami + +Kirigami.ApplicationWindow { + id: window + + width: 500 + height: 500 + + pageStack.initialPage: Kirigami.Page { + leftPadding: 0 + rightPadding: 0 + topPadding: 0 + bottomPadding: 0 + + Column { + anchors.centerIn: parent + + Kirigami.ShadowedRectangle { + width: 400 + height: 300 + + color: Kirigami.Theme.highlightColor + + size: sizeSlider.value + radius: radiusSlider.value + + xOffset: xOffsetSlider.value + yOffset: yOffsetSlider.value + } + + Item { width: 1; height: Kirigami.Units.gridUnit } + + Slider { + id: sizeSlider + + from: 0 + to: 100 + } + + Slider { + id: radiusSlider + + from: 0 + to: 200 + } + + Slider { + id: xOffsetSlider + + from: -100 + to: 100 + } + + Slider { + id: yOffsetSlider + + from: -100 + to: 100 + } + } + } +}