diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/BlurHelper.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/BlurHelper.qml new file mode 100644 index 00000000000..e68a0bc8a25 --- /dev/null +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/BlurHelper.qml @@ -0,0 +1,68 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: BSD-3-Clause + +// This file should match the BlurHelper.qml in qtquickdesigner repository, except for shader paths + +import QtQuick + +Item { + id: rootItem + property alias blurSrc1: blurredItemSource1 + property alias blurSrc2: blurredItemSource2 + property alias blurSrc3: blurredItemSource3 + property alias blurSrc4: blurredItemSource4 + property alias blurSrc5: blurredItemSource5 + + component BlurItem: ShaderEffect { + property vector2d offset: Qt.vector2d((1.0 + rootItem.blurMultiplier) / width, + (1.0 + rootItem.blurMultiplier) / height) + visible: false + layer.enabled: true + layer.smooth: true + vertexShader: g_propertyData.blur_vs_path + fragmentShader: g_propertyData.blur_fs_path + } + + QtObject { + id: priv + property bool useBlurItem1: true + property bool useBlurItem2: rootItem.blurMax > 2 + property bool useBlurItem3: rootItem.blurMax > 8 + property bool useBlurItem4: rootItem.blurMax > 16 + property bool useBlurItem5: rootItem.blurMax > 32 + } + + BlurItem { + id: blurredItemSource1 + property Item src: priv.useBlurItem1 ? source : null + // Size of the first blurred item is by default half of the source. + // Increase for quality and decrease for performance & more blur. + readonly property int blurItemSize: 8 + width: src ? Math.ceil(src.width / 16) * blurItemSize : 0 + height: src ? Math.ceil(src.height / 16) * blurItemSize : 0 + } + BlurItem { + id: blurredItemSource2 + property Item src: priv.useBlurItem2 ? blurredItemSource1 : null + width: blurredItemSource1.width * 0.5 + height: blurredItemSource1.height * 0.5 + } + BlurItem { + id: blurredItemSource3 + property Item src: priv.useBlurItem3 ? blurredItemSource2 : null + width: blurredItemSource2.width * 0.5 + height: blurredItemSource2.height * 0.5 + } + BlurItem { + id: blurredItemSource4 + property Item src: priv.useBlurItem4 ? blurredItemSource3 : null + width: blurredItemSource3.width * 0.5 + height: blurredItemSource3.height * 0.5 + } + BlurItem { + id: blurredItemSource5 + property Item src: priv.useBlurItem5 ? blurredItemSource4 : null + width: blurredItemSource4.width * 0.5 + height: blurredItemSource4.height * 0.5 + } +} diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectCompositionNode.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectCompositionNode.qml index cf9c3d668b0..2ccaeaf36c3 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectCompositionNode.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectCompositionNode.qml @@ -17,16 +17,17 @@ HelperWidgets.Section { caption: nodeName category: "EffectMaker" - draggable: true + draggable: !isDependency fillBackground: true - showCloseButton: true + showCloseButton: !isDependency closeButtonToolTip: qsTr("Remove") + visible: repeater.count > 0 || !isDependency onCloseButtonClicked: { EffectMakerBackend.effectMakerModel.removeNode(root.modelIndex) } - showEyeButton: true + showEyeButton: !isDependency eyeEnabled: nodeEnabled eyeButtonToolTip: qsTr("Enable/Disable Node") @@ -38,6 +39,7 @@ HelperWidgets.Section { spacing: 10 Repeater { + id: repeater model: nodeUniformsModel EffectCompositionNodeUniform { diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMaker.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMaker.qml index 81be29f9ae2..d3ccb36a749 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMaker.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMaker.qml @@ -213,7 +213,8 @@ Item { currItem.y = root.secsY[i] } } else if (i < root.moveFromIdx) { - if (root.draggedSec.y < currItem.y + (currItem.height - root.draggedSec.height) * .5) { + if (!repeater.model.isDependencyNode(i) + && root.draggedSec.y < currItem.y + (currItem.height - root.draggedSec.height) * .5) { currItem.y = root.secsY[i] + root.draggedSec.height root.moveToIdx = Math.min(root.moveToIdx, i) } else { diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerPreview.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerPreview.qml index 4636159f481..ea0b2d12954 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerPreview.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerPreview.qml @@ -194,6 +194,13 @@ Column { } } + BlurHelper { + id: blurHelper + anchors.fill: parent + property int blurMax: g_propertyData.blur_helper_max_level ? g_propertyData.blur_helper_max_level : 64 + property real blurMultiplier: g_propertyData.blurMultiplier ? g_propertyData.blurMultiplier : 0 + } + Item { id: componentParent width: source.width diff --git a/src/plugins/effectmakernew/compositionnode.cpp b/src/plugins/effectmakernew/compositionnode.cpp index 8cd5bda971e..9520e50d050 100644 --- a/src/plugins/effectmakernew/compositionnode.cpp +++ b/src/plugins/effectmakernew/compositionnode.cpp @@ -14,7 +14,8 @@ namespace EffectMaker { -CompositionNode::CompositionNode(const QString &effectName, const QString &qenPath, const QJsonObject &jsonObject) +CompositionNode::CompositionNode(const QString &effectName, const QString &qenPath, + const QJsonObject &jsonObject) { QJsonObject json; if (jsonObject.isEmpty()) { @@ -58,6 +59,11 @@ QString CompositionNode::description() const return m_description; } +QString CompositionNode::id() const +{ + return m_id; +} + QObject *CompositionNode::uniformsModel() { return &m_unifomrsModel; @@ -81,6 +87,11 @@ void CompositionNode::setIsEnabled(bool newIsEnabled) } } +bool CompositionNode::isDependency() const +{ + return m_refCount > 0; +} + CompositionNode::NodeType CompositionNode::type() const { return m_type; @@ -102,6 +113,13 @@ void CompositionNode::parse(const QString &effectName, const QString &qenPath, c m_fragmentCode = EffectUtils::codeFromJsonArray(json.value("fragmentCode").toArray()); m_vertexCode = EffectUtils::codeFromJsonArray(json.value("vertexCode").toArray()); + m_id = json.value("id").toString(); + if (m_id.isEmpty() && !qenPath.isEmpty()) { + QString fileName = qenPath.split('/').last(); + fileName.chop(4); // remove ".qen" + m_id = fileName; + } + // parse properties QJsonArray jsonProps = json.value("properties").toArray(); for (const auto /*QJsonValueRef*/ &prop : jsonProps) { @@ -118,8 +136,7 @@ void CompositionNode::parse(const QString &effectName, const QString &qenPath, c for (const QString &codeLine : std::as_const(shaderCodeLines)) { QString trimmedLine = codeLine.trimmed(); if (trimmedLine.startsWith("@requires")) { - // Get the required node, remove "@requires" - QString l = trimmedLine.sliced(9).trimmed(); + // Get the required node, remove "@requires " QString nodeName = trimmedLine.sliced(10); if (!nodeName.isEmpty() && !m_requiredNodes.contains(nodeName)) m_requiredNodes << nodeName; @@ -132,6 +149,36 @@ QList CompositionNode::uniforms() const return m_uniforms; } +int CompositionNode::incRefCount() +{ + ++m_refCount; + + if (m_refCount == 1) + emit isDepencyChanged(); + + return m_refCount; +} + +int CompositionNode::decRefCount() +{ + --m_refCount; + + if (m_refCount == 0) + emit isDepencyChanged(); + + return m_refCount; +} + +void CompositionNode::setRefCount(int count) +{ + bool notifyChange = (m_refCount > 0 && count == 0) || (m_refCount <= 0 && count > 0); + + m_refCount = count; + + if (notifyChange) + emit isDepencyChanged(); +} + QString CompositionNode::name() const { return m_name; diff --git a/src/plugins/effectmakernew/compositionnode.h b/src/plugins/effectmakernew/compositionnode.h index 4736f1d8afa..04f5dd5c02b 100644 --- a/src/plugins/effectmakernew/compositionnode.h +++ b/src/plugins/effectmakernew/compositionnode.h @@ -15,7 +15,8 @@ class CompositionNode : public QObject Q_OBJECT Q_PROPERTY(QString nodeName MEMBER m_name CONSTANT) - Q_PROPERTY(bool nodeEnabled READ isEnabled WRITE setIsEnabled NOTIFY isEnabledChanged) + Q_PROPERTY(bool nodeEnabled READ isEnabled WRITE setIsEnabled NOTIFY isEnabledChanged) + Q_PROPERTY(bool isDependency READ isDependency NOTIFY isDepencyChanged) Q_PROPERTY(QObject *nodeUniformsModel READ uniformsModel NOTIFY uniformsModelChanged) public: @@ -30,6 +31,7 @@ public: QString fragmentCode() const; QString vertexCode() const; QString description() const; + QString id() const; QObject *uniformsModel(); @@ -40,13 +42,20 @@ public: bool isEnabled() const; void setIsEnabled(bool newIsEnabled); + bool isDependency() const; + QString name() const; QList uniforms() const; + int incRefCount(); + int decRefCount(); + void setRefCount(int count); + signals: void uniformsModelChanged(); void isEnabledChanged(); + void isDepencyChanged(); private: void parse(const QString &effectName, const QString &qenPath, const QJsonObject &json); @@ -57,7 +66,9 @@ private: QString m_vertexCode; QString m_description; QStringList m_requiredNodes; + QString m_id; bool m_isEnabled = true; + int m_refCount = 0; QList m_uniforms; diff --git a/src/plugins/effectmakernew/effectmakermodel.cpp b/src/plugins/effectmakernew/effectmakermodel.cpp index e13e2e39a9a..fbb439ff17d 100644 --- a/src/plugins/effectmakernew/effectmakermodel.cpp +++ b/src/plugins/effectmakernew/effectmakermodel.cpp @@ -4,6 +4,8 @@ #include "effectmakermodel.h" #include "compositionnode.h" +#include "effectutils.h" +#include "propertyhandler.h" #include "syntaxhighlighterdata.h" #include "uniform.h" @@ -58,6 +60,7 @@ QHash EffectMakerModel::roleNames() const roles[NameRole] = "nodeName"; roles[EnabledRole] = "nodeEnabled"; roles[UniformsRole] = "nodeUniformsModel"; + roles[Dependency] = "isDependency"; return roles; } @@ -104,14 +107,29 @@ void EffectMakerModel::setIsEmpty(bool val) void EffectMakerModel::addNode(const QString &nodeQenPath) { - beginInsertRows({}, m_nodes.size(), m_nodes.size()); - auto *node = new CompositionNode("", nodeQenPath); + beginResetModel(); + auto *node = new CompositionNode({}, nodeQenPath); connect(qobject_cast(node->uniformsModel()), &EffectMakerUniformsModel::dataChanged, this, [this] { - setHasUnsavedChanges(true); - }); + setHasUnsavedChanges(true); + }); + + const QList requiredNodes = node->requiredNodes(); + if (requiredNodes.size() > 0) { + for (const QString &requiredId : requiredNodes) { + if (auto reqNode = findNodeById(requiredId)) { + reqNode->incRefCount(); + continue; + } + + const QString path = EffectUtils::nodesSourcesPath() + "/common/" + requiredId + ".qen"; + auto requiredNode = new CompositionNode({}, path); + requiredNode->setRefCount(1); + m_nodes.prepend(requiredNode); + } + } m_nodes.append(node); - endInsertRows(); + endResetModel(); setIsEmpty(false); @@ -121,6 +139,15 @@ void EffectMakerModel::addNode(const QString &nodeQenPath) emit nodesChanged(); } +CompositionNode *EffectMakerModel::findNodeById(const QString &id) const +{ + for (CompositionNode *node : std::as_const(m_nodes)) { + if (node->id() == id) + return node; + } + return {}; +} + void EffectMakerModel::moveNode(int fromIdx, int toIdx) { if (fromIdx == toIdx) @@ -137,10 +164,20 @@ void EffectMakerModel::moveNode(int fromIdx, int toIdx) void EffectMakerModel::removeNode(int idx) { - beginRemoveRows({}, idx, idx); + beginResetModel(); CompositionNode *node = m_nodes.takeAt(idx); + + const QStringList reqNodes = node->requiredNodes(); + for (const QString &reqId : reqNodes) { + CompositionNode *depNode = findNodeById(reqId); + if (depNode && depNode->decRefCount() <= 0) { + m_nodes.removeOne(depNode); + delete depNode; + } + } + delete node; - endRemoveRows(); + endResetModel(); if (m_nodes.isEmpty()) setIsEmpty(true); @@ -442,6 +479,8 @@ QJsonObject nodeToJson(const CompositionNode &node) nodeObject.insert("description", node.description()); nodeObject.insert("enabled", node.isEnabled()); nodeObject.insert("version", 1); + nodeObject.insert("id", node.id()); + // Add properties QJsonArray propertiesArray; const QList uniforms = node.uniforms(); @@ -549,7 +588,17 @@ QString EffectMakerModel::getQmlEffectString() s += '\n'; } - //TODO: Blue stuff goes here + if (m_shaderFeatures.enabled(ShaderFeatures::BlurSources)) { + s += " BlurHelper {\n"; + s += " id: blurHelper\n"; + s += " anchors.fill: parent\n"; + int blurMax = 32; + if (g_propertyData.contains("BLUR_HELPER_MAX_LEVEL")) + blurMax = g_propertyData["BLUR_HELPER_MAX_LEVEL"].toInt(); + s += QString(" property int blurMax: %1\n").arg(blurMax); + s += " property real blurMultiplier: rootItem.blurMultiplier\n"; + s += " }\n"; + } s += getQmlComponentString(true); s += "}\n"; @@ -643,18 +692,30 @@ void EffectMakerModel::openComposition(const QString &path) } if (json.contains("nodes") && json["nodes"].isArray()) { + beginResetModel(); + QHash refCounts; const QJsonArray nodesArray = json["nodes"].toArray(); + for (const auto &nodeElement : nodesArray) { - beginInsertRows({}, m_nodes.size(), m_nodes.size()); - auto *node = new CompositionNode(effectName, "", nodeElement.toObject()); + auto *node = new CompositionNode(effectName, {}, nodeElement.toObject()); connect(qobject_cast(node->uniformsModel()), &EffectMakerUniformsModel::dataChanged, this, [this] { setHasUnsavedChanges(true); }); m_nodes.append(node); - endInsertRows(); + const QStringList reqIds = node->requiredNodes(); + for (const QString &reqId : reqIds) + ++refCounts[reqId]; } + for (auto it = refCounts.cbegin(), end = refCounts.cend(); it != end; ++it) { + CompositionNode *depNode = findNodeById(it.key()); + if (depNode) + depNode->setRefCount(it.value()); + } + + endResetModel(); + setIsEmpty(m_nodes.isEmpty()); bakeShaders(); } @@ -748,6 +809,19 @@ void EffectMakerModel::saveResources(const QString &name) } } + if (m_shaderFeatures.enabled(ShaderFeatures::BlurSources)) { + QString blurHelperFilename("BlurHelper.qml"); + QString blurFsFilename("bluritems.frag.qsb"); + QString blurVsFilename("bluritems.vert.qsb"); + QString blurHelperPath(EffectUtils::nodesSourcesPath() + "/common/"); + sources.append(blurHelperPath + blurHelperFilename); + sources.append(blurHelperPath + blurFsFilename); + sources.append(blurHelperPath + blurVsFilename); + dests.append(blurHelperFilename); + dests.append(blurFsFilename); + dests.append(blurVsFilename); + } + for (int i = 0; i < sources.count(); ++i) { Utils::FilePath source = Utils::FilePath::fromString(sources[i]); Utils::FilePath target = Utils::FilePath::fromString(effectsResPath + dests[i]); @@ -1505,6 +1579,13 @@ QStringList EffectMakerModel::uniformNames() const return usedList; } +bool EffectMakerModel::isDependencyNode(int index) const +{ + if (m_nodes.size() > index) + return m_nodes[index]->isDependency(); + return false; +} + void EffectMakerModel::updateQmlComponent() { // Clear possible QML runtime errors diff --git a/src/plugins/effectmakernew/effectmakermodel.h b/src/plugins/effectmakernew/effectmakermodel.h index 7f73ed4cdca..a25a4e40913 100644 --- a/src/plugins/effectmakernew/effectmakermodel.h +++ b/src/plugins/effectmakernew/effectmakermodel.h @@ -7,10 +7,10 @@ #include +#include #include #include #include -#include #include namespace ProjectExplorer { @@ -62,6 +62,8 @@ public: void addNode(const QString &nodeQenPath); + CompositionNode *findNodeById(const QString &id) const; + Q_INVOKABLE void moveNode(int fromIdx, int toIdx); Q_INVOKABLE void removeNode(int idx); Q_INVOKABLE void clear(); @@ -96,6 +98,8 @@ public: QStringList uniformNames() const; + Q_INVOKABLE bool isDependencyNode(int index) const; + signals: void isEmptyChanged(); void selectedIndexChanged(int idx); @@ -112,7 +116,8 @@ private: enum Roles { NameRole = Qt::UserRole + 1, EnabledRole, - UniformsRole + UniformsRole, + Dependency }; enum ErrorTypes { diff --git a/src/plugins/effectmakernew/effectmakernodesmodel.cpp b/src/plugins/effectmakernew/effectmakernodesmodel.cpp index c6773faa090..7f35e935ba9 100644 --- a/src/plugins/effectmakernew/effectmakernodesmodel.cpp +++ b/src/plugins/effectmakernew/effectmakernodesmodel.cpp @@ -2,8 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "effectmakernodesmodel.h" - -#include +#include "effectutils.h" #include #include @@ -41,21 +40,12 @@ QVariant EffectMakerNodesModel::data(const QModelIndex &index, int role) const return m_categories.at(index.row())->property(roleNames().value(role)); } -QString EffectMakerNodesModel::nodesSourcesPath() const -{ -#ifdef SHARE_QML_PATH - if (Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) - return QLatin1String(SHARE_QML_PATH) + "/effectMakerNodes"; -#endif - return Core::ICore::resourcePath("qmldesigner/effectMakerNodes").toString(); -} - void EffectMakerNodesModel::loadModel() { if (m_modelLoaded) return; - auto nodesPath = Utils::FilePath::fromString(nodesSourcesPath()); + auto nodesPath = Utils::FilePath::fromString(EffectUtils::nodesSourcesPath()); if (!nodesPath.exists()) { qWarning() << __FUNCTION__ << "Effects not found."; diff --git a/src/plugins/effectmakernew/effectmakerwidget.cpp b/src/plugins/effectmakernew/effectmakerwidget.cpp index db549501f37..5151c0568ca 100644 --- a/src/plugins/effectmakernew/effectmakerwidget.cpp +++ b/src/plugins/effectmakernew/effectmakerwidget.cpp @@ -7,6 +7,7 @@ #include "effectmakermodel.h" #include "effectmakernodesmodel.h" #include "effectmakerview.h" +#include "effectutils.h" #include "propertyhandler.h" //#include "qmldesigner/designercore/imagecache/midsizeimagecacheprovider.h" @@ -61,6 +62,7 @@ EffectMakerWidget::EffectMakerWidget(EffectMakerView *view) m_quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); QmlDesigner::Theme::setupTheme(m_quickWidget->engine()); m_quickWidget->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); + m_quickWidget->engine()->addImportPath(EffectUtils::nodesSourcesPath() + "/common"); m_quickWidget->setClearColor(QmlDesigner::Theme::getColor( QmlDesigner::Theme::Color::QmlDesigner_BackgroundColorDarkAlternate)); @@ -76,6 +78,10 @@ EffectMakerWidget::EffectMakerWidget(EffectMakerView *view) m_quickWidget->rootContext()->setContextProperty("g_propertyData", &g_propertyData); + QString blurPath = "file:" + EffectUtils::nodesSourcesPath() + "/common/"; + g_propertyData.insert(QString("blur_vs_path"), QString(blurPath + "bluritems.vert.qsb")); + g_propertyData.insert(QString("blur_fs_path"), QString(blurPath + "bluritems.frag.qsb")); + auto map = m_quickWidget->registerPropertyMap("EffectMakerBackend"); map->setProperties({{"effectMakerNodesModel", QVariant::fromValue(m_effectMakerNodesModel.data())}, {"effectMakerModel", QVariant::fromValue(m_effectMakerModel.data())}, diff --git a/src/plugins/effectmakernew/effectutils.cpp b/src/plugins/effectmakernew/effectutils.cpp index 8e2bb625431..a0159c520da 100644 --- a/src/plugins/effectmakernew/effectutils.cpp +++ b/src/plugins/effectmakernew/effectutils.cpp @@ -3,6 +3,8 @@ #include "effectutils.h" +#include + #include namespace EffectMaker { @@ -20,5 +22,14 @@ QString EffectUtils::codeFromJsonArray(const QJsonArray &codeArray) return codeString; } +QString EffectUtils::nodesSourcesPath() +{ +#ifdef SHARE_QML_PATH + if (Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) + return QLatin1String(SHARE_QML_PATH) + "/effectMakerNodes"; +#endif + return Core::ICore::resourcePath("qmldesigner/effectMakerNodes").toString(); +} + } // namespace EffectMaker diff --git a/src/plugins/effectmakernew/effectutils.h b/src/plugins/effectmakernew/effectutils.h index e3de9312dce..eede7952c5c 100644 --- a/src/plugins/effectmakernew/effectutils.h +++ b/src/plugins/effectmakernew/effectutils.h @@ -15,6 +15,8 @@ public: EffectUtils() = delete; static QString codeFromJsonArray(const QJsonArray &codeArray); + + static QString nodesSourcesPath(); }; } // namespace EffectMaker