QmlDesigner: Rename effect maker plugin files to effect composer

Change-Id: I1d09c1088b4067a479f2e7cc396a348f1b48614f
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
This commit is contained in:
Mahmoud Badri
2024-01-26 14:55:50 +02:00
parent 5c7090c90e
commit 33bbab25b6
64 changed files with 379 additions and 385 deletions

View File

@@ -0,0 +1,24 @@
add_qtc_plugin(EffectComposer
CONDITION TARGET Qt::Quick AND TARGET QtCreator::QmlDesigner
PLUGIN_DEPENDS
QtCreator::QmlDesigner QtCreator::ProjectExplorer QtCreator::QmlProjectManager
DEPENDS
Qt::Core Qt::CorePrivate Qt::Widgets Qt::Qml Qt::QmlPrivate Qt::Quick
QtCreator::Utils
SOURCES
effectcomposerplugin.cpp effectcomposerplugin.h
effectcomposerwidget.cpp effectcomposerwidget.h
effectcomposerview.cpp effectcomposerview.h
effectcomposermodel.cpp effectcomposermodel.h
effectcomposernodesmodel.cpp effectcomposernodesmodel.h
effectcomposeruniformsmodel.cpp effectcomposeruniformsmodel.h
effectnode.cpp effectnode.h
effectnodescategory.cpp effectnodescategory.h
compositionnode.cpp compositionnode.h
uniform.cpp uniform.h
effectutils.cpp effectutils.h
effectcomposercontextobject.cpp effectcomposercontextobject.h
shaderfeatures.cpp shaderfeatures.h
syntaxhighlighterdata.cpp syntaxhighlighterdata.h
propertyhandler.cpp propertyhandler.h
)

View File

@@ -0,0 +1,16 @@
{
"Name" : "EffectComposer",
"Version" : "${IDE_VERSION}",
"CompatVersion" : "${IDE_VERSION_COMPAT}",
"Vendor" : "The Qt Company Ltd",
"Copyright" : "(C) $$QTCREATOR_COPYRIGHT_YEAR The Qt Company Ltd",
"License" : [ "Commercial Usage",
"",
"Licensees holding valid Qt Enterprise licenses may use this plugin in accordance with the Qt Enterprise License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company."
],
"Description" : "Plugin for Effect Composer.",
"Url" : "http://www.qt.io",
"DisabledByDefault" : true,
${IDE_PLUGIN_DEPENDENCIES}
}

View File

@@ -0,0 +1,191 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "compositionnode.h"
#include "effectutils.h"
#include "effectcomposeruniformsmodel.h"
#include "propertyhandler.h"
#include "uniform.h"
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
namespace EffectComposer {
CompositionNode::CompositionNode(const QString &effectName, const QString &qenPath,
const QJsonObject &jsonObject)
{
QJsonObject json;
if (jsonObject.isEmpty()) {
QFile qenFile(qenPath);
if (!qenFile.open(QIODevice::ReadOnly)) {
qWarning("Couldn't open effect file.");
return;
}
QByteArray loadData = qenFile.readAll();
QJsonParseError parseError;
QJsonDocument jsonDoc(QJsonDocument::fromJson(loadData, &parseError));
if (parseError.error != QJsonParseError::NoError) {
QString error = QString("Error parsing effect node");
QString errorDetails = QString("%1: %2").arg(parseError.offset).arg(parseError.errorString());
qWarning() << error;
qWarning() << errorDetails;
return;
}
json = jsonDoc.object().value("QEN").toObject();
parse(effectName, qenPath, json);
}
else {
parse(effectName, "", jsonObject);
}
}
QString CompositionNode::fragmentCode() const
{
return m_fragmentCode;
}
QString CompositionNode::vertexCode() const
{
return m_vertexCode;
}
QString CompositionNode::description() const
{
return m_description;
}
QString CompositionNode::id() const
{
return m_id;
}
QObject *CompositionNode::uniformsModel()
{
return &m_unifomrsModel;
}
QStringList CompositionNode::requiredNodes() const
{
return m_requiredNodes;
}
bool CompositionNode::isEnabled() const
{
return m_isEnabled;
}
void CompositionNode::setIsEnabled(bool newIsEnabled)
{
if (newIsEnabled != m_isEnabled) {
m_isEnabled = newIsEnabled;
emit isEnabledChanged();
}
}
bool CompositionNode::isDependency() const
{
return m_refCount > 0;
}
CompositionNode::NodeType CompositionNode::type() const
{
return m_type;
}
void CompositionNode::parse(const QString &effectName, const QString &qenPath, const QJsonObject &json)
{
int version = -1;
if (json.contains("version"))
version = json["version"].toInt(-1);
if (version != 1) {
QString error = QString("Error: Unknown effect version (%1)").arg(version);
qWarning() << qPrintable(error);
return;
}
m_name = json.value("name").toString();
m_description = json.value("description").toString();
m_fragmentCode = EffectUtils::codeFromJsonArray(json.value("fragmentCode").toArray());
m_vertexCode = EffectUtils::codeFromJsonArray(json.value("vertexCode").toArray());
if (json.contains("enabled"))
m_isEnabled = json["enabled"].toBool();
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) {
const auto uniform = new Uniform(effectName, prop.toObject(), qenPath);
m_unifomrsModel.addUniform(uniform);
m_uniforms.append(uniform);
g_propertyData.insert(uniform->name(), uniform->value());
}
// Seek through code to get tags
QStringList shaderCodeLines;
shaderCodeLines += m_vertexCode.split('\n');
shaderCodeLines += m_fragmentCode.split('\n');
for (const QString &codeLine : std::as_const(shaderCodeLines)) {
QString trimmedLine = codeLine.trimmed();
if (trimmedLine.startsWith("@requires")) {
// Get the required node, remove "@requires "
QString nodeId = trimmedLine.sliced(10).toLower();
if (!nodeId.isEmpty() && !m_requiredNodes.contains(nodeId))
m_requiredNodes << nodeId;
}
}
}
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;
}
} // namespace EffectComposer

View File

@@ -0,0 +1,78 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "effectcomposeruniformsmodel.h"
#include <QJsonObject>
#include <QObject>
namespace EffectComposer {
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 isDependency READ isDependency NOTIFY isDepencyChanged)
Q_PROPERTY(QObject *nodeUniformsModel READ uniformsModel NOTIFY uniformsModelChanged)
public:
enum NodeType {
SourceNode = 0,
DestinationNode,
CustomNode
};
CompositionNode(const QString &effectName, const QString &qenPath, const QJsonObject &json = {});
QString fragmentCode() const;
QString vertexCode() const;
QString description() const;
QString id() const;
QObject *uniformsModel();
QStringList requiredNodes() const;
NodeType type() const;
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);
QString m_name;
NodeType m_type = CustomNode;
QString m_fragmentCode;
QString m_vertexCode;
QString m_description;
QStringList m_requiredNodes;
QString m_id;
bool m_isEnabled = true;
int m_refCount = 0;
QList<Uniform *> m_uniforms;
EffectComposerUniformsModel m_unifomrsModel;
};
} // namespace EffectComposer

View File

