forked from qt-creator/qt-creator
QmlDesigner: Allow saving created effect nodes for later use
Effect nodes can be added to "effect node library", which is stored in user's documents folder. Nodes in the library are available via "Add Effect" combobox, where they are shown under custom category. Fixes: QDS-14132 Change-Id: I753eeaffbfa9bf0f45d3dfcf643b52a86141f3be Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io> Reviewed-by: Ali Kianian <ali.kianian@qt.io>
This commit is contained in:
@@ -14,6 +14,10 @@ Rectangle {
|
||||
border.color: StudioTheme.Values.themeControlOutline
|
||||
color: StudioTheme.Values.themeSectionHeadBackground
|
||||
|
||||
property alias text: textLabel.text
|
||||
property alias acceptButtonLabel: acceptButton.text
|
||||
property alias cancelButtonLabel: cancelButton.text
|
||||
|
||||
signal accepted()
|
||||
signal canceled()
|
||||
|
||||
@@ -29,13 +33,13 @@ Rectangle {
|
||||
width: parent.width
|
||||
height: 50
|
||||
Text {
|
||||
id: textLabel
|
||||
anchors.centerIn: parent
|
||||
color: StudioTheme.Values.themeTextColor
|
||||
font.bold: true
|
||||
font.pixelSize: StudioTheme.Values.baseFontSize
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: qsTr("The property is in use in the shader code.\nAre you sure you want to remove it?")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +68,7 @@ Rectangle {
|
||||
id: acceptButton
|
||||
width: 80
|
||||
height: 30
|
||||
text: qsTr("Remove")
|
||||
text: qsTr("Accept")
|
||||
padding: 4
|
||||
|
||||
onClicked: {
|
@@ -60,56 +60,118 @@ HelperWidgets.Section {
|
||||
leftPadding: StudioTheme.Values.toolbarSpacing
|
||||
}
|
||||
|
||||
Item {
|
||||
id: nodeConfirmFormParent
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
id: warningText
|
||||
id: infoText
|
||||
|
||||
visible: false
|
||||
height: 60
|
||||
width: root.width
|
||||
text: qsTr("A node with this name already exists.\nSuffix was added to make the name unique.")
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: StudioTheme.Values.themeWarning
|
||||
readOnly: true
|
||||
wrapMode: TextEdit.WordWrap
|
||||
|
||||
function showNodeExistsWarning(enable)
|
||||
{
|
||||
infoText.text = qsTr("An effect with this name already exists.\nSuffix was added to make the name unique.")
|
||||
infoTimer.restart()
|
||||
infoText.visible = enable
|
||||
infoText.color = StudioTheme.Values.themeWarning
|
||||
}
|
||||
|
||||
function showNodeAddedToLibraryInfo(message)
|
||||
{
|
||||
let errorTag = "@ERROR@"
|
||||
if (message.startsWith(errorTag)) {
|
||||
infoText.text = message.substring(errorTag.length)
|
||||
infoText.color = StudioTheme.Values.themeError
|
||||
} else {
|
||||
infoText.text = message
|
||||
infoText.color = StudioTheme.Values.themeInteraction
|
||||
}
|
||||
infoTimer.restart()
|
||||
infoText.visible = message !== ""
|
||||
}
|
||||
|
||||
function showNeedRenameInfo()
|
||||
{
|
||||
infoText.text = qsTr("A built-in effect with this name already exists in the library.\nPlease rename the effect before adding it to the library.")
|
||||
infoText.visible = true
|
||||
infoText.color = StudioTheme.Values.themeWarning
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (warningText.visible) {
|
||||
warningTimer.running = true
|
||||
if (infoText.visible)
|
||||
root.expanded = true // so that warning is visible
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: warningTimer
|
||||
id: infoTimer
|
||||
|
||||
interval: 12000
|
||||
repeat: false
|
||||
running: false
|
||||
|
||||
onTriggered: {
|
||||
warningText.visible = false
|
||||
warningTimer.running = false
|
||||
infoText.visible = false
|
||||
infoTimer.running = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConfirmPropertyRemoveForm {
|
||||
id: confirmRemoveForm
|
||||
ConfirmForm {
|
||||
id: confirmForm
|
||||
|
||||
property int uniformIndex: -1
|
||||
property int confirmIndex: -1
|
||||
property bool confirmingRemove: false
|
||||
|
||||
width: root.width - StudioTheme.Values.scrollBarThicknessHover - 8
|
||||
visible: false
|
||||
|
||||
onHeightChanged: root.emitEnsure(confirmRemoveForm)
|
||||
onVisibleChanged: root.emitEnsure(confirmRemoveForm)
|
||||
onHeightChanged: root.emitEnsure(confirmForm)
|
||||
onVisibleChanged: root.emitEnsure(confirmForm)
|
||||
|
||||
onAccepted: {
|
||||
confirmRemoveForm.parent = root
|
||||
nodeUniformsModel.remove(confirmRemoveForm.uniformIndex)
|
||||
function showConfirmRemove(formParent, uniformIndex)
|
||||
{
|
||||
confirmForm.parent = formParent
|
||||
confirmForm.confirmIndex = uniformIndex
|
||||
text = qsTr("The property is in use in the shader code.\nAre you sure you want to remove it?")
|
||||
acceptButtonLabel = qsTr("Remove")
|
||||
confirmForm.confirmingRemove = true
|
||||
confirmForm.visible = true
|
||||
}
|
||||
|
||||
onCanceled: confirmRemoveForm.parent = root
|
||||
function showConfirmLibraryAdd(nodeIndex)
|
||||
{
|
||||
confirmForm.parent = nodeConfirmFormParent
|
||||
confirmForm.confirmIndex = nodeIndex
|
||||
text = qsTr("The effect is already added into the library.\nAre you sure you want to update it?")
|
||||
acceptButtonLabel = qsTr("Update")
|
||||
confirmForm.confirmingRemove = false
|
||||
confirmForm.visible = true
|
||||
}
|
||||
|
||||
function clear()
|
||||
{
|
||||
confirmForm.visible = false
|
||||
confirmForm.parent = root
|
||||
}
|
||||
|
||||
onAccepted: {
|
||||
confirmForm.clear()
|
||||
if (confirmForm.confirmingRemove)
|
||||
nodeUniformsModel.remove(confirmForm.confirmIndex)
|
||||
else
|
||||
infoText.showNodeAddedToLibraryInfo(root.backendModel.addNodeToLibraryNode(confirmForm.confirmIndex))
|
||||
}
|
||||
|
||||
onCanceled: confirmForm.clear()
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -149,7 +211,7 @@ HelperWidgets.Section {
|
||||
|
||||
onEditingFinished: {
|
||||
nameEditField.visible = false
|
||||
warningText.visible = !root.backendModel.changeNodeName(modelIndex, nameEditText.text)
|
||||
infoText.showNodeExistsWarning(!root.backendModel.changeNodeName(modelIndex, nameEditText.text))
|
||||
}
|
||||
|
||||
onActiveFocusChanged: {
|
||||
@@ -180,14 +242,19 @@ HelperWidgets.Section {
|
||||
iconColor: StudioTheme.Values.themeTextColor
|
||||
iconScale: nameEditButton.containsMouse ? 1.2 : 1
|
||||
implicitWidth: width
|
||||
tooltip: qsTr("Edit effect node name")
|
||||
tooltip: qsTr("Edit effect name")
|
||||
|
||||
onPressed: (event) => {
|
||||
function handlePress()
|
||||
{
|
||||
nameEditText.text = nodeName
|
||||
nameEditText.initializing = true
|
||||
nameEditField.visible = true
|
||||
nameEditText.forceActiveFocus()
|
||||
}
|
||||
|
||||
onPressed: (event) => {
|
||||
nameEditButton.handlePress()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -223,17 +290,16 @@ HelperWidgets.Section {
|
||||
|
||||
onReset: nodeUniformsModel.resetData(index)
|
||||
onRemove: {
|
||||
confirmForm.clear()
|
||||
if (uniformIsInUse) {
|
||||
confirmRemoveForm.parent = effectCompositionNodeUniform.editPropertyFormParent
|
||||
confirmRemoveForm.uniformIndex = index
|
||||
confirmRemoveForm.visible = true
|
||||
confirmForm.showConfirmRemove(
|
||||
effectCompositionNodeUniform.editPropertyFormParent, index)
|
||||
} else {
|
||||
nodeUniformsModel.remove(index)
|
||||
}
|
||||
}
|
||||
onEdit: {
|
||||
confirmRemoveForm.visible = false
|
||||
confirmRemoveForm.parent = root
|
||||
confirmForm.clear()
|
||||
addPropertyForm.parent = effectCompositionNodeUniform.editPropertyFormParent
|
||||
let dispNames = nodeUniformsModel.displayNames()
|
||||
let filteredDispNames = dispNames.filter(name => name !== uniformDisplayName);
|
||||
@@ -294,9 +360,14 @@ HelperWidgets.Section {
|
||||
enabled: !addPropertyForm.visible
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
HelperWidgets.ToolTipArea {
|
||||
anchors.fill: parent
|
||||
tooltip: qsTr("Add new property to the effect.")
|
||||
acceptedButtons: Qt.NoButton
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
confirmRemoveForm.visible = false
|
||||
confirmRemoveForm.parent = root
|
||||
confirmForm.clear()
|
||||
root.editedUniformIndex = -1
|
||||
addPropertyForm.parent = addProperty
|
||||
addPropertyForm.reservedDispNames = nodeUniformsModel.displayNames()
|
||||
@@ -310,7 +381,50 @@ HelperWidgets.Section {
|
||||
height: 30
|
||||
text: qsTr("Show Code")
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: root.backendModel.openCodeEditor(index)
|
||||
|
||||
HelperWidgets.ToolTipArea {
|
||||
anchors.fill: parent
|
||||
tooltip: qsTr("Open the shader code editor.")
|
||||
acceptedButtons: Qt.NoButton
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
confirmForm.clear()
|
||||
root.backendModel.openCodeEditor(index)
|
||||
}
|
||||
}
|
||||
|
||||
HelperWidgets.Button {
|
||||
id: addToLibraryButton
|
||||
width: 100
|
||||
height: 30
|
||||
text: qsTr("Add to Library")
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
HelperWidgets.ToolTipArea {
|
||||
anchors.fill: parent
|
||||
tooltip: qsTr("Add the effect to the effect library.\nYou can reuse effects added to the library in other effect compositions.")
|
||||
acceptedButtons: Qt.NoButton
|
||||
}
|
||||
|
||||
function handleClicked()
|
||||
{
|
||||
confirmForm.clear()
|
||||
if (root.backendModel.canAddNodeToLibrary(index)) {
|
||||
if (root.backendModel.nodeExists(index)) {
|
||||
infoText.visible = false
|
||||
confirmForm.showConfirmLibraryAdd(index)
|
||||
} else {
|
||||
infoText.showNodeAddedToLibraryInfo(root.backendModel
|
||||
.addNodeToLibraryNode(index))
|
||||
}
|
||||
} else {
|
||||
infoText.showNeedRenameInfo()
|
||||
nameEditButton.handlePress()
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: addToLibraryButton.handleClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -15,16 +15,18 @@ Rectangle {
|
||||
width: 140
|
||||
height: 32
|
||||
|
||||
color: mouseArea.containsMouse && modelData.canBeAdded
|
||||
color: mouseArea.containsMouse && modelData.canBeAdded && root.enabled
|
||||
? StudioTheme.Values.themeControlBackgroundInteraction : "transparent"
|
||||
|
||||
signal addEffectNode(var nodeQenPath)
|
||||
signal removeEffectNodeFromLibrary(var nodeName)
|
||||
|
||||
ToolTipArea {
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
visible: root.enabled
|
||||
|
||||
tooltip: modelData.canBeAdded ? modelData.nodeDescription
|
||||
: qsTr("An effect with same properties already exists, this effect cannot be added.")
|
||||
@@ -59,6 +61,28 @@ Rectangle {
|
||||
anchors.verticalCenter: nodeIcon.verticalCenter
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width - parent.spacing - nodeIcon.width
|
||||
|
||||
IconButton {
|
||||
id: removeButton
|
||||
|
||||
visible: root.enabled && modelData.canBeRemoved
|
||||
&& (mouseArea.containsMouse || removeButton.containsMouse)
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.rightMargin: StudioTheme.Values.iconAreaWidth / 4
|
||||
icon: StudioTheme.Constants.close_small
|
||||
transparentBg: false
|
||||
buttonSize: StudioTheme.Values.iconAreaWidth
|
||||
iconSize: StudioTheme.Values.smallIconFontSize
|
||||
iconColor: StudioTheme.Values.themeTextColor
|
||||
iconScale: removeButton.containsMouse ? 1.2 : 1
|
||||
implicitWidth: width
|
||||
tooltip: qsTr("Remove custom effect from the library.")
|
||||
|
||||
onPressed: (event) => {
|
||||
root.removeEffectNodeFromLibrary(modelData.nodeName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -78,8 +78,10 @@ StudioControls.ComboBox {
|
||||
flags: Qt.Tool | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint
|
||||
|
||||
onActiveFocusItemChanged: {
|
||||
if (!window.activeFocusItem && !root.hovered && root.popup.opened)
|
||||
if (!window.activeFocusItem && !root.hovered && root.popup.opened) {
|
||||
root.popup.close()
|
||||
confirmRemoveForm.visible = false
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -92,6 +94,7 @@ StudioControls.ComboBox {
|
||||
HelperWidgets.ScrollView {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 1
|
||||
enabled: !confirmRemoveForm.visible
|
||||
|
||||
Row {
|
||||
id: row
|
||||
@@ -118,10 +121,15 @@ StudioControls.ComboBox {
|
||||
|
||||
EffectNode {
|
||||
onAddEffectNode: (nodeQenPath) => {
|
||||
EffectComposerBackend.rootView.addEffectNode(modelData.nodeQenPath)
|
||||
EffectComposerBackend.rootView.addEffectNode(nodeQenPath)
|
||||
confirmRemoveForm.visible = false
|
||||
root.popup.close()
|
||||
root.nodeJustAdded = true
|
||||
}
|
||||
onRemoveEffectNodeFromLibrary: (nodeName) => {
|
||||
confirmRemoveForm.visible = true
|
||||
confirmRemoveForm.nodeName = nodeName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,10 +137,36 @@ StudioControls.ComboBox {
|
||||
}
|
||||
}
|
||||
|
||||
ConfirmForm {
|
||||
id: confirmRemoveForm
|
||||
|
||||
property string nodeName
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
width: 350
|
||||
height: 110
|
||||
visible: false
|
||||
text: qsTr("The node removal from library cannot be undone.\nAre you sure you want to remove node:\n'%1'?")
|
||||
.arg(confirmRemoveForm.nodeName)
|
||||
acceptButtonLabel: qsTr("Remove")
|
||||
|
||||
onAccepted: {
|
||||
EffectComposerBackend.rootView.removeEffectNodeFromLibrary(confirmRemoveForm.nodeName)
|
||||
confirmRemoveForm.visible = false
|
||||
}
|
||||
|
||||
onCanceled: confirmRemoveForm.visible = false
|
||||
}
|
||||
|
||||
|
||||
Keys.onPressed: function(event) {
|
||||
if (event.key === Qt.Key_Escape && root.popup.opened)
|
||||
if (event.key === Qt.Key_Escape && root.popup.opened) {
|
||||
confirmRemoveForm.visible = false
|
||||
root.popup.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -160,6 +160,14 @@ bool CompositionNode::isCustom() const
|
||||
return m_isCustom;
|
||||
}
|
||||
|
||||
void CompositionNode::setCustom(bool enable)
|
||||
{
|
||||
if (enable != m_isCustom) {
|
||||
m_isCustom = enable;
|
||||
emit isCustomChanged();
|
||||
}
|
||||
}
|
||||
|
||||
CompositionNode::NodeType CompositionNode::type() const
|
||||
{
|
||||
return m_type;
|
||||
|
@@ -24,7 +24,7 @@ class CompositionNode : public QObject
|
||||
Q_PROPERTY(QString nodeName READ name WRITE setName NOTIFY nameChanged)
|
||||
Q_PROPERTY(bool nodeEnabled READ isEnabled WRITE setIsEnabled NOTIFY isEnabledChanged)
|
||||
Q_PROPERTY(bool isDependency READ isDependency NOTIFY isDepencyChanged)
|
||||
Q_PROPERTY(bool isCustom READ isCustom CONSTANT)
|
||||
Q_PROPERTY(bool isCustom READ isCustom NOTIFY isCustomChanged)
|
||||
Q_PROPERTY(QObject *nodeUniformsModel READ uniformsModel NOTIFY uniformsModelChanged)
|
||||
Q_PROPERTY(
|
||||
QString fragmentCode
|
||||
@@ -59,6 +59,7 @@ public:
|
||||
|
||||
bool isDependency() const;
|
||||
bool isCustom() const;
|
||||
void setCustom(bool enable);
|
||||
|
||||
QString name() const;
|
||||
void setName(const QString &name);
|
||||
@@ -89,6 +90,7 @@ signals:
|
||||
void fragmentCodeChanged();
|
||||
void vertexCodeChanged();
|
||||
void nameChanged();
|
||||
void isCustomChanged();
|
||||
|
||||
private slots:
|
||||
void onUniformRenamed(const QString &oldName, const QString &newName);
|
||||
|
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "compositionnode.h"
|
||||
#include "effectcomposertr.h"
|
||||
#include "effectcomposernodesmodel.h"
|
||||
#include "effectshaderscodeeditor.h"
|
||||
#include "effectutils.h"
|
||||
#include "propertyhandler.h"
|
||||
@@ -31,7 +32,6 @@
|
||||
#include <QFileDialog>
|
||||
#include <QLibraryInfo>
|
||||
#include <QStandardPaths>
|
||||
#include <QTemporaryDir>
|
||||
#include <QVector2D>
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
@@ -43,6 +43,7 @@ static constexpr int MAIN_CODE_EDITOR_INDEX = -2;
|
||||
|
||||
EffectComposerModel::EffectComposerModel(QObject *parent)
|
||||
: QAbstractListModel{parent}
|
||||
, m_effectComposerNodesModel{new EffectComposerNodesModel(this)}
|
||||
, m_codeEditorIndex(INVALID_CODE_EDITOR_INDEX)
|
||||
, m_shaderDir(QDir::tempPath() + "/qds_ec_XXXXXX")
|
||||
, m_currentPreviewColor("#dddddd")
|
||||
@@ -53,6 +54,11 @@ EffectComposerModel::EffectComposerModel(QObject *parent)
|
||||
connectCodeEditor();
|
||||
}
|
||||
|
||||
EffectComposerNodesModel *EffectComposerModel::effectComposerNodesModel() const
|
||||
{
|
||||
return m_effectComposerNodesModel.data();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> EffectComposerModel::roleNames() const
|
||||
{
|
||||
static const QHash<int, QByteArray> roles = {
|
||||
@@ -74,7 +80,7 @@ int EffectComposerModel::rowCount(const QModelIndex &parent) const
|
||||
|
||||
QVariant EffectComposerModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
QTC_ASSERT(index.isValid() && index.row() < m_nodes.size(), return {});
|
||||
QTC_ASSERT(index.isValid() && isValidIndex(index.row()), return {});
|
||||
QTC_ASSERT(roleNames().contains(role), return {});
|
||||
|
||||
return m_nodes.at(index.row())->property(roleNames().value(role));
|
||||
@@ -226,7 +232,7 @@ void EffectComposerModel::removeNode(int idx)
|
||||
// Returns false if new name was generated
|
||||
bool EffectComposerModel::changeNodeName(int nodeIndex, const QString &name)
|
||||
{
|
||||
QTC_ASSERT(nodeIndex >= 0 && nodeIndex < m_nodes.size(), return false);
|
||||
QTC_ASSERT(isValidIndex(nodeIndex), return false);
|
||||
|
||||
QString trimmedName = name.trimmed();
|
||||
const QString oldName = m_nodes[nodeIndex]->name();
|
||||
@@ -237,8 +243,9 @@ bool EffectComposerModel::changeNodeName(int nodeIndex, const QString &name)
|
||||
const QStringList reservedNames = nodeNames();
|
||||
|
||||
// Matching is done case-insensitive as section headers are shown in all uppercase
|
||||
QString newName = QmlDesigner::UniqueName::generate(trimmedName, [&oldName, &reservedNames] (const QString &checkName) -> bool {
|
||||
return oldName != checkName && reservedNames.contains(checkName, Qt::CaseInsensitive);
|
||||
QString newName = QmlDesigner::UniqueName::generate(trimmedName, [&] (const QString &checkName) -> bool {
|
||||
return oldName != checkName && (reservedNames.contains(checkName, Qt::CaseInsensitive)
|
||||
|| m_effectComposerNodesModel->nodeExists(checkName));
|
||||
});
|
||||
|
||||
if (newName != oldName) {
|
||||
@@ -1326,6 +1333,91 @@ void EffectComposerModel::openMainCodeEditor()
|
||||
setCodeEditorIndex(MAIN_CODE_EDITOR_INDEX);
|
||||
}
|
||||
|
||||
bool EffectComposerModel::canAddNodeToLibrary(int idx)
|
||||
{
|
||||
QTC_ASSERT(isValidIndex(idx), return false);
|
||||
CompositionNode *node = m_nodes.at(idx);
|
||||
|
||||
if (m_effectComposerNodesModel->isBuiltIn(node->name()))
|
||||
return !m_effectComposerNodesModel->nodeExists(node->name());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EffectComposerModel::nodeExists(int idx)
|
||||
{
|
||||
QTC_ASSERT(isValidIndex(idx), return false);
|
||||
CompositionNode *node = m_nodes.at(idx);
|
||||
|
||||
return m_effectComposerNodesModel->nodeExists(node->name());
|
||||
}
|
||||
|
||||
QString EffectComposerModel::addNodeToLibraryNode(int idx)
|
||||
{
|
||||
const QString errorTag{"@ERROR@"};
|
||||
QTC_ASSERT(isValidIndex(idx) && canAddNodeToLibrary(idx), return errorTag);
|
||||
|
||||
CompositionNode *node = m_nodes.at(idx);
|
||||
|
||||
// Adding node to library changes it to a custom one, as we can't overwrite builtin nodes
|
||||
node->setCustom(true);
|
||||
|
||||
QString fileNameBase = EffectUtils::nodeNameToFileName(node->name());
|
||||
Utils::FilePath nodePath = Utils::FilePath::fromString(EffectUtils::nodeLibraryPath())
|
||||
.pathAppended(fileNameBase);
|
||||
Utils::FilePath qenFile = nodePath.pathAppended(fileNameBase + ".qen");
|
||||
|
||||
QSet<QString> newFileNames = {qenFile.fileName()};
|
||||
Utils::FilePaths oldFiles;
|
||||
bool update = nodePath.exists();
|
||||
if (update)
|
||||
oldFiles = nodePath.dirEntries(QDir::Files);
|
||||
else
|
||||
nodePath.createDir();
|
||||
|
||||
QJsonObject rootObj;
|
||||
QJsonObject nodeObject = nodeToJson(*node);
|
||||
rootObj.insert("QEN", nodeObject);
|
||||
QJsonDocument jsonDoc(rootObj);
|
||||
Utils::expected_str<qint64> result = qenFile.writeFileContents(jsonDoc.toJson());
|
||||
if (!result)
|
||||
return errorTag + Tr::tr("Failed to write QEN file for effect:\n%1").arg(qenFile.fileName());
|
||||
|
||||
QList<Utils::FilePath> sources;
|
||||
QStringList dests;
|
||||
const QList<Uniform *> uniforms = node->uniforms();
|
||||
for (Uniform *uniform : uniforms) {
|
||||
const QString valueStr = uniform->value().toString();
|
||||
if (uniform->type() == Uniform::Type::Sampler && !valueStr.isEmpty()) {
|
||||
Utils::FilePath imagePath = Utils::FilePath::fromString(valueStr);
|
||||
QString imageFilename = imagePath.fileName();
|
||||
sources.append(imagePath);
|
||||
dests.append(imageFilename);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < sources.count(); ++i) {
|
||||
Utils::FilePath source = sources[i];
|
||||
Utils::FilePath target = nodePath.pathAppended(dests[i]);
|
||||
newFileNames.insert(target.fileName());
|
||||
if (target.exists() && source.fileName() != target.fileName())
|
||||
target.removeFile(); // Remove existing file for update
|
||||
if (!target.exists() && !source.copyFile(target))
|
||||
return errorTag + Tr::tr("Failed to copy effect resource:\n%1").arg(source.fileName());
|
||||
}
|
||||
|
||||
// Delete old content that was not overwritten
|
||||
for (const Utils::FilePath &oldFile : std::as_const(oldFiles)) {
|
||||
if (!newFileNames.contains(oldFile.fileName()))
|
||||
oldFile.removeFile();
|
||||
}
|
||||
|
||||
m_effectComposerNodesModel->loadCustomNodes();
|
||||
|
||||
QString action = update ? Tr::tr("updated in") : Tr::tr("added to");
|
||||
return Tr::tr("Effect was %1 effect library.").arg(action);
|
||||
}
|
||||
|
||||
QVariant EffectComposerModel::valueLimit(const QString &type, bool max) const
|
||||
{
|
||||
static const int intMin = std::numeric_limits<int>::lowest();
|
||||
@@ -1619,7 +1711,7 @@ void EffectComposerModel::saveResources(const QString &name)
|
||||
newFileNames.insert(target.fileName());
|
||||
if (target.exists() && source.fileName() != target.fileName())
|
||||
target.removeFile(); // Remove existing file for update
|
||||
if (!source.copyFile(target) && !target.exists())
|
||||
if (!target.exists() && !source.copyFile(target))
|
||||
setEffectError(QString("Failed to copy file: %1").arg(source.toFSPathString()));
|
||||
|
||||
if (fileNameToUniformHash.contains(dests[i])) {
|
||||
@@ -2724,7 +2816,7 @@ QString EffectComposerModel::stripFileFromURL(const QString &urlString) const
|
||||
|
||||
void EffectComposerModel::addOrUpdateNodeUniform(int idx, const QVariantMap &data, int updateIndex)
|
||||
{
|
||||
QTC_ASSERT(m_nodes.size() > idx && idx >= 0, return);
|
||||
QTC_ASSERT(isValidIndex(idx), return);
|
||||
|
||||
// Convert values to Uniform digestible strings
|
||||
auto fixedData = data;
|
||||
@@ -2755,4 +2847,9 @@ bool EffectComposerModel::writeToFile(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EffectComposerModel::isValidIndex(int idx) const
|
||||
{
|
||||
return m_nodes.size() > idx && idx >= 0;
|
||||
}
|
||||
|
||||
} // namespace EffectComposer
|
||||
|
@@ -29,6 +29,7 @@ class Process;
|
||||
namespace EffectComposer {
|
||||
|
||||
class CompositionNode;
|
||||
class EffectComposerNodesModel;
|
||||
class EffectShadersCodeEditor;
|
||||
struct ShaderEditorData;
|
||||
class Uniform;
|
||||
@@ -68,6 +69,8 @@ class EffectComposerModel : public QAbstractListModel
|
||||
public:
|
||||
EffectComposerModel(QObject *parent = nullptr);
|
||||
|
||||
EffectComposerNodesModel *effectComposerNodesModel() const;
|
||||
|
||||
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;
|
||||
@@ -130,6 +133,10 @@ public:
|
||||
Q_INVOKABLE void openCodeEditor(int idx);
|
||||
Q_INVOKABLE void openMainCodeEditor();
|
||||
|
||||
Q_INVOKABLE bool canAddNodeToLibrary(int idx);
|
||||
Q_INVOKABLE bool nodeExists(int idx);
|
||||
Q_INVOKABLE QString addNodeToLibraryNode(int idx);
|
||||
|
||||
Q_INVOKABLE QVariant valueLimit(const QString &type, bool max) const;
|
||||
|
||||
void openComposition(const QString &path);
|
||||
@@ -265,6 +272,7 @@ private:
|
||||
bool writeToFile(const QByteArray &buf, const QString &filename, FileType fileType);
|
||||
|
||||
QList<CompositionNode *> m_nodes;
|
||||
QPointer<EffectComposerNodesModel> m_effectComposerNodesModel;
|
||||
|
||||
int m_selectedIndex = -1;
|
||||
int m_codeEditorIndex = -1;
|
||||
|
@@ -2,6 +2,7 @@
|
||||
// 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/algorithm.h>
|
||||
@@ -12,6 +13,8 @@
|
||||
|
||||
namespace EffectComposer {
|
||||
|
||||
constexpr QStringView customCatName{u"Custom"};
|
||||
|
||||
EffectComposerNodesModel::EffectComposerNodesModel(QObject *parent)
|
||||
: QAbstractListModel{parent}
|
||||
{
|
||||
@@ -53,7 +56,8 @@ void EffectComposerNodesModel::loadModel()
|
||||
return;
|
||||
}
|
||||
|
||||
m_categories = {};
|
||||
m_categories.clear();
|
||||
m_builtInNodeNames.clear();
|
||||
|
||||
QDirIterator itCategories(nodesPath.toUrlishString(), QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
while (itCategories.hasNext()) {
|
||||
@@ -69,10 +73,11 @@ void EffectComposerNodesModel::loadModel()
|
||||
QDirIterator itEffects(categoryPath.toUrlishString(), {"*.qen"}, QDir::Files);
|
||||
while (itEffects.hasNext()) {
|
||||
itEffects.next();
|
||||
auto node = new EffectNode(itEffects.filePath());
|
||||
auto node = new EffectNode(itEffects.filePath(), true);
|
||||
if (!node->defaultImagesHash().isEmpty())
|
||||
m_defaultImagesHash.insert(node->name(), node->defaultImagesHash());
|
||||
effects.push_back(node);
|
||||
m_builtInNodeNames.append(node->name());
|
||||
}
|
||||
|
||||
catName[0] = catName[0].toUpper(); // capitalize first letter
|
||||
@@ -80,11 +85,15 @@ void EffectComposerNodesModel::loadModel()
|
||||
|
||||
EffectNodesCategory *category = new EffectNodesCategory(catName, effects);
|
||||
m_categories.push_back(category);
|
||||
|
||||
if (catName == customCatName && !effects.isEmpty()) {
|
||||
m_builtinCustomNode = effects[0];
|
||||
m_customCategory = category;
|
||||
}
|
||||
}
|
||||
|
||||
const QString customCatName = "Custom";
|
||||
std::sort(m_categories.begin(), m_categories.end(),
|
||||
[&customCatName](EffectNodesCategory *a, EffectNodesCategory *b) {
|
||||
[](EffectNodesCategory *a, EffectNodesCategory *b) {
|
||||
if (a->name() == customCatName)
|
||||
return false;
|
||||
if (b->name() == customCatName)
|
||||
@@ -94,6 +103,40 @@ void EffectComposerNodesModel::loadModel()
|
||||
|
||||
m_modelLoaded = true;
|
||||
|
||||
loadCustomNodes();
|
||||
}
|
||||
|
||||
void EffectComposerNodesModel::loadCustomNodes()
|
||||
{
|
||||
if (!m_customCategory)
|
||||
return;
|
||||
|
||||
for (const QString &nodeName : std::as_const(m_customNodeNames))
|
||||
m_defaultImagesHash.remove(nodeName);
|
||||
|
||||
m_customNodeNames.clear();
|
||||
|
||||
QList<EffectNode *> effects;
|
||||
|
||||
const Utils::FilePath nodeLibPath = Utils::FilePath::fromString(EffectUtils::nodeLibraryPath());
|
||||
const Utils::FilePaths libraryNodes = nodeLibPath.dirEntries(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
for (const Utils::FilePath &nodePath : libraryNodes) {
|
||||
const Utils::FilePath qenPath = nodePath.pathAppended(nodePath.fileName() + ".qen");
|
||||
auto node = new EffectNode(qenPath.toFSPathString(), false);
|
||||
if (!node->defaultImagesHash().isEmpty())
|
||||
m_defaultImagesHash.insert(node->name(), node->defaultImagesHash());
|
||||
m_customNodeNames.append(node->name());
|
||||
effects.push_back(node);
|
||||
}
|
||||
|
||||
Utils::sort(effects, &EffectNode::name);
|
||||
|
||||
if (m_customCategory) {
|
||||
if (m_builtinCustomNode)
|
||||
effects.prepend(m_builtinCustomNode);
|
||||
m_customCategory->setNodes(effects);
|
||||
}
|
||||
|
||||
resetModel();
|
||||
}
|
||||
|
||||
@@ -103,6 +146,17 @@ void EffectComposerNodesModel::resetModel()
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
bool EffectComposerNodesModel::nodeExists(const QString &name)
|
||||
{
|
||||
return m_customNodeNames.contains(name, Qt::CaseInsensitive)
|
||||
|| m_builtInNodeNames.contains(name, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
bool EffectComposerNodesModel::isBuiltIn(const QString &name)
|
||||
{
|
||||
return m_builtInNodeNames.contains(name, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
void EffectComposerNodesModel::updateCanBeAdded(
|
||||
const QStringList &uniforms, [[maybe_unused]] const QStringList &nodeNames)
|
||||
{
|
||||
@@ -126,4 +180,22 @@ QHash<QString, QString> EffectComposerNodesModel::defaultImagesForNode(const QSt
|
||||
return m_defaultImagesHash.value(name);
|
||||
}
|
||||
|
||||
void EffectComposerNodesModel::removeEffectNode(const QString &name)
|
||||
{
|
||||
if (!m_customCategory || name.isEmpty())
|
||||
return;
|
||||
|
||||
m_defaultImagesHash.remove(name);
|
||||
m_customNodeNames.removeOne(name);
|
||||
m_customCategory->removeNode(name);
|
||||
|
||||
QString fileNameBase = EffectUtils::nodeNameToFileName(name);
|
||||
Utils::FilePath nodePath = Utils::FilePath::fromString(EffectUtils::nodeLibraryPath())
|
||||
.pathAppended(fileNameBase);
|
||||
if (nodePath.exists())
|
||||
nodePath.removeRecursively();
|
||||
|
||||
resetModel();
|
||||
}
|
||||
|
||||
} // namespace EffectComposer
|
||||
|
@@ -9,6 +9,8 @@
|
||||
|
||||
namespace EffectComposer {
|
||||
|
||||
class EffectNode;
|
||||
|
||||
class EffectComposerNodesModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -26,14 +28,20 @@ public:
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
void loadModel();
|
||||
void loadCustomNodes();
|
||||
void resetModel();
|
||||
|
||||
bool nodeExists(const QString &name);
|
||||
bool isBuiltIn(const QString &name);
|
||||
|
||||
QList<EffectNodesCategory *> categories() const { return m_categories; }
|
||||
|
||||
void updateCanBeAdded(const QStringList &uniforms, const QStringList &nodeNames);
|
||||
|
||||
QHash<QString, QString> defaultImagesForNode(const QString &name) const;
|
||||
|
||||
void removeEffectNode(const QString &name);
|
||||
|
||||
private:
|
||||
QString nodesSourcesPath() const;
|
||||
|
||||
@@ -41,6 +49,10 @@ private:
|
||||
bool m_probeNodesDir = false;
|
||||
bool m_modelLoaded = false;
|
||||
QHash<QString, QHash<QString, QString>> m_defaultImagesHash;
|
||||
QStringList m_builtInNodeNames;
|
||||
QStringList m_customNodeNames;
|
||||
EffectNode *m_builtinCustomNode = nullptr;
|
||||
EffectNodesCategory *m_customCategory = nullptr;
|
||||
};
|
||||
|
||||
} // namespace EffectComposer
|
||||
|
@@ -75,7 +75,6 @@ static QList<QmlDesigner::ModelNode> modelNodesFromMimeData(const QByteArray &mi
|
||||
|
||||
EffectComposerWidget::EffectComposerWidget(EffectComposerView *view)
|
||||
: m_effectComposerModel{new EffectComposerModel(this)}
|
||||
, m_effectComposerNodesModel{new EffectComposerNodesModel(this)}
|
||||
, m_effectComposerView(view)
|
||||
, m_quickWidget{new StudioQuickWidget(this)}
|
||||
{
|
||||
@@ -108,7 +107,7 @@ EffectComposerWidget::EffectComposerWidget(EffectComposerView *view)
|
||||
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())},
|
||||
map->setProperties({{"effectComposerNodesModel", QVariant::fromValue(effectComposerNodesModel().data())},
|
||||
{"effectComposerModel", QVariant::fromValue(m_effectComposerModel.data())},
|
||||
{"rootView", QVariant::fromValue(this)}});
|
||||
|
||||
@@ -174,7 +173,7 @@ QPointer<EffectComposerModel> EffectComposerWidget::effectComposerModel() const
|
||||
|
||||
QPointer<EffectComposerNodesModel> EffectComposerWidget::effectComposerNodesModel() const
|
||||
{
|
||||
return m_effectComposerNodesModel;
|
||||
return m_effectComposerModel->effectComposerNodesModel();
|
||||
}
|
||||
|
||||
void EffectComposerWidget::addEffectNode(const QString &nodeQenPath)
|
||||
@@ -188,6 +187,11 @@ void EffectComposerWidget::addEffectNode(const QString &nodeQenPath)
|
||||
}
|
||||
}
|
||||
|
||||
void EffectComposerWidget::removeEffectNodeFromLibrary(const QString &nodeName)
|
||||
{
|
||||
effectComposerNodesModel()->removeEffectNode(nodeName);
|
||||
}
|
||||
|
||||
void EffectComposerWidget::focusSection(int section)
|
||||
{
|
||||
Q_UNUSED(section)
|
||||
@@ -209,7 +213,7 @@ QPoint EffectComposerWidget::globalPos(const QPoint &point) const
|
||||
|
||||
QString EffectComposerWidget::uniformDefaultImage(const QString &nodeName, const QString &uniformName) const
|
||||
{
|
||||
return m_effectComposerNodesModel->defaultImagesForNode(nodeName).value(uniformName);
|
||||
return effectComposerNodesModel()->defaultImagesForNode(nodeName).value(uniformName);
|
||||
}
|
||||
|
||||
QString EffectComposerWidget::imagesPath() const
|
||||
@@ -247,7 +251,7 @@ void EffectComposerWidget::dropNode(const QByteArray &mimeData)
|
||||
|
||||
void EffectComposerWidget::updateCanBeAdded()
|
||||
{
|
||||
m_effectComposerNodesModel->updateCanBeAdded(m_effectComposerModel->uniformNames(),
|
||||
effectComposerNodesModel()->updateCanBeAdded(m_effectComposerModel->uniformNames(),
|
||||
m_effectComposerModel->nodeNames());
|
||||
}
|
||||
|
||||
|
@@ -47,6 +47,7 @@ public:
|
||||
QPointer<EffectComposerNodesModel> effectComposerNodesModel() const;
|
||||
|
||||
Q_INVOKABLE void addEffectNode(const QString &nodeQenPath);
|
||||
Q_INVOKABLE void removeEffectNodeFromLibrary(const QString &nodeName);
|
||||
Q_INVOKABLE void focusSection(int section);
|
||||
Q_INVOKABLE void doOpenComposition();
|
||||
Q_INVOKABLE QRect screenRect() const;
|
||||
@@ -68,7 +69,6 @@ private:
|
||||
void handleImportScanTimer();
|
||||
|
||||
QPointer<EffectComposerModel> m_effectComposerModel;
|
||||
QPointer<EffectComposerNodesModel> m_effectComposerNodesModel;
|
||||
QPointer<EffectComposerView> m_effectComposerView;
|
||||
QPointer<StudioQuickWidget> m_quickWidget;
|
||||
QmlDesigner::QmlModelNodeProxy m_backendModelNode;
|
||||
|
@@ -2,7 +2,9 @@
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "effectnode.h"
|
||||
|
||||
#include "compositionnode.h"
|
||||
#include "effectutils.h"
|
||||
#include "uniform.h"
|
||||
|
||||
#include <QDir>
|
||||
@@ -10,26 +12,26 @@
|
||||
|
||||
namespace EffectComposer {
|
||||
|
||||
EffectNode::EffectNode(const QString &qenPath)
|
||||
EffectNode::EffectNode(const QString &qenPath, bool isBuiltIn)
|
||||
: 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();
|
||||
m_isCustom = node.isCustom();
|
||||
m_canBeRemoved = !isBuiltIn;
|
||||
|
||||
QString iconPath = QStringLiteral("%1/icon/%2.svg").arg(fileInfo.absolutePath(),
|
||||
fileInfo.baseName());
|
||||
if (!QFileInfo::exists(iconPath)) {
|
||||
QString iconFileName = m_isCustom ? QString{"user_custom_effect.svg"}
|
||||
: QString{"placeholder.svg"};
|
||||
iconPath = QStringLiteral("%1/%2").arg(EffectUtils::nodesSourcesPath(), iconFileName);
|
||||
}
|
||||
m_iconPath = QUrl::fromLocalFile(iconPath);
|
||||
|
||||
const QList<Uniform *> uniforms = node.uniforms();
|
||||
for (const Uniform *uniform : uniforms) {
|
||||
|
@@ -18,9 +18,10 @@ class EffectNode : public QObject
|
||||
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)
|
||||
Q_PROPERTY(bool canBeRemoved MEMBER m_canBeRemoved CONSTANT)
|
||||
|
||||
public:
|
||||
EffectNode(const QString &qenPath);
|
||||
EffectNode(const QString &qenPath, bool isBuiltIn);
|
||||
|
||||
QString name() const;
|
||||
QString description() const;
|
||||
@@ -42,6 +43,7 @@ private:
|
||||
QUrl m_iconPath;
|
||||
bool m_isCustom = false;
|
||||
bool m_canBeAdded = true;
|
||||
bool m_canBeRemoved = false;
|
||||
QSet<QString> m_uniformNames;
|
||||
QHash<QString, QString> m_defaultImagesHash;
|
||||
};
|
||||
|
@@ -3,6 +3,8 @@
|
||||
|
||||
#include "effectnodescategory.h"
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
|
||||
namespace EffectComposer {
|
||||
|
||||
EffectNodesCategory::EffectNodesCategory(const QString &name, const QList<EffectNode *> &nodes)
|
||||
@@ -19,5 +21,19 @@ QList<EffectNode *> EffectNodesCategory::nodes() const
|
||||
return m_categoryNodes;
|
||||
}
|
||||
|
||||
void EffectNodesCategory::setNodes(const QList<EffectNode *> &nodes)
|
||||
{
|
||||
m_categoryNodes = nodes;
|
||||
|
||||
emit nodesChanged();
|
||||
}
|
||||
|
||||
void EffectNodesCategory::removeNode(const QString &nodeName)
|
||||
{
|
||||
Utils::eraseOne(m_categoryNodes, [nodeName](const EffectNode *node) {
|
||||
return node->name() == nodeName;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace EffectComposer
|
||||
|
||||
|
@@ -14,13 +14,18 @@ class EffectNodesCategory : public QObject
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString categoryName MEMBER m_name CONSTANT)
|
||||
Q_PROPERTY(QList<EffectNode *> categoryNodes MEMBER m_categoryNodes CONSTANT)
|
||||
Q_PROPERTY(QList<EffectNode *> categoryNodes READ nodes NOTIFY nodesChanged)
|
||||
|
||||
public:
|
||||
EffectNodesCategory(const QString &name, const QList<EffectNode *> &nodes);
|
||||
|
||||
QString name() const;
|
||||
QList<EffectNode *> nodes() const;
|
||||
void setNodes(const QList<EffectNode *> &nodes);
|
||||
void removeNode(const QString &nodeName);
|
||||
|
||||
signals:
|
||||
void nodesChanged();
|
||||
|
||||
private:
|
||||
QString m_name;
|
||||
|
@@ -6,6 +6,8 @@
|
||||
#include <coreplugin/icore.h>
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QRegularExpression>
|
||||
#include <QStandardPaths>
|
||||
|
||||
namespace EffectComposer {
|
||||
|
||||
@@ -31,4 +33,20 @@ QString EffectUtils::nodesSourcesPath()
|
||||
return Core::ICore::resourcePath("qmldesigner/effectComposerNodes").toUrlishString();
|
||||
}
|
||||
|
||||
QString EffectUtils::nodeLibraryPath()
|
||||
{
|
||||
QStandardPaths::StandardLocation location = QStandardPaths::DocumentsLocation;
|
||||
|
||||
return QStandardPaths::writableLocation(location)
|
||||
+ "/QtDesignStudio/effect_composer/node_library";
|
||||
}
|
||||
|
||||
QString EffectUtils::nodeNameToFileName(const QString &nodeName)
|
||||
{
|
||||
static const QRegularExpression re("[^a-zA-Z0-9]");
|
||||
QString newName = nodeName;
|
||||
newName.replace(re, "_");
|
||||
return newName;
|
||||
}
|
||||
|
||||
} // namespace EffectComposer
|
||||
|
@@ -15,8 +15,9 @@ public:
|
||||
EffectUtils() = delete;
|
||||
|
||||
static QString codeFromJsonArray(const QJsonArray &codeArray);
|
||||
|
||||
static QString nodesSourcesPath();
|
||||
static QString nodeLibraryPath();
|
||||
static QString nodeNameToFileName(const QString &nodeName);
|
||||
};
|
||||
|
||||
} // namespace EffectComposer
|
||||
|
Reference in New Issue
Block a user