EffectMaker: Improve adding and saving compositions

Add and implement 2 icons for adding and saving compositions.

Fixes: QDS-11511
Change-Id: I113eeb81ea05fc6db9019d95d476bc0fe20b409f
Reviewed-by: Qt CI Patch Build Bot <ci_patchbuild_bot@qt.io>
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
This commit is contained in:
Mahmoud Badri
2023-12-08 14:58:04 +02:00
parent 4ab995da59
commit cc07031cd6
7 changed files with 153 additions and 17 deletions

View File

@@ -16,24 +16,45 @@ Item {
property int moveToIdx: 0 property int moveToIdx: 0
property bool previewAnimationRunning: false property bool previewAnimationRunning: false
SaveDialog { SaveAsDialog {
id: saveDialog id: saveDialog
compositionName: EffectMakerBackend.effectMakerModel.currentComposition compositionName: EffectMakerBackend.effectMakerModel.currentComposition
anchors.centerIn: parent anchors.centerIn: parent
onAccepted: { onAccepted: {
let name = saveDialog.compositionName let name = saveDialog.compositionName
EffectMakerBackend.effectMakerModel.exportComposition(name) EffectMakerBackend.effectMakerModel.saveComposition(name)
EffectMakerBackend.effectMakerModel.exportResources(name)
} }
} }
SaveChangesDialog {
id: saveChangesDialog
anchors.centerIn: parent
onAccepted: EffectMakerBackend.effectMakerModel.clear()
}
Column { Column {
id: col id: col
anchors.fill: parent anchors.fill: parent
spacing: 1 spacing: 1
EffectMakerTopBar { EffectMakerTopBar {
onSaveClicked: saveDialog.open() onAddClicked: {
if (EffectMakerBackend.effectMakerModel.hasUnsavedChanges)
saveChangesDialog.open()
else
EffectMakerBackend.effectMakerModel.clear()
}
onSaveClicked: {
let name = EffectMakerBackend.effectMakerModel.currentComposition
if (name === "")
saveDialog.open()
else
EffectMakerBackend.effectMakerModel.saveComposition(name)
}
} }
EffectMakerPreview { EffectMakerPreview {

View File

@@ -15,13 +15,29 @@ Rectangle {
height: StudioTheme.Values.toolbarHeight height: StudioTheme.Values.toolbarHeight
color: StudioTheme.Values.themeToolbarBackground color: StudioTheme.Values.themeToolbarBackground
signal addClicked
signal saveClicked signal saveClicked
HelperWidgets.Button { HelperWidgets.AbstractButton {
id: addButton
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
x: 5 x: 5
style: StudioTheme.Values.viewBarButtonStyle
buttonIcon: StudioTheme.Constants.add_medium
tooltip: qsTr("Add new composition")
text: qsTr("Save in Library") onClicked: root.addClicked()
}
HelperWidgets.AbstractButton {
anchors.verticalCenter: parent.verticalCenter
x: addButton.x + addButton.width + 5
style: StudioTheme.Values.viewBarButtonStyle
buttonIcon: StudioTheme.Constants.save_medium
tooltip: qsTr("Save current composition")
enabled: EffectMakerBackend.effectMakerModel.hasUnsavedChanges
|| EffectMakerBackend.effectMakerModel.currentComposition === ""
onClicked: root.saveClicked() onClicked: root.saveClicked()
} }

View File

@@ -6,7 +6,7 @@ import QtQuick.Controls
import HelperWidgets as HelperWidgets import HelperWidgets as HelperWidgets
import StudioControls as StudioControls import StudioControls as StudioControls
import StudioTheme as StudioTheme import StudioTheme as StudioTheme
import AssetsLibraryBackend import EffectMakerBackend
StudioControls.Dialog { StudioControls.Dialog {
id: root id: root
@@ -21,7 +21,7 @@ StudioControls.Dialog {
property string compositionName: "" property string compositionName: ""
onOpened: { onOpened: {
nameText.text = compositionName //TODO: Generate unique name nameText.text = EffectMakerBackend.effectMakerModel.currentComposition
emptyText.text = "" emptyText.text = ""
nameText.forceActiveFocus() nameText.forceActiveFocus()
} }

View File

@@ -0,0 +1,62 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick
import QtQuick.Controls
import HelperWidgets as HelperWidgets
import StudioControls as StudioControls
import StudioTheme as StudioTheme
import EffectMakerBackend
StudioControls.Dialog {
id: root
title: qsTr("Save Changes")
closePolicy: Popup.CloseOnEscape
modal: true
implicitWidth: 250
implicitHeight: 140
contentItem: Column {
spacing: 35
Text {
text: qsTr("Current composition has unsaved changes.")
color: StudioTheme.Values.themeTextColor
}
Row {
anchors.right: parent.right
spacing: 2
HelperWidgets.Button {
id: btnSave
width: 70
text: qsTr("Save")
onClicked: {
if (btnSave.enabled) {
let name = EffectMakerBackend.effectMakerModel.currentComposition
EffectMakerBackend.effectMakerModel.saveComposition(name)
root.accept()
}
}
}
HelperWidgets.Button {
id: btnDontSave
width: 70
text: qsTr("Don't Save")
onClicked: root.accept()
}
HelperWidgets.Button {
width: 70
text: qsTr("Cancel")
onClicked: root.reject()
}
}
}
}

View File

@@ -106,12 +106,17 @@ void EffectMakerModel::addNode(const QString &nodeQenPath)
{ {
beginInsertRows({}, m_nodes.size(), m_nodes.size()); beginInsertRows({}, m_nodes.size(), m_nodes.size());
auto *node = new CompositionNode("", nodeQenPath); auto *node = new CompositionNode("", nodeQenPath);
connect(qobject_cast<EffectMakerUniformsModel *>(node->uniformsModel()),
&EffectMakerUniformsModel::dataChanged, this, [this] {
setHasUnsavedChanges(true);
});
m_nodes.append(node); m_nodes.append(node);
endInsertRows(); endInsertRows();
setIsEmpty(false); setIsEmpty(false);
bakeShaders(); bakeShaders();
setHasUnsavedChanges(true);
emit nodesChanged(); emit nodesChanged();
} }
@@ -126,6 +131,7 @@ void EffectMakerModel::moveNode(int fromIdx, int toIdx)
m_nodes.move(fromIdx, toIdx); m_nodes.move(fromIdx, toIdx);
endMoveRows(); endMoveRows();
setHasUnsavedChanges(true);
bakeShaders(); bakeShaders();
} }
@@ -141,6 +147,7 @@ void EffectMakerModel::removeNode(int idx)
else else
bakeShaders(); bakeShaders();
setHasUnsavedChanges(true);
emit nodesChanged(); emit nodesChanged();
} }
@@ -150,6 +157,7 @@ void EffectMakerModel::clear()
qDeleteAll(m_nodes); qDeleteAll(m_nodes);
m_nodes.clear(); m_nodes.clear();
endResetModel(); endResetModel();
setHasUnsavedChanges(!m_currentComposition.isEmpty());
setCurrentComposition(""); setCurrentComposition("");
setIsEmpty(true); setIsEmpty(true);
@@ -528,7 +536,7 @@ QString EffectMakerModel::getQmlEffectString()
return s; return s;
} }
void EffectMakerModel::exportComposition(const QString &name) void EffectMakerModel::saveComposition(const QString &name)
{ {
const QString effectsAssetsDir = QmlDesigner::ModelNodeOperations::getEffectsDefaultDirectory(); const QString effectsAssetsDir = QmlDesigner::ModelNodeOperations::getEffectsDefaultDirectory();
const QString path = effectsAssetsDir + QDir::separator() + name + ".qep"; const QString path = effectsAssetsDir + QDir::separator() + name + ".qep";
@@ -559,6 +567,10 @@ void EffectMakerModel::exportComposition(const QString &name)
saveFile.write(jsonDoc.toJson()); saveFile.write(jsonDoc.toJson());
saveFile.close(); saveFile.close();
setCurrentComposition(name);
setHasUnsavedChanges(false);
saveResources(name);
} }
void EffectMakerModel::openComposition(const QString &path) void EffectMakerModel::openComposition(const QString &path)
@@ -615,6 +627,10 @@ void EffectMakerModel::openComposition(const QString &path)
for (const auto &nodeElement : nodesArray) { for (const auto &nodeElement : nodesArray) {
beginInsertRows({}, m_nodes.size(), m_nodes.size()); beginInsertRows({}, m_nodes.size(), m_nodes.size());
auto *node = new CompositionNode(effectName, "", nodeElement.toObject()); auto *node = new CompositionNode(effectName, "", nodeElement.toObject());
connect(qobject_cast<EffectMakerUniformsModel *>(node->uniformsModel()),
&EffectMakerUniformsModel::dataChanged, this, [this] {
setHasUnsavedChanges(true);
});
m_nodes.append(node); m_nodes.append(node);
endInsertRows(); endInsertRows();
} }
@@ -623,10 +639,11 @@ void EffectMakerModel::openComposition(const QString &path)
bakeShaders(); bakeShaders();
} }
setHasUnsavedChanges(false);
emit nodesChanged(); emit nodesChanged();
} }
void EffectMakerModel::exportResources(const QString &name) void EffectMakerModel::saveResources(const QString &name)
{ {
// Make sure that uniforms are up-to-date // Make sure that uniforms are up-to-date
updateCustomUniforms(); updateCustomUniforms();
@@ -692,7 +709,7 @@ void EffectMakerModel::exportResources(const QString &name)
QString qmlFilePath = effectsResPath + qmlFilename; QString qmlFilePath = effectsResPath + qmlFilename;
writeToFile(qmlString.toUtf8(), qmlFilePath, FileType::Text); writeToFile(qmlString.toUtf8(), qmlFilePath, FileType::Text);
// Export shaders and images // Save shaders and images
QStringList sources = {m_vertexShaderFilename, m_fragmentShaderFilename}; QStringList sources = {m_vertexShaderFilename, m_fragmentShaderFilename};
QStringList dests = {vsFilename, fsFilename}; QStringList dests = {vsFilename, fsFilename};
@@ -721,7 +738,7 @@ void EffectMakerModel::exportResources(const QString &name)
qWarning() << __FUNCTION__ << " Failed to copy file: " << source; qWarning() << __FUNCTION__ << " Failed to copy file: " << source;
} }
emit resourcesExported(QString("Effects.%1.%1").arg(name).toUtf8(), effectPath); emit resourcesSaved(QString("Effects.%1.%1").arg(name).toUtf8(), effectPath);
} }
void EffectMakerModel::resetEffectError(int type) void EffectMakerModel::resetEffectError(int type)
@@ -1440,10 +1457,25 @@ void EffectMakerModel::setCurrentComposition(const QString &newCurrentCompositio
{ {
if (m_currentComposition == newCurrentComposition) if (m_currentComposition == newCurrentComposition)
return; return;
m_currentComposition = newCurrentComposition; m_currentComposition = newCurrentComposition;
emit currentCompositionChanged(); emit currentCompositionChanged();
} }
bool EffectMakerModel::hasUnsavedChanges() const
{
return m_hasUnsavedChanges;
}
void EffectMakerModel::setHasUnsavedChanges(bool val)
{
if (m_hasUnsavedChanges == val)
return;
m_hasUnsavedChanges = val;
emit hasUnsavedChangesChanged();
}
QStringList EffectMakerModel::uniformNames() const QStringList EffectMakerModel::uniformNames() const
{ {
QStringList usedList; QStringList usedList;

View File

@@ -44,6 +44,7 @@ class EffectMakerModel : public QAbstractListModel
Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged)
Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged) 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(bool shadersUpToDate READ shadersUpToDate WRITE setShadersUpToDate NOTIFY shadersUpToDateChanged)
Q_PROPERTY(QString qmlComponentString READ qmlComponentString) Q_PROPERTY(QString qmlComponentString READ qmlComponentString)
Q_PROPERTY(QString currentComposition READ currentComposition WRITE setCurrentComposition NOTIFY currentCompositionChanged) Q_PROPERTY(QString currentComposition READ currentComposition WRITE setCurrentComposition NOTIFY currentCompositionChanged)
@@ -81,14 +82,16 @@ public:
Q_INVOKABLE void resetEffectError(int type); Q_INVOKABLE void resetEffectError(int type);
Q_INVOKABLE void setEffectError(const QString &errorMessage, int type = -1, int lineNumber = -1); Q_INVOKABLE void setEffectError(const QString &errorMessage, int type = -1, int lineNumber = -1);
Q_INVOKABLE void exportComposition(const QString &name); Q_INVOKABLE void saveComposition(const QString &name);
Q_INVOKABLE void exportResources(const QString &name);
void openComposition(const QString &path); void openComposition(const QString &path);
QString currentComposition() const; QString currentComposition() const;
void setCurrentComposition(const QString &newCurrentComposition); void setCurrentComposition(const QString &newCurrentComposition);
bool hasUnsavedChanges() const;
void setHasUnsavedChanges(bool val);
QStringList uniformNames() const; QStringList uniformNames() const;
signals: signals:
@@ -97,10 +100,10 @@ signals:
void effectErrorChanged(); void effectErrorChanged();
void shadersUpToDateChanged(); void shadersUpToDateChanged();
void shadersBaked(); void shadersBaked();
void currentCompositionChanged(); void currentCompositionChanged();
void nodesChanged(); void nodesChanged();
void resourcesExported(const QByteArray &type, const Utils::FilePath &path); void resourcesSaved(const QByteArray &type, const Utils::FilePath &path);
void hasUnsavedChangesChanged();
private: private:
enum Roles { enum Roles {
@@ -152,6 +155,7 @@ private:
void updateCustomUniforms(); void updateCustomUniforms();
void createFiles(); void createFiles();
void bakeShaders(); void bakeShaders();
void saveResources(const QString &name);
QString mipmapPropertyName(const QString &name) const; QString mipmapPropertyName(const QString &name) const;
QString getQmlImagesString(bool localFiles); QString getQmlImagesString(bool localFiles);
@@ -161,6 +165,7 @@ private:
int m_selectedIndex = -1; int m_selectedIndex = -1;
bool m_isEmpty = true; bool m_isEmpty = true;
bool m_hasUnsavedChanges = false;
// True when shaders haven't changed since last baking // True when shaders haven't changed since last baking
bool m_shadersUpToDate = true; bool m_shadersUpToDate = true;
int m_remainingQsbTargets = 0; int m_remainingQsbTargets = 0;

View File

@@ -86,7 +86,7 @@ EffectMakerWidget::EffectMakerWidget(EffectMakerView *view)
m_effectMakerNodesModel->updateCanBeAdded(m_effectMakerModel->uniformNames()); m_effectMakerNodesModel->updateCanBeAdded(m_effectMakerModel->uniformNames());
}); });
connect(m_effectMakerModel.data(), &EffectMakerModel::resourcesExported, connect(m_effectMakerModel.data(), &EffectMakerModel::resourcesSaved,
this, [this](const QmlDesigner::TypeName &type, const Utils::FilePath &path) { this, [this](const QmlDesigner::TypeName &type, const Utils::FilePath &path) {
if (!m_importScan.timer) { if (!m_importScan.timer) {
m_importScan.timer = new QTimer(this); m_importScan.timer = new QTimer(this);