@@ -0,0 +1,186 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "effectcomposercontextobject.h"
#include <abstractview.h>
#include <bindingproperty.h>
#include <documentmanager.h>
#include <nodemetainfo.h>
#include <rewritingexception.h>
#include <qmldesignerplugin.h>
#include <qmldesigner/components/propertyeditor/qmlmodelnodeproxy.h>
#include <qmlobjectnode.h>
#include <qmltimeline.h>
#include <qmltimelinekeyframegroup.h>
#include <variantproperty.h>
#include <coreplugin/messagebox.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <QApplication>
#include <QCursor>
#include <QMessageBox>
#include <QQmlContext>
#include <QWindow>
#include <coreplugin/icore.h>
namespace EffectComposer {
EffectComposerContextObject::EffectComposerContextObject(QQmlContext *context, QObject *parent)
: QObject(parent)
, m_qmlContext(context)
{
}
QString EffectComposerContextObject::convertColorToString(const QVariant &color)
{
QString colorString;
QColor theColor;
if (color.canConvert(QVariant::Color)) {
theColor = color.value<QColor>();
} else if (color.canConvert(QVariant::Vector3D)) {
auto vec = color.value<QVector3D>();
theColor = QColor::fromRgbF(vec.x(), vec.y(), vec.z());
}
colorString = theColor.name(QColor::HexArgb);
return colorString;
}
// TODO: this method is used by the ColorEditor helper widget, check if at all needed?
QColor EffectComposerContextObject::colorFromString(const QString &colorString)
{
return colorString;
}
int EffectComposerContextObject::majorVersion() const
{
return m_majorVersion;
}
void EffectComposerContextObject::setMajorVersion(int majorVersion)
{
if (m_majorVersion == majorVersion)
return;
m_majorVersion = majorVersion;
emit majorVersionChanged();
}
void EffectComposerContextObject::setStateName(const QString &newStateName)
{
if (newStateName == m_stateName)
return;
m_stateName = newStateName;
emit stateNameChanged();
}
void EffectComposerContextObject::setAllStateNames(const QStringList &allStates)
{
if (allStates == m_allStateNames)
return;
m_allStateNames = allStates;
emit allStateNamesChanged();
}
void EffectComposerContextObject::setIsBaseState(bool newIsBaseState)
{
if (newIsBaseState == m_isBaseState)
return;
m_isBaseState = newIsBaseState;
emit isBaseStateChanged();
}
void EffectComposerContextObject::setSelectionChanged(bool newSelectionChanged)
{
if (newSelectionChanged == m_selectionChanged)
return;
m_selectionChanged = newSelectionChanged;
emit selectionChangedChanged();
}
void EffectComposerContextObject::setBackendValues(QQmlPropertyMap *newBackendValues)
{
if (newBackendValues == m_backendValues)
return;
m_backendValues = newBackendValues;
emit backendValuesChanged();
}
void EffectComposerContextObject::setModel(QmlDesigner::Model *model)
{
m_model = model;
}
void EffectComposerContextObject::triggerSelectionChanged()
{
setSelectionChanged(!m_selectionChanged);
}
void EffectComposerContextObject::hideCursor()
{
if (QApplication::overrideCursor())
return;
QApplication::setOverrideCursor(QCursor(Qt::BlankCursor));
if (QWidget *w = QApplication::activeWindow())
m_lastPos = QCursor::pos(w->screen());
}
void EffectComposerContextObject::restoreCursor()
{
if (!QApplication::overrideCursor())
return;
QApplication::restoreOverrideCursor();
if (QWidget *w = QApplication::activeWindow())
QCursor::setPos(w->screen(), m_lastPos);
}
void EffectComposerContextObject::holdCursorInPlace()
{
if (!QApplication::overrideCursor())
return;
if (QWidget *w = QApplication::activeWindow())
QCursor::setPos(w->screen(), m_lastPos);
}
int EffectComposerContextObject::devicePixelRatio()
{
if (QWidget *w = QApplication::activeWindow())
return w->devicePixelRatio();
return 1;
}
QStringList EffectComposerContextObject::allStatesForId(const QString &id)
{
if (m_model && m_model->rewriterView()) {
const QmlDesigner::QmlObjectNode node = m_model->rewriterView()->modelNodeForId(id);
if (node.isValid())
return node.allStateNames();
}
return {};
}
bool EffectComposerContextObject::isBlocked(const QString &) const
{
return false;
}
} // namespace EffectComposer

View File

@@ -0,0 +1,102 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <model.h>
#include <modelnode.h>
#include <QObject>
#include <QUrl>
#include <QQmlPropertyMap>
#include <QQmlComponent>
#include <QColor>
#include <QPoint>
#include <QMouseEvent>
namespace EffectComposer {
class EffectComposerContextObject : public QObject
{
Q_OBJECT
Q_PROPERTY(QString stateName READ stateName WRITE setStateName NOTIFY stateNameChanged)
Q_PROPERTY(QStringList allStateNames READ allStateNames WRITE setAllStateNames NOTIFY allStateNamesChanged)
Q_PROPERTY(int possibleTypeIndex READ possibleTypeIndex NOTIFY possibleTypeIndexChanged)
Q_PROPERTY(bool isBaseState READ isBaseState WRITE setIsBaseState NOTIFY isBaseStateChanged)
Q_PROPERTY(bool selectionChanged READ selectionChanged WRITE setSelectionChanged NOTIFY selectionChangedChanged)
Q_PROPERTY(int majorVersion READ majorVersion WRITE setMajorVersion NOTIFY majorVersionChanged)
Q_PROPERTY(QQmlPropertyMap *backendValues READ backendValues WRITE setBackendValues NOTIFY backendValuesChanged)
public:
EffectComposerContextObject(QQmlContext *context, QObject *parent = nullptr);
QString stateName() const { return m_stateName; }
QStringList allStateNames() const { return m_allStateNames; }
int possibleTypeIndex() const { return m_possibleTypeIndex; }
bool isBaseState() const { return m_isBaseState; }
bool selectionChanged() const { return m_selectionChanged; }
QQmlPropertyMap *backendValues() const { return m_backendValues; }
Q_INVOKABLE QString convertColorToString(const QVariant &color);
Q_INVOKABLE QColor colorFromString(const QString &colorString);
Q_INVOKABLE void hideCursor();
Q_INVOKABLE void restoreCursor();
Q_INVOKABLE void holdCursorInPlace();
Q_INVOKABLE int devicePixelRatio();
Q_INVOKABLE QStringList allStatesForId(const QString &id);
Q_INVOKABLE bool isBlocked(const QString &propName) const;
int majorVersion() const;
void setMajorVersion(int majorVersion);
void setStateName(const QString &newStateName);
void setAllStateNames(const QStringList &allStates);
void setIsBaseState(bool newIsBaseState);
void setSelectionChanged(bool newSelectionChanged);
void setBackendValues(QQmlPropertyMap *newBackendValues);
void setModel(QmlDesigner::Model *model);
void triggerSelectionChanged();
signals:
void stateNameChanged();
void allStateNamesChanged();
void possibleTypeIndexChanged();
void isBaseStateChanged();
void selectionChangedChanged();
void backendValuesChanged();
void majorVersionChanged();
private:
void updatePossibleTypeIndex();
QQmlContext *m_qmlContext = nullptr;
QString m_stateName;
QStringList m_allStateNames;
int m_possibleTypeIndex = -1;
QString m_currentType;
int m_majorVersion = 1;
QQmlPropertyMap *m_backendValues = nullptr;
QmlDesigner::Model *m_model = nullptr;
QPoint m_lastPos;
bool m_isBaseState = false;
bool m_selectionChanged = false;
};
} // namespace EffectComposer

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,210 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "shaderfeatures.h"
#include <utils/filepath.h>
#include <QAbstractListModel>
#include <QFileSystemWatcher>
#include <QMap>
#include <QRegularExpression>
#include <QTemporaryFile>
namespace ProjectExplorer {
class Target;
}
namespace Utils {
class Process;
}
namespace EffectComposer {
class CompositionNode;
class Uniform;
struct EffectError {
Q_GADGET
Q_PROPERTY(QString message MEMBER m_message)
Q_PROPERTY(int line MEMBER m_line)
Q_PROPERTY(int type MEMBER m_type)
public:
QString m_message;
int m_line = -1;
int m_type = -1;
};
class EffectComposerModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged)
Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged)
Q_PROPERTY(bool hasUnsavedChanges MEMBER m_hasUnsavedChanges WRITE setHasUnsavedChanges NOTIFY hasUnsavedChangesChanged)
Q_PROPERTY(bool shadersUpToDate READ shadersUpToDate WRITE setShadersUpToDate NOTIFY shadersUpToDateChanged)
Q_PROPERTY(QString qmlComponentString READ qmlComponentString)
Q_PROPERTY(QString currentComposition READ currentComposition WRITE setCurrentComposition NOTIFY currentCompositionChanged)
public:
EffectComposerModel(QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex & parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
bool isEmpty() const { return m_isEmpty; }
void setIsEmpty(bool val);
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(bool clearName = false);
Q_INVOKABLE void assignToSelected();
Q_INVOKABLE QString getUniqueEffectName() const;
bool shadersUpToDate() const;
void setShadersUpToDate(bool newShadersUpToDate);
QString fragmentShader() const;
void setFragmentShader(const QString &newFragmentShader);
QString vertexShader() const;
void setVertexShader(const QString &newVertexShader);
const QString &qmlComponentString() const;
Q_INVOKABLE void updateQmlComponent();
Q_INVOKABLE void resetEffectError(int type);
Q_INVOKABLE void setEffectError(const QString &errorMessage, int type = -1, int lineNumber = -1);
Q_INVOKABLE void saveComposition(const QString &name);
void openComposition(const QString &path);
QString currentComposition() const;
void setCurrentComposition(const QString &newCurrentComposition);
bool hasUnsavedChanges() const;
void setHasUnsavedChanges(bool val);
QStringList uniformNames() const;
Q_INVOKABLE bool isDependencyNode(int index) const;
signals:
void isEmptyChanged();
void selectedIndexChanged(int idx);
void effectErrorChanged();
void shadersUpToDateChanged();
void shadersBaked();
void currentCompositionChanged();
void nodesChanged();
void resourcesSaved(const QByteArray &type, const Utils::FilePath &path);
void hasUnsavedChangesChanged();
void assignToSelectedTriggered(const QString &effectPath);
private:
enum Roles {
NameRole = Qt::UserRole + 1,
EnabledRole,
UniformsRole,
Dependency
};
enum ErrorTypes {
ErrorCommon = -1,
ErrorQMLParsing,
ErrorVert,
ErrorFrag,
ErrorQMLRuntime,
ErrorPreprocessor
};
bool isValidIndex(int idx) const;
const QList<Uniform *> allUniforms() const;
const QString getBufUniform();
const QString getVSUniforms();
const QString getFSUniforms();
QString detectErrorMessage(const QString &errorMessage);
EffectError effectError() const;
QString valueAsString(const Uniform &uniform);
QString valueAsBinding(const Uniform &uniform);
QString valueAsVariable(const Uniform &uniform);
QString getImageElementName(const Uniform &uniform, bool localFiles);
const QString getConstVariables();
const QString getDefineProperties();
int getTagIndex(const QStringList &code, const QString &tag);
QString processVertexRootLine(const QString &line);
QString processFragmentRootLine(const QString &line);
QStringList getDefaultRootVertexShader();
QStringList getDefaultRootFragmentShader();
QStringList removeTagsFromCode(const QStringList &codeLines);
QString removeTagsFromCode(const QString &code);
QString getCustomShaderVaryings(bool outState);
QString generateVertexShader(bool includeUniforms = true);
QString generateFragmentShader(bool includeUniforms = true);
void handleQsbProcessExit(Utils::Process *qsbProcess, const QString &shader, bool preview);
QString stripFileFromURL(const QString &urlString) const;
QString getQmlEffectString();
void updateCustomUniforms();
void createFiles();
void bakeShaders();
void saveResources(const QString &name);
QString mipmapPropertyName(const QString &name) const;
QString getQmlImagesString(bool localFiles);
QString getQmlComponentString(bool localFiles);
QList<CompositionNode *> m_nodes;
int m_selectedIndex = -1;
bool m_isEmpty = true;
bool m_hasUnsavedChanges = false;
// True when shaders haven't changed since last baking
bool m_shadersUpToDate = true;
int m_remainingQsbTargets = 0;
QMap<int, EffectError> m_effectErrors;
ShaderFeatures m_shaderFeatures;
QStringList m_shaderVaryingVariables;
QString m_fragmentShader;
QString m_vertexShader;
QStringList m_defaultRootVertexShader;
QStringList m_defaultRootFragmentShader;
// Temp files to store shaders sources and binary data
QTemporaryFile m_fragmentSourceFile;
QTemporaryFile m_vertexSourceFile;
QString m_fragmentSourceFilename;
QString m_vertexSourceFilename;
QString m_fragmentShaderFilename;
QString m_vertexShaderFilename;
QString m_fragmentShaderPreviewFilename;
QString m_vertexShaderPreviewFilename;
// Used in exported QML, at root of the file
QString m_exportedRootPropertiesString;
// Used in exported QML, at ShaderEffect component of the file
QString m_exportedEffectPropertiesString;
// Used in preview QML, at ShaderEffect component of the file
QString m_previewEffectPropertiesString;
QString m_qmlComponentString;
bool m_loadComponentImages = true;
QString m_currentComposition;
const QRegularExpression m_spaceReg = QRegularExpression("\\s+");
};
} // namespace EffectComposer

