forked from qt-creator/qt-creator
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:
@@ -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 {
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user