Effect Maker: Enable helper nodes

Helper nodes are nodes that another node depends on and are added
automatically when the depending node is added. Helper nodes are
placed before all other nodes. Helper nodes that do not contain
any properties are not shown. Helper nodes keep reference count
and are removed when last referring node is removed.

Task-number: QDS-11193
Change-Id: I036019afb1414ec6e98b2f949a18bd217753a910
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
Reviewed-by: Qt CI Patch Build Bot <ci_patchbuild_bot@qt.io>
This commit is contained in:
Mahmoud Badri
2023-12-07 18:32:55 +02:00
committed by Miikka Heikkinen
parent 6b6f74ccad
commit dc42b62ddf
12 changed files with 264 additions and 33 deletions

View File

@@ -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
}
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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

View File

@@ -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) {
@@ -119,7 +137,6 @@ void CompositionNode::parse(const QString &effectName, const QString &qenPath, c
QString trimmedLine = codeLine.trimmed();
if (trimmedLine.startsWith("@requires")) {
// Get the required node, remove "@requires "
QString l = trimmedLine.sliced(9).trimmed();
QString nodeName = trimmedLine.sliced(10);
if (!nodeName.isEmpty() && !m_requiredNodes.contains(nodeName))
m_requiredNodes << nodeName;
@@ -132,6 +149,36 @@ QList<Uniform *> 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;

View File

@@ -16,6 +16,7 @@ class CompositionNode : public QObject
Q_PROPERTY(QString nodeName MEMBER m_name CONSTANT)
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<Uniform *> 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<Uniform*> m_uniforms;

View File

@@ -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<int, QByteArray> 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<EffectMakerUniformsModel *>(node->uniformsModel()),
&EffectMakerUniformsModel::dataChanged, this, [this] {
setHasUnsavedChanges(true);
});
const QList<QString> 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<Uniform *> 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<QString, int> 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<EffectMakerUniformsModel *>(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

View File

@@ -7,10 +7,10 @@
#include <utils/filepath.h>
#include <QAbstractListModel>
#include <QFileSystemWatcher>
#include <QMap>
#include <QRegularExpression>
#include <QStandardItemModel>
#include <QTemporaryFile>
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 {

View File

@@ -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 <coreplugin/icore.h>
#include "effectutils.h"
#include <utils/filepath.h>
#include <utils/hostosinfo.h>
@@ -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.";

View File

@@ -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())},

View File

@@ -3,6 +3,8 @@
#include "effectutils.h"
#include <coreplugin/icore.h>
#include <QJsonArray>
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

View File

@@ -15,6 +15,8 @@ public:
EffectUtils() = delete;
static QString codeFromJsonArray(const QJsonArray &codeArray);
static QString nodesSourcesPath();
};
} // namespace EffectMaker