View File

@@ -0,0 +1,111 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "effectcomposernodesmodel.h"
#include "effectutils.h"
#include <utils/filepath.h>
#include <utils/hostosinfo.h>
#include <QCoreApplication>
namespace EffectComposer {
EffectComposerNodesModel::EffectComposerNodesModel(QObject *parent)
: QAbstractListModel{parent}
{
}
QHash<int, QByteArray> EffectComposerNodesModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[CategoryNameRole] = "categoryName";
roles[CategoryNodesRole] = "categoryNodes";
return roles;
}
int EffectComposerNodesModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_categories.count();
}
QVariant EffectComposerNodesModel::data(const QModelIndex &index, int role) const
{
QTC_ASSERT(index.isValid() && index.row() < m_categories.size(), return {});
QTC_ASSERT(roleNames().contains(role), return {});
return m_categories.at(index.row())->property(roleNames().value(role));
}
void EffectComposerNodesModel::loadModel()
{
if (m_modelLoaded)
return;
auto nodesPath = Utils::FilePath::fromString(EffectUtils::nodesSourcesPath());
if (!nodesPath.exists()) {
qWarning() << __FUNCTION__ << "Effects not found.";
return;
}
m_categories = {};
QDirIterator itCategories(nodesPath.toString(), QDir::Dirs | QDir::NoDotAndDotDot);
while (itCategories.hasNext()) {
itCategories.next();
if (itCategories.fileName() == "images" || itCategories.fileName() == "common")
continue;
QString catName = itCategories.fileName();
QList<EffectNode *> effects = {};
Utils::FilePath categoryPath = nodesPath.resolvePath(itCategories.fileName());
QDirIterator itEffects(categoryPath.toString(), {"*.qen"}, QDir::Files);
while (itEffects.hasNext()) {
itEffects.next();
effects.push_back(new EffectNode(itEffects.filePath()));
}
catName[0] = catName[0].toUpper(); // capitalize first letter
EffectNodesCategory *category = new EffectNodesCategory(catName, effects);
m_categories.push_back(category);
}
std::sort(m_categories.begin(), m_categories.end(),
[](EffectNodesCategory *a, EffectNodesCategory *b) {
return a->name() < b->name();
});
m_modelLoaded = true;
resetModel();
}
void EffectComposerNodesModel::resetModel()
{
beginResetModel();
endResetModel();
}
void EffectComposerNodesModel::updateCanBeAdded(const QStringList &uniforms)
{
for (const EffectNodesCategory *cat : std::as_const(m_categories)) {
const QList<EffectNode *> nodes = cat->nodes();
for (EffectNode *node : nodes) {
bool match = false;
for (const QString &uniform : uniforms) {
match = node->hasUniform(uniform);
if (match)
break;
}
node->setCanBeAdded(!match);
}
}
}
} // namespace EffectComposer

View File

@@ -0,0 +1,44 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "effectnodescategory.h"
#include <QStandardItemModel>
namespace EffectComposer {
class EffectComposerNodesModel : public QAbstractListModel
{
Q_OBJECT
enum Roles {
CategoryNameRole = Qt::UserRole + 1,
CategoryNodesRole
};
public:
EffectComposerNodesModel(QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex & parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
void loadModel();
void resetModel();
QList<EffectNodesCategory *> categories() const { return m_categories; }
void updateCanBeAdded(const QStringList &uniforms);
private:
QString nodesSourcesPath() const;
QList<EffectNodesCategory *> m_categories;
bool m_probeNodesDir = false;
bool m_modelLoaded = false;
};
} // namespace EffectComposer

View File

@@ -0,0 +1,53 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "effectcomposerplugin.h"
#include "effectcomposerview.h"
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/coreconstants.h>
#include <coreplugin/icore.h>
#include <coreplugin/imode.h>
#include <coreplugin/modemanager.h>
#include <coreplugin/messagemanager.h>
#include <coreplugin/externaltool.h>
#include <coreplugin/externaltoolmanager.h>
#include <componentcore_constants.h>
#include <designeractionmanager.h>
#include <viewmanager.h>
#include <qmldesigner/dynamiclicensecheck.h>
#include <qmldesignerplugin.h>
#include <modelnodeoperations.h>
#include <QJsonDocument>
#include <QMap>
namespace EffectComposer {
static bool enableEffectComposer()
{
return true;
}
bool EffectComposerPlugin::delayedInitialize()
{
if (m_delayedInitialized)
return true;
if (enableEffectComposer()) {
auto *designerPlugin = QmlDesigner::QmlDesignerPlugin::instance();
auto &viewManager = designerPlugin->viewManager();
viewManager.registerView(std::make_unique<EffectComposerView>(
QmlDesigner::QmlDesignerPlugin::externalDependenciesForPluginInitializationOnly()));
}
m_delayedInitialized = true;
return true;
}
} // namespace EffectComposer

View File

@@ -0,0 +1,32 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <extensionsystem/iplugin.h>
#include <utils/process.h>
namespace Core {
class ActionContainer;
class ExternalTool;
}
namespace EffectComposer {
class EffectComposerPlugin : public ExtensionSystem::IPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "EffectComposer.json")
public:
EffectComposerPlugin() {}
~EffectComposerPlugin() override {}
bool delayedInitialize() override;
private:
bool m_delayedInitialized = false;
};
} // namespace EffectComposer

View File

