diff --git a/kirigami.qrc b/kirigami.qrc --- a/kirigami.qrc +++ b/kirigami.qrc @@ -79,6 +79,7 @@ src/controls/AbstractApplicationHeader.qml src/controls/FormLayout.qml src/controls/ListItemDragHandle.qml + src/controls/ShadowedImage.qml src/styles/Material/AbstractListItem.qml src/styles/Material/Theme.qml src/styles/Material/SwipeListItem.qml diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -25,15 +25,19 @@ mnemonicattached.cpp wheelhandler.cpp shadowedrectangle.cpp + shadowedtexture.cpp scenegraph/shadowedrectanglenode.cpp scenegraph/shadowedrectanglematerial.cpp scenegraph/shadowedborderrectanglematerial.cpp scenegraph/paintedrectangleitem.cpp + scenegraph/shadowedtexturenode.cpp + scenegraph/shadowedtexturematerial.cpp + scenegraph/shadowedbordertexturematerial.cpp ${kirigami_QM_LOADER} ${KIRIGAMI_STATIC_FILES} ) -qt5_add_resources(SHADERS scenegraph/shaders.qrc) +qt5_add_resources(SHADERS scenegraph/shaders/shaders.qrc) add_subdirectory(libkirigami) diff --git a/src/controls/ShadowedImage.qml b/src/controls/ShadowedImage.qml new file mode 100644 --- /dev/null +++ b/src/controls/ShadowedImage.qml @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2020 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +import QtQuick 2.1 +import org.kde.kirigami 2.12 + +Item { + property alias color: shadowRectangle.color + property alias radius: shadowRectangle.radius + property alias shadow: shadowRectangle.shadow + property alias border: shadowRectangle.border + property alias source: image.source + + ShadowedTexture { + id: shadowRectangle + anchors.fill: parent + + source: Image { + id: image + visible: false + } + } +} diff --git a/src/kirigamiplugin.cpp b/src/kirigamiplugin.cpp --- a/src/kirigamiplugin.cpp +++ b/src/kirigamiplugin.cpp @@ -18,6 +18,7 @@ #include "scenepositionattached.h" #include "wheelhandler.h" #include "shadowedrectangle.h" +#include "shadowedtexture.h" #include #include @@ -238,6 +239,9 @@ // 2.12 qmlRegisterType(uri, 2, 12, "ShadowedRectangle"); + qmlRegisterType(uri, 2, 12, "ShadowedTexture"); + qmlRegisterType(componentUrl(QStringLiteral("ShadowedImage.qml")), uri, 2, 12, "ShadowedImage"); + qmlRegisterUncreatableType(uri, 2, 12, "BorderGroup", QStringLiteral("Used as grouped property")); qmlRegisterUncreatableType(uri, 2, 12, "ShadowGroup", QStringLiteral("Used as grouped property")); diff --git a/src/scenegraph/shaders.qrc b/src/scenegraph/shaders.qrc deleted file mode 100644 --- a/src/scenegraph/shaders.qrc +++ /dev/null @@ -1,15 +0,0 @@ - - - - header_es.glsl - header_desktop.glsl - header_desktop_core.glsl - shadowedrectangle.vert - shadowedrectangle_core.vert - shadowedrectangle.frag - shadowedrectangle_core.frag - shadowedborderrectangle.frag - shadowedborderrectangle_core.frag - - - diff --git a/src/scenegraph/header_desktop.glsl b/src/scenegraph/shaders/header_desktop.glsl rename from src/scenegraph/header_desktop.glsl rename to src/scenegraph/shaders/header_desktop.glsl diff --git a/src/scenegraph/header_desktop_core.glsl b/src/scenegraph/shaders/header_desktop_core.glsl rename from src/scenegraph/header_desktop_core.glsl rename to src/scenegraph/shaders/header_desktop_core.glsl --- a/src/scenegraph/header_desktop_core.glsl +++ b/src/scenegraph/shaders/header_desktop_core.glsl @@ -12,3 +12,5 @@ // This file is intended for desktop OpenGL version 4.5 or greater. #version 450 + +#define CORE_PROFILE diff --git a/src/scenegraph/header_es.glsl b/src/scenegraph/shaders/header_es.glsl rename from src/scenegraph/header_es.glsl rename to src/scenegraph/shaders/header_es.glsl diff --git a/src/scenegraph/shaders/sdf.glsl b/src/scenegraph/shaders/sdf.glsl new file mode 100644 --- /dev/null +++ b/src/scenegraph/shaders/sdf.glsl @@ -0,0 +1,276 @@ +// SPDX-FileCopyrightText: 2020 Arjen Hiemstra +// SPDX-FileCopyrightText: 2017 Inigo Quilez +// +// SPDX-License-Identifier: MIT +// +// This file is based on +// https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm + +//if not GLES +// include "desktop_header.glsl" +//else +// include "es_header.glsl" + +// A maximum point count to be used for sdf_polygon input arrays. +// Unfortunately even function inputs require a fixed size at declaration time +// for arrays, unless we were to use OpenGL 4.5. +// Since the polygon is most likely to be defined in a uniform, this should be +// at least less than MAX_FRAGMENT_UNIFORM_COMPONENTS / 2 (since we need vec2). +#define SDF_POLYGON_MAX_POINT_COUNT 400 + +/********************************* + Shapes +*********************************/ + +// Distance field for a circle. +// +// \param point A point on the distance field. +// \param radius The radius of the circle. +// +// \return The signed distance from point to the circle. If negative, point is +// inside the circle. +lowp float sdf_circle(in lowp vec2 point, in lowp float radius) +{ + return length(point) - radius; +} + +// Distance field for a triangle. +// +// \param point A point on the distance field. +// \param p0 The first vertex of the triangle. +// \param p0 The second vertex of the triangle. +// \param p0 The third vertex of the triangle. +// +// \note The ordering of the three vertices does not matter. +// +// \return The signed distance from point to triangle. If negative, point is +// inside the triangle. +lowp float sdf_triangle(in lowp vec2 point, in lowp vec2 p0, in lowp vec2 p1, in lowp vec2 p2) +{ + lowp vec2 e0 = p1 - p0; + lowp vec2 e1 = p2 - p1; + lowp vec2 e2 = p0 - p2; + + lowp vec2 v0 = point - p0; + lowp vec2 v1 = point - p1; + lowp vec2 v2 = point - p2; + + lowp vec2 pq0 = v0 - e0 * clamp( dot(v0, e0) / dot(e0, e0), 0.0, 1.0 ); + lowp vec2 pq1 = v1 - e1 * clamp( dot(v1, e1) / dot(e1, e1), 0.0, 1.0 ); + lowp vec2 pq2 = v2 - e2 * clamp( dot(v2, e2) / dot(e2, e2), 0.0, 1.0 ); + + lowp float s = sign( e0.x*e2.y - e0.y*e2.x ); + lowp vec2 d = min(min(vec2(dot(pq0,pq0), s*(v0.x*e0.y-v0.y*e0.x)), + vec2(dot(pq1,pq1), s*(v1.x*e1.y-v1.y*e1.x))), + vec2(dot(pq2,pq2), s*(v2.x*e2.y-v2.y*e2.x))); + + return -sqrt(d.x)*sign(d.y); +} + +// Distance field for an arbitrary polygon. +// +// \param point A point on the distance field. +// \param vertices An array of points that make up the polygon. +// \param count The amount of points to use for the polygon. +// +// \note points should be an array of vec2 of size SDF_POLYGON_MAX_POINT_COUNT. +// Use count to indicate how many items of that array should be used. +// +// \return The signed distance from point to triangle. If negative, point is +// inside the triangle. + +// Strictly speaking, GLES 2.0 doesn't support function array arguments (apparently), so our validation fails here. +// But at least Mesa GLES 2.0 accepts it, so skip validation here instead. +#if !defined(GL_ES) || !defined(VALIDATING) +lowp float sdf_polygon(in lowp vec2 point, in lowp vec2[SDF_POLYGON_MAX_POINT_COUNT] vertices, in lowp int count) +{ + lowp float d = dot(point - vertices[0], point - vertices[0]); + lowp float s = 1.0; + for (int i = 0, j = count - 1; i < count && i < SDF_POLYGON_MAX_POINT_COUNT; j = i, i++) + { + lowp vec2 e = vertices[j] - vertices[i]; + lowp vec2 w = point - vertices[i]; + lowp float h = clamp( dot(w, e) / dot(e, e), 0.0, 1.0 ); + lowp vec2 b = w - e * h; + d = min(d, dot(b, b)); + + bvec3 c = bvec3(point.y >= vertices[i].y, point.y < vertices[j].y, e.x * w.y > e.y * w.x); + if(all(c) || all(not(c))) s *= -1.0; + } + return s * sqrt(d); +} +#endif + +// Distance field for a rectangle. +// +// \param point A point on the distance field. +// \param rect A vec2 with the size of the rectangle. +// +// \return The signed distance from point to rectangle. If negative, point is +// inside the rectangle. +lowp float sdf_rectangle(in lowp vec2 point, in lowp vec2 rect) +{ + lowp vec2 d = abs(point) - rect; + return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0); +} + +// Distance field for a rectangle with rounded corners. +// +// \param point The point to calculate the distance of. +// \param rect The rectangle to calculate the distance of. +// \param radius A vec4 with the radius of each corner. Order is top right, bottom right, top left, bottom left. +// +// \return The signed distance from point to rectangle. If negative, point is +// inside the rectangle. +lowp float sdf_rounded_rectangle(in lowp vec2 point, in lowp vec2 rect, 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) - rect + radius.x; + return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - radius.x; +} + +/********************* + Operators +*********************/ + +// Convert a distance field to an annular (hollow) distance field. +// +// \param sdf The result of an sdf shape to convert. +// \param thickness The thickness of the resulting shape. +// +// \return The value of sdf modified to an annular shape. +lowp float sdf_annular(in lowp float sdf, in lowp float thickness) +{ + return abs(sdf) - thickness; +} + +// Union two sdf shapes together. +// +// \param sdf1 The first sdf shape. +// \param sdf2 The second sdf shape. +// +// \return The union of sdf1 and sdf2, that is, the distance to both sdf1 and +// sdf2. +lowp float sdf_union(in lowp float sdf1, in lowp float sdf2) +{ + return min(sdf1, sdf2); +} + +// Subtract two sdf shapes. +// +// \param sdf1 The first sdf shape. +// \param sdf2 The second sdf shape. +// +// \return sdf1 with sdf2 subtracted from it. +lowp float sdf_subtract(in lowp float sdf1, in lowp float sdf2) +{ + return max(sdf1, -sdf2); +} + +// Intersect two sdf shapes. +// +// \param sdf1 The first sdf shape. +// \param sdf2 The second sdf shape. +// +// \return The intersection between sdf1 and sdf2, that is, the area where both +// sdf1 and sdf2 provide the same distance value. +lowp float sdf_intersect(in lowp float sdf1, in lowp float sdf2) +{ + return max(sdf1, sdf2); +} + +// Smoothly intersect two sdf shapes. +// +// \param sdf1 The first sdf shape. +// \param sdf2 The second sdf shape. +// \param smoothing The amount of smoothing to apply. +// +// \return A smoothed version of the intersect operation. +lowp float sdf_intersect_smooth(in lowp float sdf1, in lowp float sdf2, in lowp float smoothing) +{ + lowp float h = clamp(0.5 - 0.5 * (sdf1 - sdf2) / smoothing, 0.0, 1.0); + return mix(sdf1, sdf2, h) + smoothing * h * (1.0 - h); +} + +// Round an sdf shape. +// +// \param sdf The sdf shape to round. +// \param amount The amount of rounding to apply. +// +// \return The rounded shape of sdf. +// Note that rounding happens by basically selecting an isoline of sdf, +// therefore, the resulting shape may be larger than the input shape. +lowp float sdf_round(in lowp float sdf, in lowp float amount) +{ + return sdf - amount; +} + +// Convert an sdf shape to an outline of its shape. +// +// \param sdf The sdf shape to turn into an outline. +// +// \return The outline of sdf. +lowp float sdf_outline(in lowp float sdf) +{ + return abs(sdf); +} + +/******************** + Convenience +********************/ + +// A constant to represent a "null" value of an sdf. +// +// Since 0 is a point exactly on the outline of an sdf shape, and negative +// values are inside the shape, this uses a very large positive constant to +// indicate a value that is really far away from the actual sdf shape. +const lowp float sdf_null = 99999.0; + +// A constant for a default level of smoothing when rendering an sdf. +// +// This +const lowp float sdf_default_smoothing = 0.001; + +// Render an sdf shape. +// +// This will render the sdf shape on top of whatever source color is input, +// making sure to apply smoothing if desired. +// +// \param sdf The sdf shape to render. +// \param sourceColor The source color to render on top of. +// \param sdfColor The color to use for rendering the sdf shape. +// +// \return sourceColor with the sdf shape rendered on top. +lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor) +{ + lowp float g = fwidth(sdf); + return mix(sourceColor, sdfColor, 1.0 - smoothstep(sdf_default_smoothing - g, sdf_default_smoothing + g, sdf)); +} + +// Render an sdf shape. +// +// This is an overload of sdf_render(float, vec4, vec4) that allows specifying a +// smoothing amount. +// +// \param smoothing The amount of smoothing to apply to the sdf. +// +lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor, in lowp float smoothing) +{ + lowp float g = fwidth(sdf); + return mix(sourceColor, sdfColor, 1.0 - smoothstep(smoothing - g, smoothing + g, sdf)); +} + +// Render an sdf shape alpha-blended onto an existing color. +// +// This is an overload of sdf_render(float, vec4, vec4) that allows specifying a +// blending amount and a smoothing amount. +// +// \param alpha The alpha to use for blending. +// \param smoothing The amount of smoothing to apply to the sdf. +// +lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor, in lowp float alpha, in lowp float smoothing) +{ + lowp float g = fwidth(sdf); + return mix(sourceColor, sdfColor, alpha * (1.0 - smoothstep(smoothing - g, smoothing + g, sdf))); +} diff --git a/src/scenegraph/shaders/shaders.qrc b/src/scenegraph/shaders/shaders.qrc new file mode 100644 --- /dev/null +++ b/src/scenegraph/shaders/shaders.qrc @@ -0,0 +1,21 @@ + + + + header_es.glsl + header_desktop.glsl + header_desktop_core.glsl + sdf.glsl + sdf.glsl + shadowedrectangle.vert + shadowedrectangle.vert + shadowedrectangle.frag + shadowedrectangle.frag + shadowedborderrectangle.frag + shadowedborderrectangle.frag + shadowedtexture.frag + shadowedborderrectangle.frag + shadowedbordertexture.frag + shadowedborderrectangle.frag + + + diff --git a/src/scenegraph/shadowedborderrectangle.frag b/src/scenegraph/shaders/shadowedborderrectangle.frag rename from src/scenegraph/shadowedborderrectangle.frag rename to src/scenegraph/shaders/shadowedborderrectangle.frag --- a/src/scenegraph/shadowedborderrectangle.frag +++ b/src/scenegraph/shaders/shadowedborderrectangle.frag @@ -4,10 +4,7 @@ * SPDX-License-Identifier: LGPL-2.0-or-later */ -#line 7 - -// This is based on the 2D SDF functions provided by Inigo Quilez: -// https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm +// See sdf.glsl for the SDF related functions. // This shader renders a rectangle with rounded corners and a shadow below it. // In addition it renders a border around it. @@ -22,30 +19,14 @@ uniform lowp float borderWidth; uniform lowp vec4 borderColor; +#ifdef CORE_PROFILE +in lowp vec2 uv; +out lowp vec4 out_color; +#else varying lowp vec2 uv; +#endif const lowp float minimum_shadow_radius = 0.05; -const lowp float smoothing = 0.001; - -// 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. Order is top right, bottom right, top left, bottom left. -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; -} - -// Render an sdf value into a color. -lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor, in lowp float sdfAlpha) -{ - lowp float g = fwidth(sdf); - return mix(sourceColor, sdfColor, sdfAlpha * (1.0 - smoothstep(smoothing - g, smoothing + g, sdf))); -} void main() { @@ -56,39 +37,42 @@ // 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 shadow_radius = 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 * 2.0 * inverse_scale, vec4(shadow_radius * inverse_scale)); + lowp float shadow = sdf_rounded_rectangle(uv - offset * 2.0 * inverse_scale, aspect * inverse_scale, vec4(shadow_radius * inverse_scale)); // Render it, interpolating the color over the distance. col = mix(col, shadowColor * sign(size), 1.0 - smoothstep(-size * 0.5, size * 0.5, shadow)); // Scale corrected corner radius lowp vec4 corner_radius = vec4(radius * inverse_scale); // Calculate the outer rectangle distance field. - lowp float outer_rect = sdf_rounded_rectangle(uv, aspect * inverse_scale, vec2(0.0), corner_radius); + lowp float outer_rect = sdf_rounded_rectangle(uv, aspect * inverse_scale, corner_radius); // 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 = sdf_render(outer_rect, col, vec4(0.0), 1.0); + col = sdf_render(outer_rect, col, vec4(0.0)); // Then, render it again but this time with the proper color and properly alpha blended. - col = sdf_render(outer_rect, col, borderColor, 1.0); + col = sdf_render(outer_rect, col, borderColor); // Calculate the inner rectangle distance field. // This uses a reduced corner radius because the inner corners need to be smaller than the outer corners. lowp vec4 inner_radius = vec4((radius - borderWidth * 2.0) * inverse_scale); - lowp float inner_rect = sdf_rounded_rectangle(uv, (aspect - borderWidth * 2.0) * inverse_scale, vec2(0.0), inner_radius); + lowp float inner_rect = sdf_rounded_rectangle(uv, (aspect - borderWidth * 2.0) * inverse_scale, inner_radius); // Like above, but this time cut out the inner rectangle. - col = sdf_render(inner_rect, col, vec4(0.0), 1.0); + col = sdf_render(inner_rect, col, vec4(0.0)); // Finally, render the inner rectangle. - col = sdf_render(inner_rect, col, color, 1.0); + col = sdf_render(inner_rect, col, color); +#ifdef CORE_PROFILE + out_color = col * opacity; +#else gl_FragColor = col * opacity; +#endif } diff --git a/src/scenegraph/shadowedborderrectangle_core.frag b/src/scenegraph/shaders/shadowedbordertexture.frag rename from src/scenegraph/shadowedborderrectangle_core.frag rename to src/scenegraph/shaders/shadowedbordertexture.frag --- a/src/scenegraph/shadowedborderrectangle_core.frag +++ b/src/scenegraph/shaders/shadowedbordertexture.frag @@ -4,10 +4,7 @@ * SPDX-License-Identifier: LGPL-2.0-or-later */ -#line 7 - -// This is based on the 2D SDF functions provided by Inigo Quilez: -// https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm +// See sdf.glsl for the SDF related functions. // This shader renders a rectangle with rounded corners and a shadow below it. // In addition it renders a border around it. @@ -21,32 +18,25 @@ uniform lowp vec2 aspect; uniform lowp float borderWidth; uniform lowp vec4 borderColor; +uniform sampler2D textureSource; +#ifdef CORE_PROFILE in lowp vec2 uv; - out lowp vec4 out_color; +#else +varying lowp vec2 uv; +#endif const lowp float minimum_shadow_radius = 0.05; -const lowp float smoothing = 0.001; - -// 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. Order is top right, bottom right, top left, bottom left. -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; -} -// Render an sdf value into a color. -lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor, in lowp float sdfAlpha) +// Tiny abstraction around texture() to deal with Core profile differences. +lowp vec4 sample_texture(in sampler2D texture_source, in lowp vec2 uv) { - lowp float g = fwidth(sdf); - return mix(sourceColor, sdfColor, sdfAlpha * (1.0 - smoothstep(smoothing - g, smoothing + g, sdf))); +#ifdef CORE_PROFILE + return texture(texture_source, uv); +#else + return texture2D(texture_source, uv); +#endif } void main() @@ -58,39 +48,49 @@ // 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 shadow_radius = 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 * 2.0 * inverse_scale, vec4(shadow_radius * inverse_scale)); + lowp float shadow = sdf_rounded_rectangle(uv - offset * 2.0 * inverse_scale, aspect * inverse_scale, vec4(shadow_radius * inverse_scale)); // Render it, interpolating the color over the distance. col = mix(col, shadowColor * sign(size), 1.0 - smoothstep(-size * 0.5, size * 0.5, shadow)); // Scale corrected corner radius lowp vec4 corner_radius = vec4(radius * inverse_scale); // Calculate the outer rectangle distance field. - lowp float outer_rect = sdf_rounded_rectangle(uv, aspect * inverse_scale, vec2(0.0), corner_radius); + lowp float outer_rect = sdf_rounded_rectangle(uv, aspect * inverse_scale, corner_radius); // 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 = sdf_render(outer_rect, col, vec4(0.0), 1.0); + col = sdf_render(outer_rect, col, vec4(0.0)); // Then, render it again but this time with the proper color and properly alpha blended. - col = sdf_render(outer_rect, col, borderColor, 1.0); + col = sdf_render(outer_rect, col, borderColor); // Calculate the inner rectangle distance field. // This uses a reduced corner radius because the inner corners need to be smaller than the outer corners. lowp vec4 inner_radius = vec4((radius - borderWidth * 2.0) * inverse_scale); - lowp float inner_rect = sdf_rounded_rectangle(uv, (aspect - borderWidth * 2.0) * inverse_scale, vec2(0.0), inner_radius); + lowp float inner_rect = sdf_rounded_rectangle(uv, (aspect - borderWidth * 2.0) * inverse_scale, inner_radius); // Like above, but this time cut out the inner rectangle. - col = sdf_render(inner_rect, col, vec4(0.0), 1.0); + col = sdf_render(inner_rect, col, vec4(0.0)); // Finally, render the inner rectangle. - col = sdf_render(inner_rect, col, color, 1.0); + col = sdf_render(inner_rect, col, color); + + inner_rect = sdf_rounded_rectangle(uv, (aspect - borderWidth * 2.0 + 0.005) * inverse_scale, inner_radius); + + // Sample the texture, then blend it on top of the background color. + lowp vec2 texture_uv = ((uv / aspect) + (1.0 * inverse_scale)) / (2.0 * inverse_scale); + lowp vec4 texture_color = sample_texture(textureSource, texture_uv); + col = sdf_render(inner_rect, col, texture_color, texture_color.a, sdf_default_smoothing / 2.0); +#ifdef CORE_PROFILE out_color = col * opacity; +#else + gl_FragColor = col * opacity; +#endif } diff --git a/src/scenegraph/shadowedrectangle_core.frag b/src/scenegraph/shaders/shadowedrectangle.frag rename from src/scenegraph/shadowedrectangle_core.frag rename to src/scenegraph/shaders/shadowedrectangle.frag --- a/src/scenegraph/shadowedrectangle_core.frag +++ b/src/scenegraph/shaders/shadowedrectangle.frag @@ -4,8 +4,7 @@ * 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 +// See sdf.glsl for the SDF related functions. // This shader renders a rectangle with rounded corners and a shadow below it. @@ -17,25 +16,15 @@ uniform lowp vec2 offset; uniform lowp vec2 aspect; +#ifdef CORE_PROFILE in lowp vec2 uv; - out lowp vec4 out_color; +#else +varying lowp vec2 uv; +#endif 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. Order is top right, bottom right, top left, bottom left. -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. @@ -45,27 +34,28 @@ // 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 float shadow_radius = 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 * 2.0 * inverse_scale, vec4(shadowRadius * inverse_scale)); + lowp float shadow = sdf_rounded_rectangle(uv - offset * 2.0 * inverse_scale, aspect * inverse_scale, vec4(shadow_radius * inverse_scale)); // Render it, interpolating the color over the distance. col = mix(col, shadowColor * sign(size), 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); + lowp float rect = sdf_rounded_rectangle(uv, aspect * inverse_scale, vec4(radius * inverse_scale)); // 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)); + col = sdf_render(rect, col, vec4(0.0)); // Then, render it again but this time with the proper color and properly alpha blended. - col = mix(col, color, 1.0 - smoothstep(0.001 - g, 0.001 + g, rect)); + col = sdf_render(rect, col, color); +#ifdef CORE_PROFILE out_color = col * opacity; +#else + gl_FragColor = col * opacity; +#endif } diff --git a/src/scenegraph/shadowedrectangle.vert b/src/scenegraph/shaders/shadowedrectangle.vert rename from src/scenegraph/shadowedrectangle.vert rename to src/scenegraph/shaders/shadowedrectangle.vert --- a/src/scenegraph/shadowedrectangle.vert +++ b/src/scenegraph/shaders/shadowedrectangle.vert @@ -6,12 +6,16 @@ uniform highp mat4 matrix; uniform lowp vec2 aspect; -uniform lowp vec2 offset; +#ifdef CORE_PROFILE +in highp vec4 in_vertex; +in mediump vec2 in_uv; +out mediump vec2 uv; +#else attribute highp vec4 in_vertex; attribute mediump vec2 in_uv; - varying mediump vec2 uv; +#endif void main() { uv = (-1.0 + 2.0 * in_uv) * aspect; diff --git a/src/scenegraph/shadowedrectangle.frag b/src/scenegraph/shaders/shadowedtexture.frag rename from src/scenegraph/shadowedrectangle.frag rename to src/scenegraph/shaders/shadowedtexture.frag --- a/src/scenegraph/shadowedrectangle.frag +++ b/src/scenegraph/shaders/shadowedtexture.frag @@ -4,34 +4,37 @@ * 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 +// See sdf.glsl for the SDF related functions. -// This shader renders a rectangle with rounded corners and a shadow below it. +// This shader renders a texture on top of a rectangle with rounded corners and +// a shadow below it. 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; +uniform sampler2D textureSource; +#ifdef CORE_PROFILE +in lowp vec2 uv; +out lowp vec4 out_color; +#else varying lowp vec2 uv; +#endif 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. Order is top right, bottom right, top left, bottom left. -lowp float sdf_rounded_rectangle(in lowp vec2 point, in lowp vec2 rect, in lowp vec2 translation, in lowp vec4 radius) +// Tiny abstraction around texture() to deal with Core profile differences. +lowp vec4 sample_texture(in sampler2D texture_source, in lowp vec2 uv) { - 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; +#ifdef CORE_PROFILE + return texture(texture_source, uv); +#else + return texture2D(texture_source, uv); +#endif } void main() @@ -43,27 +46,34 @@ // 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 float shadow_radius = 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 * 2.0 * inverse_scale, vec4(shadowRadius * inverse_scale)); + lowp float shadow = sdf_rounded_rectangle(uv - offset * 2.0 * inverse_scale, aspect * inverse_scale, vec4(shadow_radius * inverse_scale)); // Render it, interpolating the color over the distance. col = mix(col, shadowColor * sign(size), 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); + lowp float rect = sdf_rounded_rectangle(uv, aspect * inverse_scale, vec4(radius * inverse_scale)); // 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)); + col = sdf_render(rect, col, vec4(0.0)); + + // Then, render it again but this time with the proper color. + col = sdf_render(rect, col, color); - // Then, render it again but this time with the proper color and properly alpha blended. - col = mix(col, color, 1.0 - smoothstep(0.001 - g, 0.001 + g, rect)); + // Sample the texture, then blend it on top of the background color. + lowp vec2 texture_uv = ((uv / aspect) + (1.0 * inverse_scale)) / (2.0 * inverse_scale); + lowp vec4 texture_color = sample_texture(textureSource, texture_uv); + col = sdf_render(rect, col, texture_color, texture_color.a, sdf_default_smoothing); +// col = sdf_render(rect, col, vec4(texture_uv / inverse_scale, sign(texture_uv).x, 1.0)); +#ifdef CORE_PROFILE + out_color = col * opacity; +#else gl_FragColor = col * opacity; +#endif } diff --git a/src/scenegraph/shadowedborderrectanglematerial.cpp b/src/scenegraph/shadowedborderrectanglematerial.cpp --- a/src/scenegraph/shadowedborderrectanglematerial.cpp +++ b/src/scenegraph/shadowedborderrectanglematerial.cpp @@ -48,6 +48,7 @@ setShaderSourceFiles(QOpenGLShader::Fragment, { shaderRoot + header, + shaderRoot + QStringLiteral("sdf.glsl"), shaderRoot + QStringLiteral("shadowedborderrectangle.frag") }); } diff --git a/src/scenegraph/shadowedbordertexturematerial.h b/src/scenegraph/shadowedbordertexturematerial.h new file mode 100644 --- /dev/null +++ b/src/scenegraph/shadowedbordertexturematerial.h @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: 2020 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#pragma once + +#include + +#include "shadowedborderrectanglematerial.h" + +class ShadowedBorderTextureMaterial : public ShadowedBorderRectangleMaterial +{ +public: + ShadowedBorderTextureMaterial(); + + QSGMaterialShader* createShader() const override; + QSGMaterialType* type() const override; + int compare(const QSGMaterial* other) const override; + + QSGTexture *textureSource = nullptr; + + static QSGMaterialType staticType; +}; + +class ShadowedBorderTextureShader : public ShadowedBorderRectangleShader +{ +public: + ShadowedBorderTextureShader(); + + void initialize() override; + void updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override; +}; diff --git a/src/scenegraph/shadowedbordertexturematerial.cpp b/src/scenegraph/shadowedbordertexturematerial.cpp new file mode 100644 --- /dev/null +++ b/src/scenegraph/shadowedbordertexturematerial.cpp @@ -0,0 +1,74 @@ +/* + * SPDX-FileCopyrightText: 2020 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "shadowedbordertexturematerial.h" + +#include + +QSGMaterialType ShadowedBorderTextureMaterial::staticType; + +ShadowedBorderTextureMaterial::ShadowedBorderTextureMaterial() + : ShadowedBorderRectangleMaterial() +{ + setFlag(QSGMaterial::Blending, true); +} + +QSGMaterialShader* ShadowedBorderTextureMaterial::createShader() const +{ + return new ShadowedBorderTextureShader{}; +} + +QSGMaterialType* ShadowedBorderTextureMaterial::type() const +{ + return &staticType; +} + +int ShadowedBorderTextureMaterial::compare(const QSGMaterial *other) const +{ + auto material = static_cast(other); + + auto result = ShadowedBorderRectangleMaterial::compare(other); + if (result == 0 + && material->textureSource == textureSource) { + return 0; + } + + return result; +} + +ShadowedBorderTextureShader::ShadowedBorderTextureShader() +{ + 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("sdf.glsl"), + shaderRoot + QStringLiteral("shadowedbordertexture.frag") + }); +} + +void ShadowedBorderTextureShader::initialize() +{ + ShadowedBorderRectangleShader::initialize(); + program()->setUniformValue("textureSource", 0); +} + +void ShadowedBorderTextureShader::updateState(const QSGMaterialShader::RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) +{ + ShadowedBorderRectangleShader::updateState(state, newMaterial, oldMaterial); + + auto texture = static_cast(newMaterial)->textureSource; + if (texture) { + texture->bind(); + } +} diff --git a/src/scenegraph/shadowedrectangle_core.vert b/src/scenegraph/shadowedrectangle_core.vert deleted file mode 100644 --- a/src/scenegraph/shadowedrectangle_core.vert +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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.cpp b/src/scenegraph/shadowedrectanglematerial.cpp --- a/src/scenegraph/shadowedrectanglematerial.cpp +++ b/src/scenegraph/shadowedrectanglematerial.cpp @@ -54,6 +54,7 @@ setShaderSourceFiles(QOpenGLShader::Fragment, { shaderRoot + header, + shaderRoot + QStringLiteral("sdf.glsl"), shaderRoot + QStringLiteral("shadowedrectangle.frag") }); } diff --git a/src/scenegraph/shadowedrectanglenode.h b/src/scenegraph/shadowedrectanglenode.h --- a/src/scenegraph/shadowedrectanglenode.h +++ b/src/scenegraph/shadowedrectanglenode.h @@ -10,7 +10,9 @@ #include #include +class QSGMaterialType; class ShadowedRectangleMaterial; +class ShadowedBorderRectangleMaterial; /** * Scene graph node for a shadowed rectangle. @@ -53,10 +55,16 @@ */ void updateGeometry(); -private: +protected: + virtual ShadowedRectangleMaterial *createBorderlessMaterial(); + virtual ShadowedBorderRectangleMaterial *createBorderMaterial(); + virtual QSGMaterialType* borderMaterialType(); + virtual QSGMaterialType* borderlessMaterialType(); + QSGGeometry *m_geometry; - ShadowedRectangleMaterial *m_material; + ShadowedRectangleMaterial *m_material = nullptr; +private: QRectF m_rect; qreal m_size = 0.0; qreal m_radius = 0.0; diff --git a/src/scenegraph/shadowedrectanglenode.cpp b/src/scenegraph/shadowedrectanglenode.cpp --- a/src/scenegraph/shadowedrectanglenode.cpp +++ b/src/scenegraph/shadowedrectanglenode.cpp @@ -23,9 +23,6 @@ m_geometry = new QSGGeometry{QSGGeometry::defaultAttributes_TexturedPoint2D(), 4}; setGeometry(m_geometry); - m_material = new ShadowedRectangleMaterial{}; - setMaterial(m_material); - setFlags(QSGNode::OwnsGeometry | QSGNode::OwnsMaterial); } @@ -37,19 +34,18 @@ // switch to the with-border material. Otherwise use the no-border version. if (enabled) { - if (m_material->type() == &ShadowedRectangleMaterial::staticType) { - auto newMaterial = new ShadowedBorderRectangleMaterial(); + if (!m_material || m_material->type() == borderlessMaterialType()) { + auto newMaterial = createBorderMaterial(); setMaterial(newMaterial); m_material = newMaterial; m_rect = QRectF{}; markDirty(QSGNode::DirtyMaterial); } } else { - if (m_material->type() == &ShadowedBorderRectangleMaterial::staticType) { - auto newMaterial = new ShadowedRectangleMaterial(); + if (!m_material || m_material->type() == borderMaterialType()) { + auto newMaterial = createBorderlessMaterial(); setMaterial(newMaterial); m_material = newMaterial; - m_material->aspect = m_aspect; m_rect = QRectF{}; markDirty(QSGNode::DirtyMaterial); } @@ -134,7 +130,7 @@ void ShadowedRectangleNode::setBorderWidth(qreal width) { - if (m_material->type() != &ShadowedBorderRectangleMaterial::staticType) { + if (m_material->type() != borderMaterialType()) { return; } @@ -151,7 +147,7 @@ void ShadowedRectangleNode::setBorderColor(const QColor& color) { - if (m_material->type() != &ShadowedBorderRectangleMaterial::staticType) { + if (m_material->type() != borderMaterialType()) { return; } @@ -176,3 +172,23 @@ QSGGeometry::updateTexturedRectGeometry(m_geometry, rect, QRectF{0.0, 0.0, 1.0, 1.0}); markDirty(QSGNode::DirtyGeometry); } + +ShadowedRectangleMaterial *ShadowedRectangleNode::createBorderlessMaterial() +{ + return new ShadowedRectangleMaterial{}; +} + +ShadowedBorderRectangleMaterial *ShadowedRectangleNode::createBorderMaterial() +{ + return new ShadowedBorderRectangleMaterial{}; +} + +QSGMaterialType *ShadowedRectangleNode::borderlessMaterialType() +{ + return &ShadowedRectangleMaterial::staticType; +} + +QSGMaterialType *ShadowedRectangleNode::borderMaterialType() +{ + return &ShadowedBorderRectangleMaterial::staticType; +} diff --git a/src/scenegraph/shadowedtexturematerial.h b/src/scenegraph/shadowedtexturematerial.h new file mode 100644 --- /dev/null +++ b/src/scenegraph/shadowedtexturematerial.h @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: 2020 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#pragma once + +#include + +#include "shadowedrectanglematerial.h" + +/** + * A material rendering a rectangle with a shadow. + * + * This material uses a distance field shader to render a rectangle with a + * shadow below it, optionally with rounded corners. + */ +class ShadowedTextureMaterial : public ShadowedRectangleMaterial +{ +public: + ShadowedTextureMaterial(); + + QSGMaterialShader* createShader() const override; + QSGMaterialType* type() const override; + int compare(const QSGMaterial* other) const override; + + QSGTexture *textureSource = nullptr; + + static QSGMaterialType staticType; +}; + +class ShadowedTextureShader : public ShadowedRectangleShader +{ +public: + ShadowedTextureShader(); + + void initialize() override; + void updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override; +}; diff --git a/src/scenegraph/shadowedtexturematerial.cpp b/src/scenegraph/shadowedtexturematerial.cpp new file mode 100644 --- /dev/null +++ b/src/scenegraph/shadowedtexturematerial.cpp @@ -0,0 +1,74 @@ +/* + * SPDX-FileCopyrightText: 2020 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "shadowedtexturematerial.h" + +#include + +QSGMaterialType ShadowedTextureMaterial::staticType; + +ShadowedTextureMaterial::ShadowedTextureMaterial() + : ShadowedRectangleMaterial() +{ + setFlag(QSGMaterial::Blending, true); +} + +QSGMaterialShader* ShadowedTextureMaterial::createShader() const +{ + return new ShadowedTextureShader{}; +} + +QSGMaterialType* ShadowedTextureMaterial::type() const +{ + return &staticType; +} + +int ShadowedTextureMaterial::compare(const QSGMaterial *other) const +{ + auto material = static_cast(other); + + auto result = ShadowedRectangleMaterial::compare(other); + if (result == 0 + && material->textureSource == textureSource) { + return 0; + } + + return result; +} + +ShadowedTextureShader::ShadowedTextureShader() +{ + 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("sdf.glsl"), + shaderRoot + QStringLiteral("shadowedtexture.frag") + }); +} + +void ShadowedTextureShader::initialize() +{ + ShadowedRectangleShader::initialize(); + program()->setUniformValue("textureSource", 0); +} + +void ShadowedTextureShader::updateState(const QSGMaterialShader::RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) +{ + ShadowedRectangleShader::updateState(state, newMaterial, oldMaterial); + + auto texture = static_cast(newMaterial)->textureSource; + if (texture) { + texture->bind(); + } +} diff --git a/src/scenegraph/shadowedtexturenode.h b/src/scenegraph/shadowedtexturenode.h new file mode 100644 --- /dev/null +++ b/src/scenegraph/shadowedtexturenode.h @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2020 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#pragma once + +#include +#include + +#include "shadowedrectanglenode.h" +#include "shadowedtexturematerial.h" + +/** + * Scene graph node for a shadowed texture source. + * + * This node will set up the geometry and materials for a shadowed rectangle, + * optionally with rounded corners, using a supplied texture source as the color + * for the rectangle. + * + * \note You must call updateGeometry() after setting properties of this node, + * otherwise the node's state will not correctly reflect all the properties. + * + * \sa ShadowedTexture + */ +class ShadowedTextureNode : public ShadowedRectangleNode +{ +public: + ShadowedTextureNode(); + + void setTextureSource(QSGTextureProvider *source); + void preprocess() override; + +private: + ShadowedRectangleMaterial *createBorderlessMaterial() override; + ShadowedBorderRectangleMaterial *createBorderMaterial() override; + QSGMaterialType *borderlessMaterialType() override; + QSGMaterialType *borderMaterialType() override; + + QPointer m_textureSource; +}; diff --git a/src/scenegraph/shadowedtexturenode.cpp b/src/scenegraph/shadowedtexturenode.cpp new file mode 100644 --- /dev/null +++ b/src/scenegraph/shadowedtexturenode.cpp @@ -0,0 +1,81 @@ +/* + * SPDX-FileCopyrightText: 2020 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "shadowedtexturenode.h" + +#include +#include "shadowedtexturematerial.h" +#include "shadowedbordertexturematerial.h" + +template +inline void preprocessTexture(QSGMaterial *material, QSGTextureProvider *provider) +{ + auto m = static_cast(material); + // Since we handle texture coordinates differently in the shader, we + // need to remove the texture from the atlas for now. + if (provider->texture()->isAtlasTexture()) { + // Blegh, I have no idea why "removedFromAtlas" doesn't just return + // the texture when it's not an atlas. + m->textureSource = provider->texture()->removedFromAtlas(); + } else { + m->textureSource = provider->texture(); + } + if (QSGDynamicTexture *dynamic_texture = qobject_cast(m->textureSource)) { + dynamic_texture->updateTexture(); + } +} + +ShadowedTextureNode::ShadowedTextureNode() + : ShadowedRectangleNode() +{ + setFlag(QSGNode::UsePreprocess); +} + +void ShadowedTextureNode::setTextureSource(QSGTextureProvider *source) +{ + if (m_textureSource == source) { + return; + } + + if (m_textureSource) { + m_textureSource->disconnect(); + } + + m_textureSource = source; + QObject::connect(m_textureSource.data(), &QSGTextureProvider::textureChanged, [this]() { markDirty(QSGNode::DirtyMaterial); }); + markDirty(QSGNode::DirtyMaterial); +} + +void ShadowedTextureNode::preprocess() +{ + if (m_textureSource && m_material) { + if (m_material->type() == borderlessMaterialType()) { + preprocessTexture(m_material, m_textureSource); + } else { + preprocessTexture(m_material, m_textureSource); + } + } +} + +ShadowedRectangleMaterial *ShadowedTextureNode::createBorderlessMaterial() +{ + return new ShadowedTextureMaterial{}; +} + +ShadowedBorderRectangleMaterial *ShadowedTextureNode::createBorderMaterial() +{ + return new ShadowedBorderTextureMaterial{}; +} + +QSGMaterialType *ShadowedTextureNode::borderlessMaterialType() +{ + return &ShadowedTextureMaterial::staticType; +} + +QSGMaterialType *ShadowedTextureNode::borderMaterialType() +{ + return &ShadowedBorderTextureMaterial::staticType; +} diff --git a/src/shadowedrectangle.h b/src/shadowedrectangle.h --- a/src/shadowedrectangle.h +++ b/src/shadowedrectangle.h @@ -41,6 +41,11 @@ Q_SIGNAL void changed(); + inline bool isEnabled() const + { + return !qFuzzyIsNull(m_width); + } + private: qreal m_width = 0.0; QColor m_color = Qt::black; @@ -110,6 +115,8 @@ * using distance fields, which provide greatly improved performance. The shadow is * rendered outside of the item's bounds, so the item's width and height are the * rectangle's width and height. + * + * @since 5.69 / 2.12 */ class ShadowedRectangle : public QQuickItem { @@ -158,7 +165,7 @@ void componentComplete() override; protected: - void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value); + void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) override; QSGNode *updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *data) override; private: diff --git a/src/shadowedrectangle.cpp b/src/shadowedrectangle.cpp --- a/src/shadowedrectangle.cpp +++ b/src/shadowedrectangle.cpp @@ -175,6 +175,35 @@ checkSoftwareItem(); } +void ShadowedRectangle::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) +{ + if (change == QQuickItem::ItemSceneChange && value.window) { + checkSoftwareItem(); + } +} + +QSGNode *ShadowedRectangle::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *data) +{ + Q_UNUSED(data); + + if (!node) { + node = new ShadowedRectangleNode{}; + } + + auto shadowNode = static_cast(node); + shadowNode->setBorderEnabled(m_border->isEnabled()); + shadowNode->setRect(boundingRect()); + shadowNode->setSize(m_shadow->size()); + shadowNode->setRadius(m_radius); + shadowNode->setOffset(QVector2D{float(m_shadow->xOffset()), float(m_shadow->yOffset())}); + shadowNode->setColor(m_color); + shadowNode->setShadowColor(m_shadow->color()); + shadowNode->setBorderWidth(m_border->width()); + shadowNode->setBorderColor(m_border->color()); + shadowNode->updateGeometry(); + return shadowNode; +} + void ShadowedRectangle::checkSoftwareItem() { if (!m_softwareItem && window() && window()->rendererInterface()->graphicsApi() == QSGRendererInterface::Software) { @@ -202,33 +231,3 @@ setFlag(QQuickItem::ItemHasContents, false); } } - -void ShadowedRectangle::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) -{ - if (change == QQuickItem::ItemSceneChange && value.window) { - checkSoftwareItem(); - } -} - -QSGNode *ShadowedRectangle::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *data) -{ - Q_UNUSED(data); - - if (!node) { - node = new ShadowedRectangleNode; - } - - auto elevatedNode = static_cast(node); - elevatedNode->setBorderEnabled(!qFuzzyIsNull(m_border->width())); - elevatedNode->setRect(boundingRect()); - elevatedNode->setSize(m_shadow->size()); - elevatedNode->setRadius(m_radius); - elevatedNode->setOffset(QVector2D{float(m_shadow->xOffset()), float(m_shadow->yOffset())}); - elevatedNode->setColor(m_color); - elevatedNode->setShadowColor(m_shadow->color()); - elevatedNode->setBorderWidth(m_border->width()); - elevatedNode->setBorderColor(m_border->color()); - elevatedNode->updateGeometry(); - - return elevatedNode; -} diff --git a/src/shadowedtexture.h b/src/shadowedtexture.h new file mode 100644 --- /dev/null +++ b/src/shadowedtexture.h @@ -0,0 +1,43 @@ +/* + * SPDX-FileCopyrightText: 2020 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#pragma once + +#include "shadowedrectangle.h" + +/** + * A rectangle with a shadow, using a QQuickItem as texture. + * + * This item will render a rectangle, with a shadow below it. The rendering is done + * using distance fields, which provide greatly improved performance. The shadow is + * rendered outside of the item's bounds, so the item's width and height are the + * rectangle's width and height. + * + * @since 5.69 / 2.12 + */ +class ShadowedTexture : public ShadowedRectangle +{ + Q_OBJECT + + Q_PROPERTY(QQuickItem *source READ source WRITE setSource NOTIFY sourceChanged) + +public: + ShadowedTexture(QQuickItem *parent = nullptr); + ~ShadowedTexture() override; + + QQuickItem *source() const; + void setSource(QQuickItem *newSource); + Q_SIGNAL void sourceChanged(); + +// void componentComplete() override; + +protected: +// void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) override; + QSGNode *updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *data) override; + +private: + QQuickItem *m_source = nullptr; +}; diff --git a/src/shadowedtexture.cpp b/src/shadowedtexture.cpp new file mode 100644 --- /dev/null +++ b/src/shadowedtexture.cpp @@ -0,0 +1,111 @@ +/* + * SPDX-FileCopyrightText: 2020 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "shadowedtexture.h" + +#include +#include +#include + +#include "scenegraph/shadowedtexturenode.h" + +ShadowedTexture::ShadowedTexture(QQuickItem *parentItem) + : ShadowedRectangle(parentItem) +{ +} + +ShadowedTexture::~ShadowedTexture() +{ +} + +QQuickItem *ShadowedTexture::source() const +{ + return m_source; +} + +void ShadowedTexture::setSource(QQuickItem *newSource) +{ + if (newSource == m_source) { + return; + } + + m_source = newSource; + if (!m_source->parentItem()) { + m_source->setParentItem(this); + } + + update(); + Q_EMIT sourceChanged(); +} + +// void ShadowedRectangle::componentComplete() +// { +// QQuickItem::componentComplete(); +// +// checkSoftwareItem(); +// } +// +// void ShadowedRectangle::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) +// { +// if (change == QQuickItem::ItemSceneChange && value.window) { +// checkSoftwareItem(); +// } +// } + +QSGNode *ShadowedTexture::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *data) +{ + Q_UNUSED(data); + + if (!node) { + node = new ShadowedTextureNode{}; + } + + auto shadowNode = static_cast(node); + shadowNode->setBorderEnabled(border()->isEnabled()); + shadowNode->setRect(boundingRect()); + shadowNode->setSize(shadow()->size()); + shadowNode->setRadius(radius()); + shadowNode->setOffset(QVector2D{float(shadow()->xOffset()), float(shadow()->yOffset())}); + shadowNode->setColor(color()); + shadowNode->setShadowColor(shadow()->color()); + shadowNode->setBorderWidth(border()->width()); + shadowNode->setBorderColor(border()->color()); + + if (m_source) { + shadowNode->setTextureSource(m_source->textureProvider()); + } + + shadowNode->updateGeometry(); + return shadowNode; +} + +// void ShadowedTexture::checkSoftwareItem() +// { +// if (!m_softwareItem && window() && window()->rendererInterface()->graphicsApi() == QSGRendererInterface::Software) { +// m_softwareItem = new PaintedRectangleItem{this}; +// +// auto updateItem = [this]() { +// auto borderWidth = m_border->width(); +// auto rect = boundingRect().adjusted(-borderWidth / 2, -borderWidth / 2, borderWidth / 2, borderWidth / 2); +// m_softwareItem->setX(-borderWidth / 2); +// m_softwareItem->setY(-borderWidth / 2); +// m_softwareItem->setSize(rect.size()); +// m_softwareItem->setColor(m_color); +// m_softwareItem->setRadius(m_radius); +// m_softwareItem->setBorderWidth(borderWidth); +// m_softwareItem->setBorderColor(m_border->color()); +// }; +// +// updateItem(); +// +// connect(this, &ShadowedTexture::widthChanged, m_softwareItem, updateItem); +// connect(this, &ShadowedTexture::heightChanged, m_softwareItem, updateItem); +// connect(this, &ShadowedRectangle::colorChanged, m_softwareItem, updateItem); +// connect(this, &ShadowedRectangle::radiusChanged, m_softwareItem, updateItem); +// connect(m_border.get(), &BorderGroup::changed, m_softwareItem, updateItem); +// setFlag(QQuickItem::ItemHasContents, false); +// } +// } diff --git a/tests/ShadowedImageTest.qml b/tests/ShadowedImageTest.qml new file mode 100644 --- /dev/null +++ b/tests/ShadowedImageTest.qml @@ -0,0 +1,83 @@ +/* + * 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.ShadowedImage { + width: 400 + height: 300 + + color: Kirigami.Theme.highlightColor + + radius: radiusSlider.value + + shadow.size: sizeSlider.value + shadow.xOffset: xOffsetSlider.value + shadow.yOffset: yOffsetSlider.value + + border.width: borderWidthSlider.value + border.color: Kirigami.Theme.textColor + + source: "/usr/share/wallpapers/Next/contents/images/1024x768.jpg" + } + + 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 + } + + Slider { + id: borderWidthSlider + + from: 0 + to: 50 + } + } + } +}