diff --git a/src/controls/ShadowedImage.qml b/src/controls/ShadowedImage.qml index 72353e1d..6a692169 100644 --- a/src/controls/ShadowedImage.qml +++ b/src/controls/ShadowedImage.qml @@ -1,26 +1,27 @@ /* * 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 corners: shadowRectangle.corners 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 index 50a19ea9..50501432 100644 --- a/src/kirigamiplugin.cpp +++ b/src/kirigamiplugin.cpp @@ -1,257 +1,259 @@ /* * SPDX-FileCopyrightText: 2009 Alan Alpert * SPDX-FileCopyrightText: 2010 Ménard Alexis * SPDX-FileCopyrightText: 2010 Marco Martin * * SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kirigamiplugin.h" #include "columnview.h" #include "enums.h" #include "icon.h" #include "settings.h" #include "formlayoutattached.h" #include "mnemonicattached.h" #include "delegaterecycler.h" #include "pagepool.h" #include "scenepositionattached.h" #include "wheelhandler.h" #include "shadowedrectangle.h" #include "shadowedtexture.h" #include #include #include #include #include #include #include "libkirigami/platformtheme.h" static QString s_selectedStyle; //Q_INIT_RESOURCE(kirigami); #ifdef KIRIGAMI_BUILD_TYPE_STATIC #include #endif class CopyHelperPrivate : public QObject { Q_OBJECT public: Q_INVOKABLE static void copyTextToClipboard(const QString& text) { qGuiApp->clipboard()->setText(text); } }; // we can't do this in the plugin object directly, as that can live in a different thread // and event filters are only allowed in the same thread as the filtered object class LanguageChangeEventFilter : public QObject { Q_OBJECT public: bool eventFilter(QObject *receiver, QEvent *event) override { if (event->type() == QEvent::LanguageChange && receiver == QCoreApplication::instance()) { emit languageChangeEvent(); } return QObject::eventFilter(receiver, event); } Q_SIGNALS: void languageChangeEvent(); }; KirigamiPlugin::KirigamiPlugin(QObject *parent) : QQmlExtensionPlugin(parent) { auto filter = new LanguageChangeEventFilter; filter->moveToThread(QCoreApplication::instance()->thread()); QCoreApplication::instance()->installEventFilter(filter); connect(filter, &LanguageChangeEventFilter::languageChangeEvent, this, &KirigamiPlugin::languageChangeEvent); } QUrl KirigamiPlugin::componentUrl(const QString &fileName) const { for (const QString &style : qAsConst(m_stylesFallbackChain)) { const QString candidate = QStringLiteral("styles/") + style + QLatin1Char('/') + fileName; if (QFile::exists(resolveFilePath(candidate))) { #ifdef KIRIGAMI_BUILD_TYPE_STATIC return QUrl(QStringLiteral("qrc:/org/kde/kirigami/styles/") + style + QLatin1Char('/') + fileName); #else return QUrl(resolveFileUrl(candidate)); #endif } } #ifdef KIRIGAMI_BUILD_TYPE_STATIC return QUrl(QStringLiteral("qrc:/org/kde/kirigami/") + fileName); #else return QUrl(resolveFileUrl(fileName)); #endif } void KirigamiPlugin::registerTypes(const char *uri) { #if defined(Q_OS_ANDROID) && QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QResource::registerResource(QStringLiteral("assets:/android_rcc_bundle.rcc")); #endif Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.kirigami")); const QString style = QQuickStyle::name(); if (QIcon::themeName().isEmpty() && !qEnvironmentVariableIsSet("XDG_CURRENT_DESKTOP")) { QIcon::setThemeSearchPaths({resolveFilePath(QStringLiteral(".")), QStringLiteral(":/icons")}); QIcon::setThemeName(QStringLiteral("breeze-internal")); } #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) //org.kde.desktop.plasma is a couple of files that fall back to desktop by purpose if ((style.isEmpty() || style == QStringLiteral("org.kde.desktop.plasma")) && QFile::exists(resolveFilePath(QStringLiteral("/styles/org.kde.desktop")))) { m_stylesFallbackChain.prepend(QStringLiteral("org.kde.desktop")); } #elif defined(Q_OS_ANDROID) if (!m_stylesFallbackChain.contains(QLatin1String("Material"))) { m_stylesFallbackChain.prepend(QStringLiteral("Material")); } #else // do we have an iOS specific style? if (!m_stylesFallbackChain.contains(QLatin1String("Material"))) { m_stylesFallbackChain.prepend(QStringLiteral("Material")); } #endif if (!style.isEmpty() && QFile::exists(resolveFilePath(QStringLiteral("/styles/") + style)) && !m_stylesFallbackChain.contains(style)) { m_stylesFallbackChain.prepend(style); //if we have plasma deps installed, use them for extra integration if (style == QStringLiteral("org.kde.desktop") && QFile::exists(resolveFilePath(QStringLiteral("/styles/org.kde.desktop.plasma")))) { m_stylesFallbackChain.prepend(QStringLiteral("org.kde.desktop.plasma")); } } else { #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) m_stylesFallbackChain.prepend(QStringLiteral("org.kde.desktop")); #endif } //At this point the fallback chain will be selected->org.kde.desktop->Fallback s_selectedStyle = m_stylesFallbackChain.first(); qmlRegisterSingletonType(uri, 2, 0, "Settings", [](QQmlEngine *e, QJSEngine*) -> QObject* { Settings *settings = Settings::self(); //singleton managed internally, qml should never delete it e->setObjectOwnership(settings, QQmlEngine::CppOwnership); settings->setStyle(s_selectedStyle); return settings; } ); qmlRegisterUncreatableType(uri, 2, 0, "ApplicationHeaderStyle", QStringLiteral("Cannot create objects of type ApplicationHeaderStyle")); //old legacy retrocompatible Theme qmlRegisterSingletonType(componentUrl(QStringLiteral("Theme.qml")), uri, 2, 0, "Theme"); qmlRegisterSingletonType(componentUrl(QStringLiteral("Units.qml")), uri, 2, 0, "Units"); qmlRegisterType(componentUrl(QStringLiteral("Action.qml")), uri, 2, 0, "Action"); qmlRegisterType(componentUrl(QStringLiteral("AbstractApplicationHeader.qml")), uri, 2, 0, "AbstractApplicationHeader"); qmlRegisterType(componentUrl(QStringLiteral("AbstractApplicationWindow.qml")), uri, 2, 0, "AbstractApplicationWindow"); qmlRegisterType(componentUrl(QStringLiteral("AbstractListItem.qml")), uri, 2, 0, "AbstractListItem"); qmlRegisterType(componentUrl(QStringLiteral("ApplicationHeader.qml")), uri, 2, 0, "ApplicationHeader"); qmlRegisterType(componentUrl(QStringLiteral("ToolBarApplicationHeader.qml")), uri, 2, 0, "ToolBarApplicationHeader"); qmlRegisterType(componentUrl(QStringLiteral("ApplicationWindow.qml")), uri, 2, 0, "ApplicationWindow"); qmlRegisterType(componentUrl(QStringLiteral("BasicListItem.qml")), uri, 2, 0, "BasicListItem"); qmlRegisterType(componentUrl(QStringLiteral("OverlayDrawer.qml")), uri, 2, 0, "OverlayDrawer"); qmlRegisterType(componentUrl(QStringLiteral("ContextDrawer.qml")), uri, 2, 0, "ContextDrawer"); qmlRegisterType(componentUrl(QStringLiteral("GlobalDrawer.qml")), uri, 2, 0, "GlobalDrawer"); qmlRegisterType(componentUrl(QStringLiteral("Heading.qml")), uri, 2, 0, "Heading"); qmlRegisterType(componentUrl(QStringLiteral("Separator.qml")), uri, 2, 0, "Separator"); qmlRegisterType(componentUrl(QStringLiteral("PageRow.qml")), uri, 2, 0, "PageRow"); qmlRegisterType(uri, 2, 0, "Icon"); qmlRegisterType(componentUrl(QStringLiteral("Label.qml")), uri, 2, 0, "Label"); //TODO: uncomment for 2.3 release //qmlRegisterTypeNotAvailable(uri, 2, 3, "Label", "Label type not supported anymore, use QtQuick.Controls.Label 2.0 instead"); qmlRegisterType(componentUrl(QStringLiteral("OverlaySheet.qml")), uri, 2, 0, "OverlaySheet"); qmlRegisterType(componentUrl(QStringLiteral("Page.qml")), uri, 2, 0, "Page"); qmlRegisterType(componentUrl(QStringLiteral("ScrollablePage.qml")), uri, 2, 0, "ScrollablePage"); qmlRegisterType(componentUrl(QStringLiteral("SplitDrawer.qml")), uri, 2, 0, "SplitDrawer"); qmlRegisterType(componentUrl(QStringLiteral("SwipeListItem.qml")), uri, 2, 0, "SwipeListItem"); //2.1 qmlRegisterType(componentUrl(QStringLiteral("AbstractItemViewHeader.qml")), uri, 2, 1, "AbstractItemViewHeader"); qmlRegisterType(componentUrl(QStringLiteral("ItemViewHeader.qml")), uri, 2, 1, "ItemViewHeader"); qmlRegisterType(componentUrl(QStringLiteral("AbstractApplicationItem.qml")), uri, 2, 1, "AbstractApplicationItem"); qmlRegisterType(componentUrl(QStringLiteral("ApplicationItem.qml")), uri, 2, 1, "ApplicationItem"); //2.2 //Theme changed from a singleton to an attached property qmlRegisterUncreatableType(uri, 2, 2, "Theme", QStringLiteral("Cannot create objects of type Theme, use it as an attached property")); //2.3 qmlRegisterType(componentUrl(QStringLiteral("FormLayout.qml")), uri, 2, 3, "FormLayout"); qmlRegisterUncreatableType(uri, 2, 3, "FormData", QStringLiteral("Cannot create objects of type FormData, use it as an attached property")); qmlRegisterUncreatableType(uri, 2, 3, "MnemonicData", QStringLiteral("Cannot create objects of type MnemonicData, use it as an attached property")); //2.4 qmlRegisterType(componentUrl(QStringLiteral("AbstractCard.qml")), uri, 2, 4, "AbstractCard"); qmlRegisterType(componentUrl(QStringLiteral("Card.qml")), uri, 2, 4, "Card"); qmlRegisterType(componentUrl(QStringLiteral("CardsListView.qml")), uri, 2, 4, "CardsListView"); qmlRegisterType(componentUrl(QStringLiteral("CardsGridView.qml")), uri, 2, 4, "CardsGridView"); qmlRegisterType(componentUrl(QStringLiteral("CardsLayout.qml")), uri, 2, 4, "CardsLayout"); qmlRegisterType(componentUrl(QStringLiteral("InlineMessage.qml")), uri, 2, 4, "InlineMessage"); qmlRegisterUncreatableType(uri, 2, 4, "MessageType", QStringLiteral("Cannot create objects of type MessageType")); qmlRegisterType(uri, 2, 4, "DelegateRecycler"); //2.5 qmlRegisterType(componentUrl(QStringLiteral("ListItemDragHandle.qml")), uri, 2, 5, "ListItemDragHandle"); qmlRegisterType(componentUrl(QStringLiteral("ActionToolBar.qml")), uri, 2, 5, "ActionToolBar"); qmlRegisterUncreatableType(uri, 2, 5, "ScenePosition", QStringLiteral("Cannot create objects of type ScenePosition, use it as an attached property")); //2.6 qmlRegisterType(componentUrl(QStringLiteral("AboutPage.qml")), uri, 2, 6, "AboutPage"); qmlRegisterType(componentUrl(QStringLiteral("LinkButton.qml")), uri, 2, 6, "LinkButton"); qmlRegisterType(componentUrl(QStringLiteral("UrlButton.qml")), uri, 2, 6, "UrlButton"); qmlRegisterSingletonType("org.kde.kirigami.private", 2, 6, "CopyHelperPrivate", [] (QQmlEngine*, QJSEngine*) -> QObject* { return new CopyHelperPrivate; }); //2.7 qmlRegisterType(uri, 2, 7, "ColumnView"); qmlRegisterType(componentUrl(QStringLiteral("ActionTextField.qml")), uri, 2, 7, "ActionTextField"); //2.8 qmlRegisterType(componentUrl(QStringLiteral("SearchField.qml")), uri, 2, 8, "SearchField"); qmlRegisterType(componentUrl(QStringLiteral("PasswordField.qml")), uri, 2, 8, "PasswordField"); //2.9 qmlRegisterType(uri, 2, 9, "WheelHandler"); qmlRegisterUncreatableType(uri, 2, 9, "WheelEvent", QStringLiteral("Cannot create objects of type WheelEvent.")); //2.10 qmlRegisterType(componentUrl(QStringLiteral("ListSectionHeader.qml")), uri, 2, 10, "ListSectionHeader"); // 2.11 qmlRegisterType(uri, 2, 11, "PagePool"); qmlRegisterType(componentUrl(QStringLiteral("PagePoolAction.qml")), uri, 2, 11, "PagePoolAction"); //TODO: remove qmlRegisterType(componentUrl(QStringLiteral("SwipeListItem2.qml")), uri, 2, 11, "SwipeListItem2"); // 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")); + qmlRegisterUncreatableType(uri, 2, 12, "CornersGroup", QStringLiteral("Used as grouped property")); + qmlProtectModule(uri, 2); } void KirigamiPlugin::initializeEngine(QQmlEngine *engine, const char *uri) { Q_UNUSED(uri); connect(this, &KirigamiPlugin::languageChangeEvent, engine, &QQmlEngine::retranslate); } #include "kirigamiplugin.moc" diff --git a/src/scenegraph/shaders/shadowedborderrectangle.frag b/src/scenegraph/shaders/shadowedborderrectangle.frag index 9146a446..e860fdfe 100644 --- a/src/scenegraph/shaders/shadowedborderrectangle.frag +++ b/src/scenegraph/shaders/shadowedborderrectangle.frag @@ -1,78 +1,78 @@ /* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ // 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. uniform lowp float opacity; uniform lowp float size; -uniform lowp float radius; +uniform lowp vec4 radius; uniform lowp vec4 color; uniform lowp vec4 shadowColor; uniform lowp vec2 offset; uniform lowp vec2 aspect; 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; 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 shadow_radius = radius + size * size_factor; + lowp vec4 size_factor = 0.5 * (minimum_shadow_radius / max(radius, minimum_shadow_radius)); + lowp vec4 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 - offset * 2.0 * inverse_scale, aspect * inverse_scale, vec4(shadow_radius * inverse_scale)); + lowp float shadow = sdf_rounded_rectangle(uv - offset * 2.0 * inverse_scale, aspect * inverse_scale, 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); + lowp vec4 corner_radius = radius * inverse_scale; // Calculate the outer rectangle distance field. 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)); // Then, render it again but this time with the proper color and properly alpha blended. 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 vec4 inner_radius = (radius - borderWidth * 2.0) * inverse_scale; 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)); // Finally, render the inner rectangle. 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/shaders/shadowedbordertexture.frag b/src/scenegraph/shaders/shadowedbordertexture.frag index dbd78a4c..1d34ac74 100644 --- a/src/scenegraph/shaders/shadowedbordertexture.frag +++ b/src/scenegraph/shaders/shadowedbordertexture.frag @@ -1,96 +1,97 @@ /* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ // 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. uniform lowp float opacity; uniform lowp float size; -uniform lowp float radius; +uniform lowp vec4 radius; uniform lowp vec4 color; uniform lowp vec4 shadowColor; uniform lowp vec2 offset; 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; // Tiny abstraction around texture() to deal with Core profile differences. lowp vec4 sample_texture(in sampler2D texture_source, in lowp vec2 uv) { #ifdef CORE_PROFILE return texture(texture_source, uv); #else return texture2D(texture_source, uv); #endif } 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 shadow_radius = radius + size * size_factor; + lowp vec4 size_factor = 0.5 * (minimum_shadow_radius / max(radius, minimum_shadow_radius)); + lowp vec4 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 - offset * 2.0 * inverse_scale, aspect * inverse_scale, vec4(shadow_radius * inverse_scale)); + lowp float shadow = sdf_rounded_rectangle(uv - offset * 2.0 * inverse_scale, aspect * inverse_scale, 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); + lowp vec4 corner_radius = radius * inverse_scale; // Calculate the outer rectangle distance field. 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)); // Then, render it again but this time with the proper color and properly alpha blended. 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 vec4 inner_radius = (radius - borderWidth * 2.0) * inverse_scale; 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)); // Finally, render the inner rectangle. col = sdf_render(inner_rect, col, color); + // Slightly increase the size of the inner rectangle, to avoid issues with anti-aliasing. 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/shaders/shadowedrectangle.frag b/src/scenegraph/shaders/shadowedrectangle.frag index d56fd737..943a8ef5 100644 --- a/src/scenegraph/shaders/shadowedrectangle.frag +++ b/src/scenegraph/shaders/shadowedrectangle.frag @@ -1,61 +1,61 @@ /* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ // See sdf.glsl for the SDF related functions. // This shader renders 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 radius; uniform lowp vec4 color; uniform lowp vec4 shadowColor; 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; 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 shadow_radius = radius + size * size_factor; + lowp vec4 size_factor = 0.5 * (minimum_shadow_radius / max(radius, minimum_shadow_radius)); + lowp vec4 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 - offset * 2.0 * inverse_scale, aspect * inverse_scale, vec4(shadow_radius * inverse_scale)); + lowp float shadow = sdf_rounded_rectangle(uv - offset * 2.0 * inverse_scale, aspect * inverse_scale, 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, vec4(radius * inverse_scale)); + lowp float rect = sdf_rounded_rectangle(uv, aspect * inverse_scale, 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 = sdf_render(rect, col, vec4(0.0)); // Then, render it again but this time with the proper color and properly alpha blended. col = sdf_render(rect, col, color); #ifdef CORE_PROFILE out_color = col * opacity; #else gl_FragColor = col * opacity; #endif } diff --git a/src/scenegraph/shaders/shadowedtexture.frag b/src/scenegraph/shaders/shadowedtexture.frag index b3009aae..d0cb9b72 100644 --- a/src/scenegraph/shaders/shadowedtexture.frag +++ b/src/scenegraph/shaders/shadowedtexture.frag @@ -1,79 +1,78 @@ /* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ // See sdf.glsl for the SDF related functions. // 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 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; // Tiny abstraction around texture() to deal with Core profile differences. lowp vec4 sample_texture(in sampler2D texture_source, in lowp vec2 uv) { #ifdef CORE_PROFILE return texture(texture_source, uv); #else return texture2D(texture_source, uv); #endif } 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 shadow_radius = radius + size * size_factor; + lowp vec4 size_factor = 0.5 * (minimum_shadow_radius / max(radius, minimum_shadow_radius)); + lowp vec4 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 - offset * 2.0 * inverse_scale, aspect * inverse_scale, vec4(shadow_radius * inverse_scale)); + lowp float shadow = sdf_rounded_rectangle(uv - offset * 2.0 * inverse_scale, aspect * inverse_scale, 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, vec4(radius * inverse_scale)); + lowp float rect = sdf_rounded_rectangle(uv, aspect * inverse_scale, 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 = sdf_render(rect, col, vec4(0.0)); // Then, render it again but this time with the proper color. col = sdf_render(rect, col, color); // 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/shadowedrectanglematerial.h b/src/scenegraph/shadowedrectanglematerial.h index e362b454..a02cc7d0 100644 --- a/src/scenegraph/shadowedrectanglematerial.h +++ b/src/scenegraph/shadowedrectanglematerial.h @@ -1,57 +1,57 @@ /* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ #pragma once #include #include #include /** * 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 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; + QVector4D radius = QVector4D{0.0, 0.0, 0.0, 0.0}; QColor color = Qt::white; QColor shadowColor = Qt::black; QVector2D offset; static QSGMaterialType staticType; }; class ShadowedRectangleShader : public QSGMaterialShader { public: ShadowedRectangleShader(); 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/shadowedrectanglenode.cpp b/src/scenegraph/shadowedrectanglenode.cpp index 9a935f64..b9d31ab6 100644 --- a/src/scenegraph/shadowedrectanglenode.cpp +++ b/src/scenegraph/shadowedrectanglenode.cpp @@ -1,194 +1,199 @@ /* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ #include "shadowedrectanglenode.h" #include "shadowedrectanglematerial.h" #include "shadowedborderrectanglematerial.h" QColor premultiply(const QColor &color) { return QColor::fromRgbF( color.redF() * color.alphaF(), color.greenF() * color.alphaF(), color.blueF() * color.alphaF(), color.alphaF() ); } ShadowedRectangleNode::ShadowedRectangleNode() { m_geometry = new QSGGeometry{QSGGeometry::defaultAttributes_TexturedPoint2D(), 4}; setGeometry(m_geometry); setFlags(QSGNode::OwnsGeometry | QSGNode::OwnsMaterial); } void ShadowedRectangleNode::setBorderEnabled(bool enabled) { // We can achieve more performant shaders by splitting the two into separate // shaders. This requires separating the materials as well. So when // borderWidth is increased to something where the border should be visible, // switch to the with-border material. Otherwise use the no-border version. if (enabled) { 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 || m_material->type() == borderMaterialType()) { auto newMaterial = createBorderlessMaterial(); setMaterial(newMaterial); m_material = newMaterial; m_rect = QRectF{}; markDirty(QSGNode::DirtyMaterial); } } } 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); +void ShadowedRectangleNode::setRadius(const QVector4D &radius) +{ + float minDimension = std::min(m_rect.width(), m_rect.height()); + auto uniformRadius = QVector4D{ + std::min(radius.x() * 2.0f / minDimension, 1.0f), + std::min(radius.y() * 2.0f / minDimension, 1.0f), + std::min(radius.z() * 2.0f / minDimension, 1.0f), + std::min(radius.w() * 2.0f / minDimension, 1.0f) + }; + + if (m_material->radius != uniformRadius) { + m_material->radius = uniformRadius; markDirty(QSGNode::DirtyMaterial); m_radius = radius; } } void ShadowedRectangleNode::setColor(const QColor &color) { auto premultiplied = premultiply(color); if (m_material->color != premultiplied) { m_material->color = premultiplied; markDirty(QSGNode::DirtyMaterial); } } void ShadowedRectangleNode::setShadowColor(const QColor& color) { auto premultiplied = premultiply(color); if (m_material->shadowColor != premultiplied) { m_material->shadowColor = premultiplied; 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::setBorderWidth(qreal width) { if (m_material->type() != borderMaterialType()) { return; } auto minDimension = std::min(m_rect.width(), m_rect.height()); float uniformBorderWidth = width / minDimension; auto borderMaterial = static_cast(m_material); if (!qFuzzyCompare(borderMaterial->borderWidth, uniformBorderWidth)) { borderMaterial->borderWidth = uniformBorderWidth; markDirty(QSGNode::DirtyMaterial); m_borderWidth = width; } } void ShadowedRectangleNode::setBorderColor(const QColor& color) { if (m_material->type() != borderMaterialType()) { return; } auto borderMaterial = static_cast(m_material); auto premultiplied = premultiply(color); if (borderMaterial->borderColor != premultiplied) { borderMaterial->borderColor = premultiplied; markDirty(QSGNode::DirtyMaterial); } } 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); } 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/shadowedrectanglenode.h b/src/scenegraph/shadowedrectanglenode.h index 301200b1..7b23d0e4 100644 --- a/src/scenegraph/shadowedrectanglenode.h +++ b/src/scenegraph/shadowedrectanglenode.h @@ -1,75 +1,76 @@ /* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ #pragma once #include #include #include +#include class QSGMaterialType; class ShadowedRectangleMaterial; class ShadowedBorderRectangleMaterial; /** * Scene graph node for a shadowed rectangle. * * This node will set up the geometry and materials for a shadowed rectangle, * optionally with rounded corners. * * \note You must call updateGeometry() after setting properties of this node, * otherwise the node's state will not correctly reflect all the properties. * * \sa ShadowedRectangle */ class ShadowedRectangleNode : public QSGGeometryNode { public: ShadowedRectangleNode(); /** * Set whether to draw a border. * * Note that this will switch between a material with or without border. * This means this needs to be called before any other setters. */ void setBorderEnabled(bool enabled); void setRect(const QRectF &rect); void setSize(qreal size); - void setRadius(qreal radius); + void setRadius(const QVector4D &radius); void setColor(const QColor &color); void setShadowColor(const QColor &color); void setOffset(const QVector2D &offset); void setBorderWidth(qreal width); void setBorderColor(const QColor &color); /** * Update the geometry for this node. * * This is done as an explicit step to avoid the geometry being recreated * multiple times while updating properties. */ void updateGeometry(); protected: virtual ShadowedRectangleMaterial *createBorderlessMaterial(); virtual ShadowedBorderRectangleMaterial *createBorderMaterial(); virtual QSGMaterialType* borderMaterialType(); virtual QSGMaterialType* borderlessMaterialType(); QSGGeometry *m_geometry; ShadowedRectangleMaterial *m_material = nullptr; private: QRectF m_rect; qreal m_size = 0.0; - qreal m_radius = 0.0; + QVector4D m_radius = QVector4D{0.0, 0.0, 0.0, 0.0}; QVector2D m_offset = QVector2D{0.0, 0.0}; QVector2D m_aspect = QVector2D{1.0, 1.0}; qreal m_borderWidth = 0.0; QColor m_borderColor; }; diff --git a/src/shadowedrectangle.cpp b/src/shadowedrectangle.cpp index 66145a33..022a350c 100644 --- a/src/shadowedrectangle.cpp +++ b/src/shadowedrectangle.cpp @@ -1,233 +1,317 @@ /* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ #include "shadowedrectangle.h" #include #include #include #include "scenegraph/shadowedrectanglenode.h" #include "scenegraph/paintedrectangleitem.h" BorderGroup::BorderGroup(QObject* parent) : QObject(parent) { } qreal BorderGroup::width() const { return m_width; } void BorderGroup::setWidth(qreal newWidth) { if (newWidth == m_width) { return; } m_width = newWidth; Q_EMIT changed(); } QColor BorderGroup::color() const { return m_color; } void BorderGroup::setColor(const QColor & newColor) { if (newColor == m_color) { return; } m_color = newColor; Q_EMIT changed(); } ShadowGroup::ShadowGroup(QObject *parent) : QObject(parent) { } qreal ShadowGroup::size() const { return m_size; } void ShadowGroup::setSize(qreal newSize) { if (newSize == m_size) { return; } m_size = newSize; Q_EMIT changed(); } qreal ShadowGroup::xOffset() const { return m_xOffset; } void ShadowGroup::setXOffset(qreal newXOffset) { if (newXOffset == m_xOffset) { return; } m_xOffset = newXOffset; Q_EMIT changed(); } qreal ShadowGroup::yOffset() const { return m_yOffset; } void ShadowGroup::setYOffset(qreal newYOffset) { if (newYOffset == m_yOffset) { return; } m_yOffset = newYOffset; Q_EMIT changed(); } QColor ShadowGroup::color() const { return m_color; } void ShadowGroup::setColor(const QColor & newColor) { if (newColor == m_color) { return; } m_color = newColor; Q_EMIT changed(); } +CornersGroup::CornersGroup(QObject* parent) + : QObject(parent) +{ +} + +qreal CornersGroup::topLeft() const +{ + return m_topLeft; +} + +void CornersGroup::setTopLeft(qreal newTopLeft) +{ + if (newTopLeft == m_topLeft) { + return; + } + + m_topLeft = newTopLeft; + Q_EMIT changed(); +} + +qreal CornersGroup::topRight() const +{ + return m_topRight; +} + +void CornersGroup::setTopRight(qreal newTopRight) +{ + if (newTopRight == m_topRight) { + return; + } + + m_topRight = newTopRight; + Q_EMIT changed(); +} + +qreal CornersGroup::bottomLeft() const +{ + return m_bottomLeft; +} + +void CornersGroup::setBottomLeft(qreal newBottomLeft) +{ + if (newBottomLeft == m_bottomLeft) { + return; + } + + m_bottomLeft = newBottomLeft; + Q_EMIT changed(); +} + +qreal CornersGroup::bottomRight() const +{ + return m_bottomRight; +} + +void CornersGroup::setBottomRight(qreal newBottomRight) +{ + if (newBottomRight == m_bottomRight) { + return; + } + + m_bottomRight = newBottomRight; + Q_EMIT changed(); +} + +QVector4D CornersGroup::toVector4D(float all) const +{ + return QVector4D{ + m_bottomRight < 0.0 ? all : m_bottomRight, + m_topRight < 0.0 ? all : m_topRight, + m_bottomLeft < 0.0 ? all : m_bottomLeft, + m_topLeft < 0.0 ? all : m_topLeft + }; +} + ShadowedRectangle::ShadowedRectangle(QQuickItem *parentItem) - : QQuickItem(parentItem), m_border(new BorderGroup), m_shadow(new ShadowGroup) + : QQuickItem(parentItem) + , m_border(new BorderGroup) + , m_shadow(new ShadowGroup) + , m_corners(new CornersGroup) { setFlag(QQuickItem::ItemHasContents, true); connect(m_border.get(), &BorderGroup::changed, this, &ShadowedRectangle::update); connect(m_shadow.get(), &ShadowGroup::changed, this, &ShadowedRectangle::update); + connect(m_corners.get(), &CornersGroup::changed, this, &ShadowedRectangle::update); } ShadowedRectangle::~ShadowedRectangle() { } BorderGroup *ShadowedRectangle::border() const { return m_border.get(); } ShadowGroup *ShadowedRectangle::shadow() const { return m_shadow.get(); } +CornersGroup *ShadowedRectangle::corners() const +{ + return m_corners.get(); +} + qreal ShadowedRectangle::radius() const { return m_radius; } void ShadowedRectangle::setRadius(qreal newRadius) { if (newRadius == m_radius) { return; } m_radius = newRadius; update(); Q_EMIT radiusChanged(); } QColor ShadowedRectangle::color() const { return m_color; } void ShadowedRectangle::setColor(const QColor & newColor) { if (newColor == m_color) { return; } m_color = newColor; update(); Q_EMIT colorChanged(); } void ShadowedRectangle::componentComplete() { QQuickItem::componentComplete(); 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->setRadius(m_corners->toVector4D(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) { 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, &ShadowedRectangle::widthChanged, m_softwareItem, updateItem); connect(this, &ShadowedRectangle::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/src/shadowedrectangle.h b/src/shadowedrectangle.h index 7e7dec14..0e7d9a2e 100644 --- a/src/shadowedrectangle.h +++ b/src/shadowedrectangle.h @@ -1,178 +1,249 @@ /* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ #pragma once #include #include class PaintedRectangleItem; /** * Grouped property for rectangle border. */ class BorderGroup : public QObject { Q_OBJECT /** * The width of the border in pixels. * * Default is 0. */ Q_PROPERTY(qreal width READ width WRITE setWidth NOTIFY changed) /** * The color of the border. * * Full RGBA colors are supported. The default is fully opaque black. */ Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY changed) public: explicit BorderGroup(QObject *parent = nullptr); qreal width() const; void setWidth(qreal newWidth); QColor color() const; void setColor(const QColor &newColor); Q_SIGNAL void changed(); inline bool isEnabled() const { return !qFuzzyIsNull(m_width); } private: qreal m_width = 0.0; QColor m_color = Qt::black; }; /** * Grouped property for rectangle shadow. */ class ShadowGroup : public QObject { Q_OBJECT /** * The size of the shadow. * * This is the approximate size of the shadow in pixels. However, due to falloff * the actual shadow size can differ. The default is 0, which means no shadow will * be rendered. */ Q_PROPERTY(qreal size READ size WRITE setSize NOTIFY changed) /** * Offset of the shadow on the X axis. * * In pixels. The default is 0. */ Q_PROPERTY(qreal xOffset READ xOffset WRITE setXOffset NOTIFY changed) /** * Offset of the shadow on the Y axis. * * In pixels. The default is 0. */ Q_PROPERTY(qreal yOffset READ yOffset WRITE setYOffset NOTIFY changed) /** * The color of the shadow. * * Full RGBA colors are supported. The default is fully opaque black. */ Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY changed) public: explicit ShadowGroup(QObject *parent = nullptr); qreal size() const; void setSize(qreal newSize); qreal xOffset() const; void setXOffset(qreal newXOffset); qreal yOffset() const; void setYOffset(qreal newYOffset); QColor color() const; void setColor(const QColor &newShadowColor); Q_SIGNAL void changed(); private: qreal m_size = 0.0; qreal m_xOffset = 0.0; qreal m_yOffset = 0.0; QColor m_color = Qt::black; }; +/** + * Grouped property for corner radius. + */ +class CornersGroup : public QObject +{ + Q_OBJECT + /** + * The radius of the top-left corner. + * + * In pixels. Defaults to -1, which indicates this value should not be used. + */ + Q_PROPERTY(qreal topLeftRadius READ topLeft WRITE setTopLeft NOTIFY changed) + /** + * The radius of the top-right corner. + * + * In pixels. Defaults to -1, which indicates this value should not be used. + */ + Q_PROPERTY(qreal topRightRadius READ topRight WRITE setTopRight NOTIFY changed) + /** + * The radius of the bottom-left corner. + * + * In pixels. Defaults to -1, which indicates this value should not be used. + */ + Q_PROPERTY(qreal bottomLeftRadius READ bottomLeft WRITE setBottomLeft NOTIFY changed) + /** + * The radius of the bottom-right corner. + * + * In pixels. Defaults to -1, which indicates this value should not be used. + */ + Q_PROPERTY(qreal bottomRightRadius READ bottomRight WRITE setBottomRight NOTIFY changed) + +public: + explicit CornersGroup(QObject *parent = nullptr); + + qreal topLeft() const; + void setTopLeft(qreal newTopLeft); + + qreal topRight() const; + void setTopRight(qreal newTopRight); + + qreal bottomLeft() const; + void setBottomLeft(qreal newBottomLeft); + + qreal bottomRight() const; + void setBottomRight(qreal newBottomRight); + + Q_SIGNAL void changed(); + + QVector4D toVector4D(float all) const; + +private: + float m_topLeft = -1.0; + float m_topRight = -1.0; + float m_bottomLeft = -1.0; + float m_bottomRight = -1.0; +}; + /** * A rectangle with a shadow. * * 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 ShadowedRectangle : public QQuickItem { Q_OBJECT /** * Corner radius of the rectangle. * - * This is the amount of rounding to apply to the rectangle's corners, in pixels. + * This is the amount of rounding to apply to all of the rectangle's + * corners, in pixels. Individual corners can have a different radius, see + * \property corners. + * * The default is 0. */ Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged) /** * The color of the rectangle. * * Full RGBA colors are supported. The default is fully opaque white. */ Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) /** * Border properties. * * \sa BorderGroup */ Q_PROPERTY(BorderGroup *border READ border CONSTANT) /** * Shadow properties. * * \sa ShadowGroup */ Q_PROPERTY(ShadowGroup *shadow READ shadow CONSTANT) + /** + * Corner radius. + * + * Note that the values from this group override \property radius for the + * corner they affect. + * + * \sa CornerGroup + */ + Q_PROPERTY(CornersGroup *corners READ corners CONSTANT) public: ShadowedRectangle(QQuickItem *parent = nullptr); ~ShadowedRectangle() override; BorderGroup *border() const; ShadowGroup *shadow() const; + CornersGroup *corners() const; qreal radius() const; void setRadius(qreal newRadius); Q_SIGNAL void radiusChanged(); QColor color() const; void setColor(const QColor &newColor); Q_SIGNAL void colorChanged(); void componentComplete() override; protected: void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) override; QSGNode *updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *data) override; private: void checkSoftwareItem(); const std::unique_ptr m_border; const std::unique_ptr m_shadow; + const std::unique_ptr m_corners; qreal m_radius = 0.0; QColor m_color = Qt::white; PaintedRectangleItem *m_softwareItem = nullptr; }; diff --git a/src/shadowedtexture.cpp b/src/shadowedtexture.cpp index 17d9ef93..96715325 100644 --- a/src/shadowedtexture.cpp +++ b/src/shadowedtexture.cpp @@ -1,111 +1,97 @@ /* * 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->setRadius(corners()->toVector4D(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/src/shadowedtexture.h b/src/shadowedtexture.h index 071871c2..da454d6b 100644 --- a/src/shadowedtexture.h +++ b/src/shadowedtexture.h @@ -1,43 +1,40 @@ /* * 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/tests/ShadowedImageTest.qml b/tests/ShadowedImageTest.qml index c4674a5d..76b6f948 100644 --- a/tests/ShadowedImageTest.qml +++ b/tests/ShadowedImageTest.qml @@ -1,83 +1,67 @@ /* * 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 + width: 600 + height: 800 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 + source: "/usr/share/wallpapers/Next/contents/images/1024x768.jpg" + 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 + corners.topLeftRadius: topLeftSlider.value + corners.topRightRadius: topRightSlider.value + corners.bottomLeftRadius: bottomLeftSlider.value + corners.bottomRightRadius: bottomRightSlider.value } - Slider { - id: radiusSlider + Kirigami.FormLayout { + Item { Kirigami.FormData.isSection: true } - from: 0 - to: 200 - } - - Slider { - id: xOffsetSlider - - from: -100 - to: 100 - } - - Slider { - id: yOffsetSlider - - from: -100 - to: 100 - } + Slider { id: radiusSlider; from: 0; to: 200; Kirigami.FormData.label: "Overall Radius" } + Slider { id: topLeftSlider; from: -1; to: 200; value: -1; Kirigami.FormData.label: "Top Left Radius" } + Slider { id: topRightSlider; from: -1; to: 200; value: -1; Kirigami.FormData.label: "Top Right Radius" } + Slider { id: bottomLeftSlider; from: -1; to: 200; value: -1; Kirigami.FormData.label: "Bottom Left Radius" } + Slider { id: bottomRightSlider; from: -1; to: 200; value: -1; Kirigami.FormData.label: "Bottom Right Radius" } - Slider { - id: borderWidthSlider + Slider { id: sizeSlider; from: 0; to: 100; Kirigami.FormData.label: "Shadow Size" } + Slider { id: xOffsetSlider; from: -100; to: 100; Kirigami.FormData.label: "Shadow X-Offset" } + Slider { id: yOffsetSlider; from: -100; to: 100; Kirigami.FormData.label: "Shadow Y-Offset" } - from: 0 - to: 50 + Slider { id: borderWidthSlider; from: 0; to: 50; Kirigami.FormData.label: "Border Width" } } } } } diff --git a/tests/ShadowedRectangleTest.qml b/tests/ShadowedRectangleTest.qml index 198944c9..240f4571 100644 --- a/tests/ShadowedRectangleTest.qml +++ b/tests/ShadowedRectangleTest.qml @@ -1,81 +1,65 @@ /* * 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 + width: 600 + height: 800 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 radius: radiusSlider.value shadow.size: sizeSlider.value shadow.xOffset: xOffsetSlider.value shadow.yOffset: yOffsetSlider.value border.width: borderWidthSlider.value border.color: Kirigami.Theme.textColor - } - - Item { width: 1; height: Kirigami.Units.gridUnit } - - Slider { - id: sizeSlider - - from: 0 - to: 100 - } - Slider { - id: radiusSlider - - from: 0 - to: 200 + corners.topLeftRadius: topLeftSlider.value + corners.topRightRadius: topRightSlider.value + corners.bottomLeftRadius: bottomLeftSlider.value + corners.bottomRightRadius: bottomRightSlider.value } - Slider { - id: xOffsetSlider - - from: -100 - to: 100 - } + Kirigami.FormLayout { + Item { Kirigami.FormData.isSection: true } - Slider { - id: yOffsetSlider - - from: -100 - to: 100 - } + Slider { id: radiusSlider; from: 0; to: 200; Kirigami.FormData.label: "Overall Radius" } + Slider { id: topLeftSlider; from: -1; to: 200; value: -1; Kirigami.FormData.label: "Top Left Radius" } + Slider { id: topRightSlider; from: -1; to: 200; value: -1; Kirigami.FormData.label: "Top Right Radius" } + Slider { id: bottomLeftSlider; from: -1; to: 200; value: -1; Kirigami.FormData.label: "Bottom Left Radius" } + Slider { id: bottomRightSlider; from: -1; to: 200; value: -1; Kirigami.FormData.label: "Bottom Right Radius" } - Slider { - id: borderWidthSlider + Slider { id: sizeSlider; from: 0; to: 100; Kirigami.FormData.label: "Shadow Size" } + Slider { id: xOffsetSlider; from: -100; to: 100; Kirigami.FormData.label: "Shadow X-Offset" } + Slider { id: yOffsetSlider; from: -100; to: 100; Kirigami.FormData.label: "Shadow Y-Offset" } - from: 0 - to: 50 + Slider { id: borderWidthSlider; from: 0; to: 50; Kirigami.FormData.label: "Border Width" } } } } }