@@ -0,0 +1,93 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "effectcomposeruniformsmodel.h"
#include "propertyhandler.h"
#include "uniform.h"
#include <utils/qtcassert.h>
namespace EffectComposer {
EffectComposerUniformsModel::EffectComposerUniformsModel(QObject *parent)
: QAbstractListModel{parent}
{
}
QHash<int, QByteArray> EffectComposerUniformsModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[NameRole] = "uniformName";
roles[DescriptionRole] = "uniformDescription";
roles[ValueRole] = "uniformValue";
roles[BackendValueRole] = "uniformBackendValue";
roles[DefaultValueRole] = "uniformDefaultValue";
roles[MinValueRole] = "uniformMinValue";
roles[MaxValueRole] = "uniformMaxValue";
roles[TypeRole] = "uniformType";
roles[UseCustomValueRole] = "uniformUseCustomValue";
return roles;
}
int EffectComposerUniformsModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_uniforms.size();
}
QVariant EffectComposerUniformsModel::data(const QModelIndex &index, int role) const
{
QTC_ASSERT(index.isValid() && index.row() < m_uniforms.size(), return {});
QTC_ASSERT(roleNames().contains(role), return {});
return m_uniforms.at(index.row())->property(roleNames().value(role));
}
bool EffectComposerUniformsModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!index.isValid() || !roleNames().contains(role))
return false;
auto uniform = m_uniforms.at(index.row());
if (uniform->type() == Uniform::Type::Sampler) {
QString updatedValue = value.toString();
int idx = value.toString().indexOf("file:");
QString path = idx > 0 ? updatedValue.right(updatedValue.size() - idx - 5) : updatedValue;
updatedValue = QUrl::fromLocalFile(path).toString();
uniform->setValue(updatedValue);
g_propertyData.insert(uniform->name(), updatedValue);
} else {
uniform->setValue(value);
g_propertyData.insert(uniform->name(), value);
}
emit dataChanged(index, index, {role});
return true;
}
void EffectComposerUniformsModel::resetModel()
{
beginResetModel();
endResetModel();
}
void EffectComposerUniformsModel::addUniform(Uniform *uniform)
{
beginInsertRows({}, m_uniforms.size(), m_uniforms.size());
m_uniforms.append(uniform);
endInsertRows();
}
QList<Uniform *> EffectComposerUniformsModel::uniforms() const
{
return m_uniforms;
}
} // namespace EffectComposer

View File

