From cc07031cd6d06758bba401f99986d2a03832b119 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Fri, 8 Dec 2023 14:58:04 +0200 Subject: [PATCH] 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 Reviewed-by: Miikka Heikkinen --- .../effectMakerQmlSources/EffectMaker.qml | 29 +++++++-- .../EffectMakerTopBar.qml | 20 +++++- .../{SaveDialog.qml => SaveAsDialog.qml} | 4 +- .../SaveChangesDialog.qml | 62 +++++++++++++++++++ .../effectmakernew/effectmakermodel.cpp | 40 ++++++++++-- src/plugins/effectmakernew/effectmakermodel.h | 13 ++-- .../effectmakernew/effectmakerwidget.cpp | 2 +- 7 files changed, 153 insertions(+), 17 deletions(-) rename share/qtcreator/qmldesigner/effectMakerQmlSources/{SaveDialog.qml => SaveAsDialog.qml} (96%) create mode 100644 share/qtcreator/qmldesigner/effectMakerQmlSources/SaveChangesDialog.qml diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMaker.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMaker.qml index 3f962478e0b..07cfba3cf6d 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMaker.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMaker.qml @@ -16,24 +16,45 @@ Item { property int moveToIdx: 0 property bool previewAnimationRunning: false - SaveDialog { + SaveAsDialog { id: saveDialog compositionName: EffectMakerBackend.effectMakerModel.currentComposition anchors.centerIn: parent + onAccepted: { let name = saveDialog.compositionName - EffectMakerBackend.effectMakerModel.exportComposition(name) - EffectMakerBackend.effectMakerModel.exportResources(name) + EffectMakerBackend.effectMakerModel.saveComposition(name) } } + SaveChangesDialog { + id: saveChangesDialog + anchors.centerIn: parent + + onAccepted: EffectMakerBackend.effectMakerModel.clear() + } + Column { id: col anchors.fill: parent spacing: 1 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 { diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerTopBar.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerTopBar.qml index 1ae879994bd..2c7ff15cd5e 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerTopBar.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/EffectMakerTopBar.qml @@ -15,13 +15,29 @@ Rectangle { height: StudioTheme.Values.toolbarHeight color: StudioTheme.Values.themeToolbarBackground + signal addClicked signal saveClicked - HelperWidgets.Button { + HelperWidgets.AbstractButton { + id: addButton + anchors.verticalCenter: parent.verticalCenter 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() } diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/SaveDialog.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/SaveAsDialog.qml similarity index 96% rename from share/qtcreator/qmldesigner/effectMakerQmlSources/SaveDialog.qml rename to share/qtcreator/qmldesigner/effectMakerQmlSources/SaveAsDialog.qml index 60ddae35452..bf136210aa6 100644 --- a/share/qtcreator/qmldesigner/effectMakerQmlSources/SaveDialog.qml +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/SaveAsDialog.qml @@ -6,7 +6,7 @@ import QtQuick.Controls import HelperWidgets as HelperWidgets import StudioControls as StudioControls import StudioTheme as StudioTheme -import AssetsLibraryBackend +import EffectMakerBackend StudioControls.Dialog { id: root @@ -21,7 +21,7 @@ StudioControls.Dialog { property string compositionName: "" onOpened: { - nameText.text = compositionName //TODO: Generate unique name + nameText.text = EffectMakerBackend.effectMakerModel.currentComposition emptyText.text = "" nameText.forceActiveFocus() } diff --git a/share/qtcreator/qmldesigner/effectMakerQmlSources/SaveChangesDialog.qml b/share/qtcreator/qmldesigner/effectMakerQmlSources/SaveChangesDialog.qml new file mode 100644 index 00000000000..2552cb79102 --- /dev/null +++ b/share/qtcreator/qmldesigner/effectMakerQmlSources/SaveChangesDialog.qml @@ -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() + } + } + } +} diff --git a/src/plugins/effectmakernew/effectmakermodel.cpp b/src/plugins/effectmakernew/effectmakermodel.cpp index 45efcb8c3ff..fa9d2d31c2f 100644 --- a/src/plugins/effectmakernew/effectmakermodel.cpp +++ b/src/plugins/effectmakernew/effectmakermodel.cpp @@ -106,12 +106,17 @@ void EffectMakerModel::addNode(const QString &nodeQenPath) { beginInsertRows({}, m_nodes.size(), m_nodes.size()); auto *node = new CompositionNode("", nodeQenPath); + connect(qobject_cast(node->uniformsModel()), + &EffectMakerUniformsModel::dataChanged, this, [this] { + setHasUnsavedChanges(true); + }); m_nodes.append(node); endInsertRows(); setIsEmpty(false); bakeShaders(); + setHasUnsavedChanges(true); emit nodesChanged(); } @@ -126,6 +131,7 @@ void EffectMakerModel::moveNode(int fromIdx, int toIdx) m_nodes.move(fromIdx, toIdx); endMoveRows(); + setHasUnsavedChanges(true); bakeShaders(); } @@ -141,6 +147,7 @@ void EffectMakerModel::removeNode(int idx) else bakeShaders(); + setHasUnsavedChanges(true); emit nodesChanged(); } @@ -150,6 +157,7 @@ void EffectMakerModel::clear() qDeleteAll(m_nodes); m_nodes.clear(); endResetModel(); + setHasUnsavedChanges(!m_currentComposition.isEmpty()); setCurrentComposition(""); setIsEmpty(true); @@ -528,7 +536,7 @@ QString EffectMakerModel::getQmlEffectString() return s; } -void EffectMakerModel::exportComposition(const QString &name) +void EffectMakerModel::saveComposition(const QString &name) { const QString effectsAssetsDir = QmlDesigner::ModelNodeOperations::getEffectsDefaultDirectory(); const QString path = effectsAssetsDir + QDir::separator() + name + ".qep"; @@ -559,6 +567,10 @@ void EffectMakerModel::exportComposition(const QString &name) saveFile.write(jsonDoc.toJson()); saveFile.close(); + setCurrentComposition(name); + setHasUnsavedChanges(false); + + saveResources(name); } void EffectMakerModel::openComposition(const QString &path) @@ -615,6 +627,10 @@ void EffectMakerModel::openComposition(const QString &path) for (const auto &nodeElement : nodesArray) { beginInsertRows({}, m_nodes.size(), m_nodes.size()); auto *node = new CompositionNode(effectName, "", nodeElement.toObject()); + connect(qobject_cast(node->uniformsModel()), + &EffectMakerUniformsModel::dataChanged, this, [this] { + setHasUnsavedChanges(true); + }); m_nodes.append(node); endInsertRows(); } @@ -623,10 +639,11 @@ void EffectMakerModel::openComposition(const QString &path) bakeShaders(); } + setHasUnsavedChanges(false); emit nodesChanged(); } -void EffectMakerModel::exportResources(const QString &name) +void EffectMakerModel::saveResources(const QString &name) { // Make sure that uniforms are up-to-date updateCustomUniforms(); @@ -692,7 +709,7 @@ void EffectMakerModel::exportResources(const QString &name) QString qmlFilePath = effectsResPath + qmlFilename; writeToFile(qmlString.toUtf8(), qmlFilePath, FileType::Text); - // Export shaders and images + // Save shaders and images QStringList sources = {m_vertexShaderFilename, m_fragmentShaderFilename}; QStringList dests = {vsFilename, fsFilename}; @@ -721,7 +738,7 @@ void EffectMakerModel::exportResources(const QString &name) 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) @@ -1440,10 +1457,25 @@ void EffectMakerModel::setCurrentComposition(const QString &newCurrentCompositio { if (m_currentComposition == newCurrentComposition) return; + m_currentComposition = newCurrentComposition; 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 usedList; diff --git a/src/plugins/effectmakernew/effectmakermodel.h b/src/plugins/effectmakernew/effectmakermodel.h index 02e46ce2cd7..c06a1d5749b 100644 --- a/src/plugins/effectmakernew/effectmakermodel.h +++ b/src/plugins/effectmakernew/effectmakermodel.h @@ -44,6 +44,7 @@ class EffectMakerModel : public QAbstractListModel 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) @@ -81,14 +82,16 @@ public: Q_INVOKABLE void resetEffectError(int type); Q_INVOKABLE void setEffectError(const QString &errorMessage, int type = -1, int lineNumber = -1); - Q_INVOKABLE void exportComposition(const QString &name); - Q_INVOKABLE void exportResources(const QString &name); + 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; signals: @@ -97,10 +100,10 @@ signals: void effectErrorChanged(); void shadersUpToDateChanged(); void shadersBaked(); - void currentCompositionChanged(); void nodesChanged(); - void resourcesExported(const QByteArray &type, const Utils::FilePath &path); + void resourcesSaved(const QByteArray &type, const Utils::FilePath &path); + void hasUnsavedChangesChanged(); private: enum Roles { @@ -152,6 +155,7 @@ private: void updateCustomUniforms(); void createFiles(); void bakeShaders(); + void saveResources(const QString &name); QString mipmapPropertyName(const QString &name) const; QString getQmlImagesString(bool localFiles); @@ -161,6 +165,7 @@ private: 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; diff --git a/src/plugins/effectmakernew/effectmakerwidget.cpp b/src/plugins/effectmakernew/effectmakerwidget.cpp index ceb8017ce43..877d0e2d523 100644 --- a/src/plugins/effectmakernew/effectmakerwidget.cpp +++ b/src/plugins/effectmakernew/effectmakerwidget.cpp @@ -86,7 +86,7 @@ EffectMakerWidget::EffectMakerWidget(EffectMakerView *view) 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) { if (!m_importScan.timer) { m_importScan.timer = new QTimer(this);