@@ -0,0 +1,47 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <QStandardItemModel>
namespace EffectComposer {
class Uniform;
class EffectComposerUniformsModel : public QAbstractListModel
{
Q_OBJECT
public:
EffectComposerUniformsModel(QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex & parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
void resetModel();
void addUniform(Uniform *uniform);
QList<Uniform *> uniforms() const;
private:
enum Roles {
NameRole = Qt::UserRole + 1,
DescriptionRole,
ValueRole,
BackendValueRole,
DefaultValueRole,
MaxValueRole,
MinValueRole,
TypeRole,
UseCustomValueRole
};
QList<Uniform *> m_uniforms;
};
} // namespace EffectComposer

View File

@@ -0,0 +1,99 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "effectcomposerview.h"
#include "effectcomposermodel.h"
#include "effectcomposernodesmodel.h"
#include "effectcomposerwidget.h"
#include <documentmanager.h>
#include <modelnodeoperations.h>
#include <qmldesignerconstants.h>
#include <coreplugin/icore.h>
namespace EffectComposer {
EffectComposerContext::EffectComposerContext(QWidget *widget)
: IContext(widget)
{
setWidget(widget);
setContext(Core::Context(QmlDesigner::Constants::C_QMLEFFECTCOMPOSER,
QmlDesigner::Constants::C_QT_QUICK_TOOLS_MENU));
}
void EffectComposerContext::contextHelp(const HelpCallback &callback) const
{
qobject_cast<EffectComposerWidget *>(m_widget)->contextHelp(callback);
}
EffectComposerView::EffectComposerView(QmlDesigner::ExternalDependenciesInterface &externalDependencies)
: AbstractView{externalDependencies}
{
}
EffectComposerView::~EffectComposerView()
{}
bool EffectComposerView::hasWidget() const
{
return true;
}
QmlDesigner::WidgetInfo EffectComposerView::widgetInfo()
{
if (m_widget.isNull()) {
m_widget = new EffectComposerWidget{this};
connect(m_widget->effectComposerModel(), &EffectComposerModel::assignToSelectedTriggered, this,
[&] (const QString &effectPath) {
executeInTransaction("EffectComposerView::widgetInfo", [&] {
const QList<QmlDesigner::ModelNode> selectedNodes = selectedModelNodes();
for (const QmlDesigner::ModelNode &node : selectedNodes)
QmlDesigner::ModelNodeOperations::handleItemLibraryEffectDrop(effectPath, node);
});
});
auto context = new EffectComposerContext(m_widget.data());
Core::ICore::addContextObject(context);
}
return createWidgetInfo(m_widget.data(), "Effect Composer",
QmlDesigner::WidgetInfo::LeftPane, 0, tr("Effect Composer [beta]"));
}
void EffectComposerView::customNotification([[maybe_unused]] const AbstractView *view,
const QString &identifier,
[[maybe_unused]] const QList<QmlDesigner::ModelNode> &nodeList,
const QList<QVariant> &data)
{
if (identifier == "open_effectcomposer_composition" && data.count() > 0) {
const QString compositionPath = data[0].toString();
m_widget->openComposition(compositionPath);
}
}
void EffectComposerView::modelAttached(QmlDesigner::Model *model)
{
AbstractView::modelAttached(model);
m_widget->effectComposerNodesModel()->loadModel();
QString currProjectPath = QmlDesigner::DocumentManager::currentProjectDirPath().toString();
// if starting a new project, clear the effect composer
if (m_currProjectPath != currProjectPath)
m_widget->effectComposerModel()->clear();
m_currProjectPath = currProjectPath;
m_widget->initView();
}
void EffectComposerView::modelAboutToBeDetached(QmlDesigner::Model *model)
{
AbstractView::modelAboutToBeDetached(model);
}
} // namespace EffectComposer

View File

@@ -0,0 +1,46 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "abstractview.h"
#include <coreplugin/icontext.h>
#include <QPointer>
namespace EffectComposer {
class EffectComposerWidget;
class EffectComposerContext : public Core::IContext
{
Q_OBJECT
public:
EffectComposerContext(QWidget *widget);
void contextHelp(const Core::IContext::HelpCallback &callback) const override;
};
class EffectComposerView : public QmlDesigner::AbstractView
{
public:
EffectComposerView(QmlDesigner::ExternalDependenciesInterface &externalDependencies);
~EffectComposerView() override;
bool hasWidget() const override;
QmlDesigner::WidgetInfo widgetInfo() override;
// AbstractView
void modelAttached(QmlDesigner::Model *model) override;
void modelAboutToBeDetached(QmlDesigner::Model *model) override;
private:
void customNotification(const AbstractView *view, const QString &identifier,
const QList<QmlDesigner::ModelNode> &nodeList, const QList<QVariant> &data) override;
QPointer<EffectComposerWidget> m_widget;
QString m_currProjectPath;
};
} // namespace EffectComposer

View File

@@ -0,0 +1,284 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "effectcomposerwidget.h"
#include "effectcomposercontextobject.h"
#include "effectcomposermodel.h"
#include "effectcomposernodesmodel.h"
#include "effectcomposerview.h"
#include "effectutils.h"
#include "propertyhandler.h"
//#include "qmldesigner/designercore/imagecache/midsizeimagecacheprovider.h"
#include "qmldesignerconstants.h"
#include "qmldesignerplugin.h"
#include "theme.h"
#include <coreplugin/icore.h>
#include <qmldesigner/documentmanager.h>
#include <qmldesigner/qmldesignerconstants.h>
#include <qmldesigner/qmldesignerplugin.h>
#include <studioquickwidget.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
#include <utils/algorithm.h>
#include <utils/async.h>
#include <utils/environment.h>
#include <utils/qtcassert.h>
#include <QHBoxLayout>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQuickItem>
#include <QTimer>
namespace EffectComposer {
static QString propertyEditorResourcesPath()
{
#ifdef SHARE_QML_PATH
if (Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE"))
return QLatin1String(SHARE_QML_PATH) + "/propertyEditorQmlSources";
#endif
return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toString();
}
EffectComposerWidget::EffectComposerWidget(EffectComposerView *view)
: m_effectComposerModel{new EffectComposerModel(this)}
, m_effectComposerNodesModel{new EffectComposerNodesModel(this)}
, m_effectComposerView(view)
, m_quickWidget{new StudioQuickWidget(this)}
{
setWindowTitle(tr("Effect Composer", "Title of effect composer widget"));
setMinimumWidth(250);
m_quickWidget->quickWidget()->installEventFilter(this);
// create the inner widget
m_quickWidget->quickWidget()->setObjectName(QmlDesigner::Constants::OBJECT_NAME_EFFECT_COMPOSER);
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));
auto layout = new QHBoxLayout(this);
layout->setContentsMargins({});
layout->setSpacing(0);
layout->addWidget(m_quickWidget.data());
setStyleSheet(QmlDesigner::Theme::replaceCssColors(
QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css"))));
QmlDesigner::QmlDesignerPlugin::trackWidgetFocusTime(this, QmlDesigner::Constants::EVENT_EFFECTCOMPOSER_TIME);
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("EffectComposerBackend");
map->setProperties({{"effectComposerNodesModel", QVariant::fromValue(m_effectComposerNodesModel.data())},
{"effectComposerModel", QVariant::fromValue(m_effectComposerModel.data())},
{"rootView", QVariant::fromValue(this)}});
connect(m_effectComposerModel.data(), &EffectComposerModel::nodesChanged, this, [this]() {
m_effectComposerNodesModel->updateCanBeAdded(m_effectComposerModel->uniformNames());
});
connect(m_effectComposerModel.data(), &EffectComposerModel::resourcesSaved,
this, [this](const QmlDesigner::TypeName &type, const Utils::FilePath &path) {
if (!m_importScan.timer) {
m_importScan.timer = new QTimer(this);
connect(m_importScan.timer, &QTimer::timeout,
this, &EffectComposerWidget::handleImportScanTimer);
}
if (m_importScan.timer->isActive() && !m_importScan.future.isFinished())
m_importScan.future.cancel();
m_importScan.counter = 0;
m_importScan.type = type;
m_importScan.path = path;
m_importScan.timer->start(100);
});
}
bool EffectComposerWidget::eventFilter(QObject *obj, QEvent *event)
{
Q_UNUSED(obj)
Q_UNUSED(event)
// TODO
return false;
}
void EffectComposerWidget::contextHelp(const Core::IContext::HelpCallback &callback) const
{
Q_UNUSED(callback)
}
StudioQuickWidget *EffectComposerWidget::quickWidget() const
{
return m_quickWidget.data();
}
QPointer<EffectComposerModel> EffectComposerWidget::effectComposerModel() const
{
return m_effectComposerModel;
}
QPointer<EffectComposerNodesModel> EffectComposerWidget::effectComposerNodesModel() const
{
return m_effectComposerNodesModel;
}
void EffectComposerWidget::addEffectNode(const QString &nodeQenPath)
{
m_effectComposerModel->addNode(nodeQenPath);
}
void EffectComposerWidget::focusSection(int section)
{
Q_UNUSED(section)
}
QRect EffectComposerWidget::screenRect() const
{
if (m_quickWidget && m_quickWidget->screen())
return m_quickWidget->screen()->availableGeometry();
return {};
}
QPoint EffectComposerWidget::globalPos(const QPoint &point) const
{
if (m_quickWidget)
return m_quickWidget->mapToGlobal(point);
return point;
}
QSize EffectComposerWidget::sizeHint() const
{
return {420, 420};
}
QString EffectComposerWidget::qmlSourcesPath()
{
#ifdef SHARE_QML_PATH
if (Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE"))
return QLatin1String(SHARE_QML_PATH) + "/effectComposerQmlSources";
#endif
return Core::ICore::resourcePath("qmldesigner/effectComposerQmlSources").toString();
}
void EffectComposerWidget::initView()
{
auto ctxObj = new EffectComposerContextObject(m_quickWidget->rootContext());
m_quickWidget->rootContext()->setContextObject(ctxObj);
m_backendModelNode.setup(m_effectComposerView->rootModelNode());
m_quickWidget->rootContext()->setContextProperty("anchorBackend", &m_backendAnchorBinding);
m_quickWidget->rootContext()->setContextProperty("modelNodeBackend", &m_backendModelNode);
m_quickWidget->rootContext()->setContextProperty("activeDragSuffix", "");
//TODO: Fix crash on macos
// m_quickWidget->engine()->addImageProvider("qmldesigner_thumbnails",
// new QmlDesigner::AssetImageProvider(
// QmlDesigner::QmlDesignerPlugin::imageCache()));
// init the first load of the QML UI elements
reloadQmlSource();
}
void EffectComposerWidget::openComposition(const QString &path)
{
m_compositionPath = path;
if (effectComposerModel()->hasUnsavedChanges())
QMetaObject::invokeMethod(quickWidget()->rootObject(), "promptToSaveBeforeOpen");
else
doOpenComposition();
}
void EffectComposerWidget::doOpenComposition()
{
effectComposerModel()->openComposition(m_compositionPath);
}
void EffectComposerWidget::reloadQmlSource()
{
const QString effectComposerQmlPath = qmlSourcesPath() + "/EffectComposer.qml";
QTC_ASSERT(QFileInfo::exists(effectComposerQmlPath), return);
m_quickWidget->setSource(QUrl::fromLocalFile(effectComposerQmlPath));
}
void EffectComposerWidget::handleImportScanTimer()
{
++m_importScan.counter;
if (m_importScan.counter == 1) {
// Rescan the effect import to update code model
auto modelManager = QmlJS::ModelManagerInterface::instance();
if (modelManager) {
QmlJS::PathsAndLanguages pathToScan;
pathToScan.maybeInsert(m_importScan.path);
m_importScan.future = ::Utils::asyncRun(&QmlJS::ModelManagerInterface::importScan,
modelManager->workingCopy(),
pathToScan, modelManager, true, true, true);
}
} else if (m_importScan.counter < 100) {
// We have to wait a while to ensure qmljs detects new files and updates its
// internal model. Then we force amend on rewriter to trigger qmljs snapshot update.
if (m_importScan.future.isCanceled() || m_importScan.future.isFinished())
m_importScan.counter = 100; // skip the timeout step
} else if (m_importScan.counter == 100) {
// Scanning is taking too long, abort
m_importScan.future.cancel();
m_importScan.timer->stop();
m_importScan.counter = 0;
} else if (m_importScan.counter == 101) {
if (m_effectComposerView->model() && m_effectComposerView->model()->rewriterView()) {
QmlDesigner::QmlDesignerPlugin::instance()->documentManager().resetPossibleImports();
m_effectComposerView->model()->rewriterView()->forceAmend();
}
} else if (m_importScan.counter == 102) {
if (m_effectComposerView->model()) {
// If type is in use, we have to reset puppet to update 2D view
if (!m_effectComposerView->allModelNodesOfType(
m_effectComposerView->model()->metaInfo(m_importScan.type)).isEmpty()) {
m_effectComposerView->resetPuppet();
}
}
} else if (m_importScan.counter >= 103) {
// Refresh property view by resetting selection if any selected node is of updated type
if (m_effectComposerView->model() && m_effectComposerView->hasSelectedModelNodes()) {
const auto nodes = m_effectComposerView->selectedModelNodes();
QmlDesigner::MetaInfoType metaType
= m_effectComposerView->model()->metaInfo(m_importScan.type).type();
bool match = false;
for (const QmlDesigner::ModelNode &node : nodes) {
if (node.metaInfo().type() == metaType) {
match = true;
break;
}
}
if (match) {
m_effectComposerView->clearSelectedModelNodes();
m_effectComposerView->setSelectedModelNodes(nodes);
}
}
m_importScan.timer->stop();
m_importScan.counter = 0;
}
}
} // namespace EffectComposer

View File

@@ -0,0 +1,83 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <qmldesigner/components/propertyeditor/qmlanchorbindingproxy.h>
#include <qmldesigner/components/propertyeditor/qmlmodelnodeproxy.h>
#include <coreplugin/icontext.h>
#include <QFrame>
#include <QFuture>
class StudioQuickWidget;
QT_BEGIN_NAMESPACE
class QTimer;
QT_END_NAMESPACE
namespace EffectComposer {
class EffectComposerView;
class EffectComposerModel;
class EffectComposerNodesModel;
class EffectComposerWidget : public QFrame
{
Q_OBJECT
public:
EffectComposerWidget(EffectComposerView *view);
~EffectComposerWidget() = default;
void contextHelp(const Core::IContext::HelpCallback &callback) const;
static QString qmlSourcesPath();
void clearSearchFilter();
void delayedUpdateModel();
void updateModel();
void initView();
void openComposition(const QString &path);
StudioQuickWidget *quickWidget() const;
QPointer<EffectComposerModel> effectComposerModel() const;
QPointer<EffectComposerNodesModel> effectComposerNodesModel() const;
Q_INVOKABLE void addEffectNode(const QString &nodeQenPath);
Q_INVOKABLE void focusSection(int section);
Q_INVOKABLE void doOpenComposition();
Q_INVOKABLE QRect screenRect() const;
Q_INVOKABLE QPoint globalPos(const QPoint &point) const;
QSize sizeHint() const override;
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
private:
void reloadQmlSource();
void handleImportScanTimer();
QPointer<EffectComposerModel> m_effectComposerModel;
QPointer<EffectComposerNodesModel> m_effectComposerNodesModel;
QPointer<EffectComposerView> m_effectComposerView;
QPointer<StudioQuickWidget> m_quickWidget;
QmlDesigner::QmlModelNodeProxy m_backendModelNode;
QmlDesigner::QmlAnchorBindingProxy m_backendAnchorBinding;
struct ImportScanData {
QFuture<void> future;
int counter = 0;
QTimer *timer = nullptr;
QmlDesigner::TypeName type;
Utils::FilePath path;
};
ImportScanData m_importScan;
QString m_compositionPath;
};
} // namespace EffectComposer

View File

@@ -0,0 +1,67 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "effectnode.h"
#include "compositionnode.h"
#include "uniform.h"
#include <QDir>
#include <QFileInfo>
namespace EffectComposer {
EffectNode::EffectNode(const QString &qenPath)
: m_qenPath(qenPath)
{
const QFileInfo fileInfo = QFileInfo(qenPath);
QString iconPath = QStringLiteral("%1/icon/%2.svg").arg(fileInfo.absolutePath(),
fileInfo.baseName());
if (!QFileInfo::exists(iconPath)) {
QDir parentDir = QDir(fileInfo.absolutePath());
parentDir.cdUp();
iconPath = QStringLiteral("%1/%2").arg(parentDir.path(), "placeholder.svg");
}
m_iconPath = QUrl::fromLocalFile(iconPath);
CompositionNode node({}, qenPath);
m_name = node.name();
m_description = node.description();
const QList<Uniform *> uniforms = node.uniforms();
for (const Uniform *uniform : uniforms)
m_uniformNames.insert(uniform->name());
}
QString EffectNode::name() const
{
return m_name;
}
QString EffectNode::description() const
{
return m_description;
}
QString EffectNode::qenPath() const
{
return m_qenPath;
}
void EffectNode::setCanBeAdded(bool enabled)
{
if (enabled != m_canBeAdded) {
m_canBeAdded = enabled;
emit canBeAddedChanged();
}
}
bool EffectNode::hasUniform(const QString &name)
{
return m_uniformNames.contains(name);
}
} // namespace EffectComposer

View File

@@ -0,0 +1,46 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <QObject>
#include <QSet>
#include <QUrl>
namespace EffectComposer {
class EffectNode : public QObject
{
Q_OBJECT
Q_PROPERTY(QString nodeName MEMBER m_name CONSTANT)
Q_PROPERTY(QString nodeDescription MEMBER m_description CONSTANT)
Q_PROPERTY(QUrl nodeIcon MEMBER m_iconPath CONSTANT)
Q_PROPERTY(QString nodeQenPath MEMBER m_qenPath CONSTANT)
Q_PROPERTY(bool canBeAdded MEMBER m_canBeAdded NOTIFY canBeAddedChanged)
public:
EffectNode(const QString &qenPath);
QString name() const;
QString description() const;
QString qenPath() const;
void setCanBeAdded(bool enabled);
bool hasUniform(const QString &name);
signals:
void canBeAddedChanged();
private:
QString m_name;
QString m_description;
QString m_qenPath;
QUrl m_iconPath;
bool m_canBeAdded = true;
QSet<QString> m_uniformNames;
};
} // namespace EffectComposer

View File

@@ -0,0 +1,23 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "effectnodescategory.h"
namespace EffectComposer {
EffectNodesCategory::EffectNodesCategory(const QString &name, const QList<EffectNode *> &nodes)
: m_name(name),
m_categoryNodes(nodes) {}
QString EffectNodesCategory::name() const
{
return m_name;
}
QList<EffectNode *> EffectNodesCategory::nodes() const
{
return m_categoryNodes;
}
} // namespace EffectComposer

View File

@@ -0,0 +1,31 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "effectnode.h"
#include <QObject>
namespace EffectComposer {
class EffectNodesCategory : public QObject
{
Q_OBJECT
Q_PROPERTY(QString categoryName MEMBER m_name CONSTANT)
Q_PROPERTY(QList<EffectNode *> categoryNodes MEMBER m_categoryNodes CONSTANT)
public:
EffectNodesCategory(const QString &name, const QList<EffectNode *> &nodes);
QString name() const;
QList<EffectNode *> nodes() const;
private:
QString m_name;
QList<EffectNode *> m_categoryNodes;
};
} // namespace EffectComposer

View File

@@ -0,0 +1,34 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "effectutils.h"
#include <coreplugin/icore.h>
#include <QJsonArray>
namespace EffectComposer {
QString EffectUtils::codeFromJsonArray(const QJsonArray &codeArray)
{
if (codeArray.isEmpty())
return {};
QString codeString;
for (const auto &element : codeArray)
codeString += element.toString() + '\n';
codeString.chop(1); // Remove last '\n'
return codeString;
}
QString EffectUtils::nodesSourcesPath()
{
#ifdef SHARE_QML_PATH
if (Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE"))
return QLatin1String(SHARE_QML_PATH) + "/effectComposerNodes";
#endif
return Core::ICore::resourcePath("qmldesigner/effectComposerNodes").toString();
}
} // namespace EffectComposer

View File

@@ -0,0 +1,23 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <QString>
QT_FORWARD_DECLARE_CLASS(QJsonArray)
namespace EffectComposer {
class EffectUtils
{
public:
EffectUtils() = delete;
static QString codeFromJsonArray(const QJsonArray &codeArray);
static QString nodesSourcesPath();
};
} // namespace EffectComposer

View File

@@ -0,0 +1,10 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "propertyhandler.h"
namespace EffectComposer {
QQmlPropertyMap g_propertyData;
}

View File

@@ -0,0 +1,15 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#pragma once
#include <QQmlPropertyMap>
namespace EffectComposer {
// This will be used for binding dynamic properties
// changes between C++ and QML.
extern QQmlPropertyMap g_propertyData;
}

View File

@@ -0,0 +1,91 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "shaderfeatures.h"
#include <QStringList>
#include <QDebug>
namespace EffectComposer {
ShaderFeatures::ShaderFeatures()
{
}
// Browse the shaders and check which features are used in them.
void ShaderFeatures::update(const QString &vs, const QString &fs, const QString &qml)
{
QStringList vsList = vs.split("\n");
QStringList fsList = fs.split("\n");
const QStringList code = vsList + fsList;
Features newFeatures = {};
m_gridMeshWidth = 1;
m_gridMeshHeight = 1;
for (const QString &line : code)
checkLine(line, newFeatures);
// iTime may also be used in QML side, without being used in shaders.
// In this case enable the time helpers creation.
if (qml.contains("iTime"))
newFeatures.setFlag(Time, true);
if (newFeatures != m_enabledFeatures)
m_enabledFeatures = newFeatures;
}
bool ShaderFeatures::enabled(ShaderFeatures::Feature feature) const
{
return m_enabledFeatures.testFlag(feature);
}
void ShaderFeatures::checkLine(const QString &line, Features &features)
{
if (line.contains("iTime"))
features.setFlag(Time, true);
if (line.contains("iFrame"))
features.setFlag(Frame, true);
if (line.contains("iResolution"))
features.setFlag(Resolution, true);
if (line.contains("iSource"))
features.setFlag(Source, true);
if (line.contains("iMouse"))
features.setFlag(Mouse, true);
if (line.contains("fragCoord"))
features.setFlag(FragCoord, true);
if (line.contains("@mesh")) {
// Get the mesh size, remove "@mesh"
QString l = line.trimmed().sliced(5);
QStringList list = l.split(QLatin1Char(','));
if (list.size() >= 2) {
int w = list.at(0).trimmed().toInt();
int h = list.at(1).trimmed().toInt();
// Set size to max values
m_gridMeshWidth = std::max(m_gridMeshWidth, w);
m_gridMeshHeight = std::max(m_gridMeshHeight, h);
}
// If is bigger than default (1, 1), set the feature
if (m_gridMeshWidth > 1 || m_gridMeshHeight > 1)
features.setFlag(GridMesh, true);
}
if (line.contains("@blursources"))
features.setFlag(BlurSources, true);
}
int ShaderFeatures::gridMeshHeight() const
{
return m_gridMeshHeight;
}
int ShaderFeatures::gridMeshWidth() const
{
return m_gridMeshWidth;
}
} // namespace EffectComposer

View File

@@ -0,0 +1,44 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#pragma once
#include <QFlags>
#include <QString>
namespace EffectComposer {
class ShaderFeatures
{
public:
enum Feature {
Time = 1 << 0,
Frame = 1 << 1,
Resolution = 1 << 2,
Source = 1 << 3,
Mouse = 1 << 4,
FragCoord = 1 << 5,
GridMesh = 1 << 6,
BlurSources = 1 << 7
};
Q_DECLARE_FLAGS(Features, Feature)
ShaderFeatures();
void update(const QString &vs, const QString &fs, const QString &qml);
bool enabled(ShaderFeatures::Feature feature) const;
int gridMeshWidth() const;
int gridMeshHeight() const;
private:
void checkLine(const QString &line, ShaderFeatures::Features &features);
ShaderFeatures::Features m_enabledFeatures;
int m_gridMeshWidth = 1;
int m_gridMeshHeight = 1;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(ShaderFeatures::Features)
} // namespace EffectComposer

View File

@@ -0,0 +1,191 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "syntaxhighlighterdata.h"
namespace EffectComposer {
static constexpr QByteArrayView shader_arg_names[] {
{ "gl_Position" },
{ "qt_MultiTexCoord0" },
{ "qt_Vertex" },
{ "qt_Matrix" },
{ "qt_Opacity" },
{ "vertCoord" },
{ "fragCoord" },
{ "texCoord" },
{ "fragColor" },
{ "iMouse" },
{ "iResolution" },
{ "iTime" },
{ "iFrame" },
{ "iSource" },
{ "iSourceBlur1" },
{ "iSourceBlur2" },
{ "iSourceBlur3" },
{ "iSourceBlur4" },
{ "iSourceBlur5" },
{ "iSourceBlur6" }
};
static constexpr QByteArrayView shader_tag_names[] {
{ "@main" },
{ "@nodes" },
{ "@mesh" },
{ "@blursources" },
{ "@requires" }
};
// From https://registry.khronos.org/OpenGL/specs/gl/GLSLangSpec.4.40.pdf
// Not including functions only available with compatibility profile
static constexpr QByteArrayView shader_function_names[] {
{ "radians()" },
{ "degrees()" },
{ "sin()" },
{ "cos()" },
{ "tan()" },
{ "asin()" },
{ "acos()" },
{ "atan()" },
{ "sinh()" },
{ "cosh()" },
{ "tanh()" },
{ "asinh()" },
{ "acosh()" },
{ "atanh()" },
{ "pow()" },
{ "exp()" },
{ "log()" },
{ "exp2()" },
{ "log2()" },
{ "sqrt()" },
{ "inversesqrt()" },
{ "abs()" },
{ "sign()" },
{ "floor()" },
{ "trunc()" },
{ "round()" },
{ "roundEven()" },
{ "ceil()" },
{ "fract()" },
{ "mod()" },
{ "modf()" },
{ "min()" },
{ "max()" },
{ "clamp()" },
{ "mix()" },
{ "step()" },
{ "smoothstep()" },
{ "isnan()" },
{ "isinf()" },
{ "floatBitsToInt()" },
{ "intBitsToFloat()" },
{ "fma()" },
{ "frexp()" },
{ "ldexp()" },
{ "packUnorm2x16()" },
{ "packSnorm2x16()" },
{ "packUnorm4x8()" },
{ "packSnorm4x8()" },
{ "unpackUnorm2x16()" },
{ "unpackSnorm2x16()" },
{ "unpackUnorm4x8()" },
{ "unpackSnorm4x8()" },
//{ "packDouble2x32()" }, // Not supported in HLSL
//{ "unpackDouble2x32()" },
{ "packHalf2x16()" },
{ "unpackHalf2x16()" },
{ "length()" },
{ "distance()" },
{ "dot()" },
{ "cross()" },
{ "normalize()" },
{ "faceforward()" },
{ "reflect()" },
{ "refract()" },
{ "matrixCompMult()" },
{ "outerProduct()" },
{ "transpose()" },
{ "determinant()" },
{ "inverse()" },
{ "lessThan()" },
{ "lessThanEqual()" },
{ "greaterThan()" },
{ "greaterThanEqual()" },
{ "equal()" },
{ "notEqual()" },
{ "any()" },
{ "all()" },
{ "not()" },
//{ "uaddCarry()" }, // Extended arithmetic is only available from ESSL 310
//{ "usubBorrow()" },
//{ "umulExtended()" },
//{ "imulExtended()" },
{ "bitfieldExtract()" },
{ "bitfieldInsert()" },
{ "bitfieldReverse()" },
{ "bitCount()" },
{ "findLSB()" },
{ "findMSB()" },
{ "textureSize()" },
//{ "textureQueryLod()" }, // ImageQueryLod is only supported on MSL 2.2 and up.
//{ "textureQueryLevels()" }, // textureQueryLevels not supported in ES profile.
{ "texture()" },
{ "textureProj()" },
{ "textureLod()" },
{ "textureOffset()" },
{ "texelFetch()" },
{ "texelFetchOffset()" },
{ "textureProjOffset()" },
{ "textureLodOffset()" },
{ "textureProjLod()" },
{ "textureProjLodOffset()" },
{ "textureGrad()" },
{ "textureGradOffset()" },
{ "textureProjGrad()" },
{ "textureProjGradOffset()" },
//{ "textureGather()" }, // textureGather requires ESSL 310.
//{ "textureGatherOffset()" },
//{ "textureGatherOffsets()" },
//{ "atomicCounterIncrement()" }, // 'atomic counter types' : not allowed when using GLSL for Vulkan
//{ "atomicCounterDecrement()" },
//{ "atomicCounter()" },
//{ "atomicAdd()" }, // HLSL: interlocked targets must be groupshared or UAV elements
//{ "atomicMin()" },
//{ "atomicMax()" },
//{ "atomicAnd()" },
//{ "atomicOr()" },
//{ "atomicXor()" },
//{ "atomicExchange()" },
//{ "atomicCompSwap()" },
{ "dFdx()" },
{ "dFdy()" },
{ "fwidth()" }
//{ "interpolateAtCentroid()" }, // Pull-model interpolation requires MSL 2.3.
//{ "interpolateAtSample()" },
//{ "interpolateAtOffset()" }
};
SyntaxHighlighterData::SyntaxHighlighterData()
{
}
QList<QByteArrayView> SyntaxHighlighterData::reservedArgumentNames()
{
return { std::begin(shader_arg_names), std::end(shader_arg_names) };
}
QList<QByteArrayView> SyntaxHighlighterData::reservedTagNames()
{
return { std::begin(shader_tag_names), std::end(shader_tag_names) };
}
QList<QByteArrayView> SyntaxHighlighterData::reservedFunctionNames()
{
return { std::begin(shader_function_names), std::end(shader_function_names) };
}
} // namespace EffectComposer

View File

@@ -0,0 +1,23 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#pragma once
#include <QByteArrayView>
#include <QList>
namespace EffectComposer {
class SyntaxHighlighterData
{
public:
SyntaxHighlighterData();
static QList<QByteArrayView> reservedArgumentNames();
static QList<QByteArrayView> reservedTagNames();
static QList<QByteArrayView> reservedFunctionNames();
};
} // namespace EffectComposer

View File

@@ -0,0 +1,345 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "uniform.h"
#include <qmldesignerplugin.h>
#include "propertyhandler.h"
#include <modelnodeoperations.h>
#include <QColor>
#include <QJsonObject>
#include <QVector2D>
namespace EffectComposer {
Uniform::Uniform(const QString &effectName, const QJsonObject &propObj, const QString &qenPath)
: m_qenPath(qenPath)
{
QString value, defaultValue, minValue, maxValue;
m_name = propObj.value("name").toString();
m_description = propObj.value("description").toString();
m_type = Uniform::typeFromString(propObj.value("type").toString());
defaultValue = propObj.value("defaultValue").toString();
m_displayName = propObj.value("displayName").toString();
if (m_displayName.isEmpty())
m_displayName = m_name;
value = propObj.contains("value") ? propObj.value("value").toString() : defaultValue;
if (m_type == Type::Sampler) {
if (!defaultValue.isEmpty())
defaultValue = getResourcePath(effectName, defaultValue, qenPath);
if (!value.isEmpty())
value = getResourcePath(effectName, value, qenPath);
if (value.isEmpty())
value = defaultValue;
if (propObj.contains("enableMipmap"))
m_enableMipmap = getBoolValue(propObj.value("enableMipmap"), false);
// Update the mipmap property
QString mipmapProperty = mipmapPropertyName(m_name);
g_propertyData[mipmapProperty] = m_enableMipmap;
}
m_customValue = propObj.value("customValue").toString();
m_useCustomValue = getBoolValue(propObj.value("useCustomValue"), false);
minValue = propObj.value("minValue").toString();
maxValue = propObj.value("maxValue").toString();
setValueData(value, defaultValue, minValue, maxValue);
m_backendValue = new QmlDesigner::PropertyEditorValue(this);
m_backendValue->setValue(value);
}
Uniform::Type Uniform::type() const
{
return m_type;
}
// String representation of the type for qml
QString Uniform::typeName() const
{
return Uniform::stringFromType(m_type);
}
QVariant Uniform::value() const
{
return m_value;
}
QVariant Uniform::backendValue() const
{
return QVariant::fromValue(m_backendValue);
}
void Uniform::setValue(const QVariant &newValue)
{
if (m_value != newValue) {
m_value = newValue;
emit uniformValueChanged();
}
}
QVariant Uniform::defaultValue() const
{
return m_defaultValue;
}
QVariant Uniform::minValue() const
{
return m_minValue;
}
QVariant Uniform::maxValue() const
{
return m_maxValue;
}
QString Uniform::name() const
{
return m_name;
}
QString Uniform::description() const
{
return m_description;
}
QString Uniform::displayName() const
{
return m_displayName;
}
QString Uniform::customValue() const
{
return m_customValue;
}
void Uniform::setCustomValue(const QString &newCustomValue)
{
m_customValue = newCustomValue;
}
bool Uniform::useCustomValue() const
{
return m_useCustomValue;
}
bool Uniform::enabled() const
{
return m_enabled;
}
void Uniform::setEnabled(bool newEnabled)
{
m_enabled = newEnabled;
}
bool Uniform::enableMipmap() const
{
return m_enableMipmap;
}
// Returns name for image mipmap property.
// e.g. "myImage" -> "myImageMipmap".
QString Uniform::mipmapPropertyName(const QString &name) const
{
QString simplifiedName = name.simplified();
simplifiedName = simplifiedName.remove(' ');
simplifiedName += "Mipmap";
return simplifiedName;
}
// Returns the boolean value of QJsonValue. It can be either boolean
// (true, false) or string ("true", "false"). Returns the defaultValue
// if QJsonValue is undefined, empty, or some other type.
bool Uniform::getBoolValue(const QJsonValue &jsonValue, bool defaultValue)
{
if (jsonValue.isBool())
return jsonValue.toBool();
if (jsonValue.isString())
return jsonValue.toString().toLower() == "true";
return defaultValue;
}
// Returns the path for a shader resource
// Used with sampler types
QString Uniform::getResourcePath(const QString &effectName, const QString &value, const QString &qenPath) const
{
QString filePath = value;
if (qenPath.isEmpty()) {
const Utils::FilePath effectsResDir = QmlDesigner::ModelNodeOperations::getEffectsImportDirectory();
return effectsResDir.pathAppended(effectName).pathAppended(value).toString();
} else {
QDir dir(m_qenPath);
dir.cdUp();
QString absPath = dir.absoluteFilePath(filePath);
absPath = QDir::cleanPath(absPath);
absPath = QUrl::fromLocalFile(absPath).toString();
return absPath;
}
}
// Validation and setting values
void Uniform::setValueData(const QString &value, const QString &defaultValue,
const QString &minValue, const QString &maxValue)
{
m_value = value.isEmpty() ? getInitializedVariant(false) : valueStringToVariant(value);
m_defaultValue = defaultValue.isEmpty() ? getInitializedVariant(false)
: valueStringToVariant(defaultValue);
m_minValue = minValue.isEmpty() ? getInitializedVariant(false) : valueStringToVariant(minValue);
m_maxValue = maxValue.isEmpty() ? getInitializedVariant(true) : valueStringToVariant(maxValue);
}
// Initialize the value variant with correct type
QVariant Uniform::getInitializedVariant(bool maxValue)
{
switch (m_type) {
case Uniform::Type::Bool:
return maxValue ? true : false;
case Uniform::Type::Int:
return maxValue ? 100 : 0;
case Uniform::Type::Float:
return maxValue ? 1.0 : 0.0;
case Uniform::Type::Vec2:
return maxValue ? QVector2D(1.0, 1.0) : QVector2D(0.0, 0.0);
case Uniform::Type::Vec3:
return maxValue ? QVector3D(1.0, 1.0, 1.0) : QVector3D(0.0, 0.0, 0.0);
case Uniform::Type::Vec4:
return maxValue ? QVector4D(1.0, 1.0, 1.0, 1.0) : QVector4D(0.0, 0.0, 0.0, 0.0);
case Uniform::Type::Color:
return maxValue ? QColor::fromRgbF(1.0f, 1.0f, 1.0f, 1.0f) : QColor::fromRgbF(0.0f, 0.0f, 0.0f, 0.0f);
default:
return QVariant();
}
}
QVariant Uniform::valueStringToVariant(const QString &value)
{
QVariant variant;
switch (m_type) {
case Type::Bool:
variant = (value == "true");
break;
case Type::Int:
case Type::Float:
variant = value;
break;
case Type::Vec2: {
QStringList list = value.split(QLatin1Char(','));
if (list.size() >= 2)
variant = QVector2D(list.at(0).toDouble(), list.at(1).toDouble());
}
break;
case Type::Vec3: {
QStringList list = value.split(QLatin1Char(','));
if (list.size() >= 3)
variant = QVector3D(list.at(0).toDouble(), list.at(1).toDouble(), list.at(2).toDouble());
}
break;
case Type::Vec4: {
QStringList list = value.split(QLatin1Char(','));
if (list.size() >= 4)
variant = QVector4D(list.at(0).toDouble(), list.at(1).toDouble(),
list.at(2).toDouble(), list.at(3).toDouble());
}
break;
case Type::Color: {
QStringList list = value.split(QLatin1Char(','));
if (list.size() >= 4)
variant = QColor::fromRgbF(list.at(0).toDouble(), list.at(1).toDouble(),
list.at(2).toDouble(), list.at(3).toDouble());
}
break;
case Type::Sampler:
variant = value;
break;
case Uniform::Type::Define:
variant = value;
break;
}
return variant;
}
QString Uniform::stringFromType(Uniform::Type type, bool isShader)
{
if (type == Type::Bool)
return "bool";
else if (type == Type::Int)
return "int";
else if (type == Type::Float)
return "float";
else if (type == Type::Vec2)
return "vec2";
else if (type == Type::Vec3)
return "vec3";
else if (type == Type::Vec4)
return "vec4";
else if (type == Type::Color)
return isShader ? QString("vec4") : QString("color");
else if (type == Type::Sampler)
return "sampler2D";
else if (type == Type::Define)
return "define";
qWarning() << QString("Unknown type");
return "float";
}
Uniform::Type Uniform::typeFromString(const QString &typeString)
{
if (typeString == "bool")
return Uniform::Type::Bool;
else if (typeString == "int")
return Uniform::Type::Int;
else if (typeString == "float")
return Uniform::Type::Float;
else if (typeString == "vec2")
return Uniform::Type::Vec2;
else if (typeString == "vec3")
return Uniform::Type::Vec3;
else if (typeString == "vec4")
return Uniform::Type::Vec4;
else if (typeString == "color")
return Uniform::Type::Color;
else if (typeString == "sampler2D" || typeString == "image") //TODO: change image to sample2D in all QENs
return Uniform::Type::Sampler;
else if (typeString == "define")
return Uniform::Type::Define;
qWarning() << QString("Unknown type: %1").arg(typeString).toLatin1();
return Uniform::Type::Float;
}
QString Uniform::typeToProperty(Uniform::Type type)
{
if (type == Uniform::Type::Bool)
return "bool";
else if (type == Uniform::Type::Int)
return "int";
else if (type == Uniform::Type::Float)
return "real";
else if (type == Uniform::Type::Vec2)
return "point";
else if (type == Uniform::Type::Vec3)
return "vector3d";
else if (type == Uniform::Type::Vec4)
return "vector4d";
else if (type == Uniform::Type::Color)
return "color";
else if (type == Uniform::Type::Sampler || type == Uniform::Type::Define)
return "var";
qWarning() << QString("Unhandled const variable type: %1").arg(int(type)).toLatin1();
return QString();
}
} // namespace EffectComposer

View File

@@ -0,0 +1,112 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <QObject>
#include <QVariant>
#include <qmldesigner/components/propertyeditor/propertyeditorvalue.h>
QT_FORWARD_DECLARE_CLASS(QColor)
QT_FORWARD_DECLARE_CLASS(QJsonObject)
QT_FORWARD_DECLARE_CLASS(QVector2D)
namespace EffectComposer {
class Uniform : public QObject
{
Q_OBJECT
Q_PROPERTY(QString uniformName MEMBER m_displayName CONSTANT)
Q_PROPERTY(QString uniformType READ typeName CONSTANT)
Q_PROPERTY(QString uniformDescription READ description CONSTANT)
Q_PROPERTY(QVariant uniformValue READ value WRITE setValue NOTIFY uniformValueChanged)
Q_PROPERTY(QVariant uniformBackendValue READ backendValue NOTIFY uniformBackendValueChanged)
Q_PROPERTY(QVariant uniformMinValue MEMBER m_minValue CONSTANT)
Q_PROPERTY(QVariant uniformMaxValue MEMBER m_maxValue CONSTANT)
Q_PROPERTY(QVariant uniformDefaultValue MEMBER m_defaultValue CONSTANT)
Q_PROPERTY(QVariant uniformUseCustomValue MEMBER m_useCustomValue CONSTANT)
public:
enum class Type
{
Bool,
Int,
Float,
Vec2,
Vec3,
Vec4,
Color,
Sampler,
Define
};
Uniform(const QString &effectName, const QJsonObject &props, const QString &qenPath);
Type type() const;
QString typeName() const;
QVariant value() const;
void setValue(const QVariant &newValue);
QVariant backendValue() const;
QVariant defaultValue() const;
QVariant minValue() const;
QVariant maxValue() const;
QString name() const;
QString description() const;
QString displayName() const;
QString customValue() const;
void setCustomValue(const QString &newCustomValue);
bool useCustomValue() const;
bool enabled() const;
void setEnabled(bool newEnabled);
bool enableMipmap() const;
static QString stringFromType(Uniform::Type type, bool isShader = false);
static Uniform::Type typeFromString(const QString &typeString);
static QString typeToProperty(Uniform::Type type);
signals:
void uniformValueChanged();
void uniformBackendValueChanged();
private:
QString mipmapPropertyName(const QString &name) const;
bool getBoolValue(const QJsonValue &jsonValue, bool defaultValue);
QString getResourcePath(const QString &effectName, const QString &value, const QString &qenPath) const;
void setValueData(const QString &value, const QString &defaultValue,
const QString &minValue, const QString &maxValue);
QVariant getInitializedVariant(bool maxValue);
QVariant valueStringToVariant(const QString &value);
QString m_qenPath;
Type m_type;
QVariant m_value;
QVariant m_defaultValue;
QVariant m_minValue;
QVariant m_maxValue;
QString m_name;
QString m_displayName;
QString m_description;
QString m_customValue;
bool m_useCustomValue = false;
bool m_enabled = true;
bool m_enableMipmap = false;
QmlDesigner::PropertyEditorValue *m_backendValue = nullptr;
bool operator==(const Uniform &rhs) const noexcept
{
return this->m_name == rhs.m_name;
}
};
} // namespace EffectComposer