forked from qt-creator/qt-creator
Node graph save functionality
Change-Id: Ic43649b68d6488a77a7b7ac055ef5377877d7646 Reviewed-by: spyro-adb <adb@spyro-soft.com>
This commit is contained in:
committed by
spyro-adb
parent
e17d213580
commit
db42dde3b4
@@ -22,6 +22,7 @@ TreeViewDelegate {
|
||||
readonly property string suffix: model.fileName.substr(-4)
|
||||
readonly property bool isFont: root.suffix === ".ttf" || root.suffix === ".otf"
|
||||
readonly property bool isEffect: root.suffix === ".qep"
|
||||
readonly property bool isNodeGraph: root.suffix === ".qng"
|
||||
property bool currFileSelected: false
|
||||
property int initialDepth: -1
|
||||
property bool __isDirectory: assetsModel.isDirectory(model.filePath)
|
||||
@@ -202,6 +203,10 @@ TreeViewDelegate {
|
||||
AssetsLibraryBackend.tooltipBackend.hideTooltip()
|
||||
if (mouse.button === Qt.LeftButton && root.isEffect)
|
||||
AssetsLibraryBackend.rootView.openEffectComposer(filePath)
|
||||
|
||||
if (mouse.button === Qt.LeftButton && root.isNodeGraph){
|
||||
AssetsLibraryBackend.rootView.openNodeGraphEditor(filePath)
|
||||
}
|
||||
}
|
||||
|
||||
ToolTip {
|
||||
|
@@ -12,6 +12,7 @@ GridItem {
|
||||
readonly property bool isFont: root.suffix === ".ttf" || root.suffix === ".otf"
|
||||
readonly property bool isEffect: root.suffix === ".qep"
|
||||
readonly property bool isMaterial: root.suffix === ".mat"
|
||||
readonly property bool isNodeGraph: root.suffix === ".qng"
|
||||
|
||||
icon.source: "image://qmldesigner_assets/" + model.filePath
|
||||
|
||||
@@ -48,10 +49,15 @@ GridItem {
|
||||
|
||||
mouseArea.onDoubleClicked: (mouse) => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
if (root.isEffect)
|
||||
if (root.isEffect) {
|
||||
root.rootView.openEffectComposer(filePath)
|
||||
else if (root.isMaterial)
|
||||
}
|
||||
else if (root.isMaterial) {
|
||||
root.rootView.openMaterialEditor(filePath)
|
||||
}
|
||||
else if (root.isNodeGraph) {
|
||||
root.rootView.openNodeGraphEditor(filePath)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -9,12 +9,195 @@ import StudioTheme as StudioTheme
|
||||
|
||||
import QuickQanava as Qan
|
||||
import Editor as Editor
|
||||
import Nodes as Nodes
|
||||
|
||||
import NodeGraphEditorBackend
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string nodeGraphFileName
|
||||
|
||||
// Invoked after save changes is done
|
||||
property var onSaveChangesCallback: () => {}
|
||||
|
||||
function initEdges(edges) {
|
||||
for (let edge in edges) {
|
||||
let outNodeUuid = edges[edge].outNodeUuid;
|
||||
let inNodeUuid = edges[edge].inNodeUuid;
|
||||
let n1, n2;
|
||||
|
||||
for (let i = 0; i < graphView.graph.getNodeCount(); i++) {
|
||||
if (graphView.graph.nodes.at(i).item.uuid == outNodeUuid) {
|
||||
n1 = graphView.graph.nodes.at(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < graphView.graph.getNodeCount(); i++) {
|
||||
if (graphView.graph.nodes.at(i).item.uuid == inNodeUuid) {
|
||||
n2 = graphView.graph.nodes.at(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
let e = graphView.graph.insertEdge(n1, n2);
|
||||
let p1 = n1.item.findPort(edges[edge].outPortName);
|
||||
let p2 = n2.item.findPort(edges[edge].inPortName);
|
||||
|
||||
graphView.graph.bindEdge(e, p1, p2);
|
||||
if (p2.dataBinding) {
|
||||
p2.dataBinding(n1.item.value);
|
||||
} else {
|
||||
// TODO: change old implementation
|
||||
n2.item.value[p2.dataName] = Qt.binding(() => {
|
||||
if (n1.dataName !== "")
|
||||
return n1.item.value[p1.dataName];
|
||||
return n1.item.value;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initNodes(nodes) {
|
||||
graphView.graph.clearGraph();
|
||||
let n = undefined;
|
||||
for (let node in nodes) {
|
||||
switch (nodes[node].type) {
|
||||
case "Material":
|
||||
n = graphView.graph.insertNode(Nodes.Components.material);
|
||||
break;
|
||||
case "Color":
|
||||
n = graphView.graph.insertNode(Nodes.Components.color);
|
||||
n.item.value.color = nodes[node].value.color;
|
||||
break;
|
||||
case "Metalness":
|
||||
n = graphView.graph.insertNode(Nodes.Components.metalness);
|
||||
break;
|
||||
case "BaseColor":
|
||||
n = graphView.graph.insertNode(Nodes.Components.baseColor);
|
||||
break;
|
||||
case "RealSpinBox":
|
||||
n = graphView.graph.insertNode(Nodes.Components.realSpinBox);
|
||||
n.item.value.floating = nodes[node].value.floating;
|
||||
break;
|
||||
case "Texture":
|
||||
n = graphView.graph.insertNode(Nodes.Components.texture);
|
||||
break;
|
||||
case "CheckBox":
|
||||
n = graphView.graph.insertNode(Nodes.Components.checkBox);
|
||||
n.item.value.binary = nodes[node].value.binary;
|
||||
break;
|
||||
case "ComboBox":
|
||||
n = graphView.graph.insertNode(Nodes.Components.comboBox);
|
||||
n.item.value.text = nodes[node].value.text;
|
||||
break;
|
||||
case "Roughness":
|
||||
n = graphView.graph.insertNode(Nodes.Components.roughness);
|
||||
break;
|
||||
}
|
||||
n.item.x = nodes[node].x;
|
||||
n.item.y = nodes[node].y;
|
||||
n.item.uuid = nodes[node].uuid;
|
||||
}
|
||||
}
|
||||
|
||||
// Invoked from C++ side when open node graph is requested and there are unsaved changes
|
||||
function promptToSaveBeforeOpen(newNodeGraphName) {
|
||||
nodeGraphFileName = newNodeGraphName;
|
||||
saveChangesDialog.open();
|
||||
}
|
||||
|
||||
function updateGraphData() {
|
||||
let arr = {
|
||||
nodes: [],
|
||||
edges: []
|
||||
};
|
||||
|
||||
for (let i = 0; i < graphView.graph.nodes.length; i++) {
|
||||
let node = {
|
||||
uuid: graphView.graph.nodes.at(i).item.uuid,
|
||||
x: graphView.graph.nodes.at(i).item.x,
|
||||
y: graphView.graph.nodes.at(i).item.y,
|
||||
type: graphView.graph.nodes.at(i).item.type
|
||||
};
|
||||
|
||||
//Set the actual value only to the OUT nodes
|
||||
if (node.type == "Color" || node.type == "RealSpinBox" || node.type == "CheckBox" || node.type == "ComboBox") {
|
||||
node.value = graphView.graph.nodes.at(i).item.value;
|
||||
}
|
||||
arr["nodes"].push(node);
|
||||
}
|
||||
|
||||
for (let i = 0; i < graphView.graph.edges.length; i++) {
|
||||
let edge = {
|
||||
outNodeUuid: graphView.graph.edges.at(i).item.sourceItem.node.item.uuid,
|
||||
inNodeUuid: graphView.graph.edges.at(i).item.destinationItem.node.item.uuid,
|
||||
inPortName: graphView.graph.edges.at(i).item.destinationItem.dataId,
|
||||
outPortName: graphView.graph.edges.at(i).item.sourceItem.dataId
|
||||
};
|
||||
arr["edges"].push(edge);
|
||||
}
|
||||
let newGraphData = JSON.stringify(arr);
|
||||
if (NodeGraphEditorBackend.nodeGraphEditorModel.graphData !== newGraphData) {
|
||||
NodeGraphEditorBackend.nodeGraphEditorModel.graphData = newGraphData;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: NodeGraphEditorBackend.nodeGraphEditorModel
|
||||
|
||||
onNodeGraphLoaded: {
|
||||
graphView.graph.clearGraph();
|
||||
let jsonString = NodeGraphEditorBackend.nodeGraphEditorModel.graphData;
|
||||
let jsonObject = JSON.parse(jsonString);
|
||||
let nodes = jsonObject.nodes;
|
||||
let edges = jsonObject.edges;
|
||||
initNodes(nodes);
|
||||
initEdges(edges);
|
||||
NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = false;
|
||||
}
|
||||
}
|
||||
|
||||
SaveAsDialog {
|
||||
id: saveAsDialog
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
onSave: {
|
||||
updateGraphData();
|
||||
NodeGraphEditorBackend.nodeGraphEditorModel.saveFile(fileName);
|
||||
NodeGraphEditorBackend.nodeGraphEditorModel.openFileName(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
SaveChangesDialog {
|
||||
id: saveChangesDialog
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
onDiscard: {
|
||||
NodeGraphEditorBackend.nodeGraphEditorModel.currentFileName = nodeGraphFileName;
|
||||
NodeGraphEditorBackend.nodeGraphEditorModel.openFileName(NodeGraphEditorBackend.nodeGraphEditorModel.currentFileName);
|
||||
}
|
||||
onRejected: {}
|
||||
onSave: {
|
||||
if (NodeGraphEditorBackend.nodeGraphEditorModel.currentFileName !== "") {
|
||||
/*Save current graph data to the backend*/
|
||||
updateGraphData();
|
||||
|
||||
/*Save old data, before opening new node graph*/
|
||||
NodeGraphEditorBackend.nodeGraphEditorModel.saveFile(NodeGraphEditorBackend.nodeGraphEditorModel.currentFileName);
|
||||
NodeGraphEditorBackend.nodeGraphEditorModel.currentFileName = root.nodeGraphFileName;
|
||||
|
||||
/*Open new node graph*/
|
||||
NodeGraphEditorBackend.nodeGraphEditorModel.openFileName(NodeGraphEditorBackend.nodeGraphEditorModel.currentFileName);
|
||||
NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = false;
|
||||
} else {
|
||||
saveAsDialog.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Editor.ContextMenu {
|
||||
id: contextMenu
|
||||
|
||||
@@ -49,6 +232,47 @@ Item {
|
||||
newNodeGraphDialog.open();
|
||||
}
|
||||
}
|
||||
|
||||
HelperWidgets.AbstractButton {
|
||||
buttonIcon: StudioTheme.Constants.remove_medium
|
||||
style: StudioTheme.Values.viewBarButtonStyle
|
||||
tooltip: qsTr("Clear graph.")
|
||||
|
||||
onClicked: () => {
|
||||
graphView.graph.clearGraph();
|
||||
}
|
||||
}
|
||||
|
||||
HelperWidgets.AbstractButton {
|
||||
buttonIcon: StudioTheme.Constants.save_medium
|
||||
enabled: NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges && NodeGraphEditorBackend.nodeGraphEditorModel.currentFileName != ""
|
||||
style: StudioTheme.Values.viewBarButtonStyle
|
||||
tooltip: qsTr("Save.")
|
||||
|
||||
onClicked: () => {
|
||||
if (NodeGraphEditorBackend.nodeGraphEditorModel.currentFileName !== "") {
|
||||
updateGraphData();
|
||||
NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = false;
|
||||
NodeGraphEditorBackend.nodeGraphEditorModel.saveFile(NodeGraphEditorBackend.nodeGraphEditorModel.currentFileName);
|
||||
} else {
|
||||
saveAsDialog.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HelperWidgets.AbstractButton {
|
||||
buttonIcon: StudioTheme.Constants.saveAs_medium
|
||||
style: StudioTheme.Values.viewBarButtonStyle
|
||||
tooltip: qsTr("Save as ...")
|
||||
|
||||
onClicked: () => {
|
||||
saveAsDialog.open();
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: NodeGraphEditorBackend.nodeGraphEditorModel.currentFileName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
106
share/qtcreator/qmldesigner/nodegrapheditor/SaveAsDialog.qml
Normal file
106
share/qtcreator/qmldesigner/nodegrapheditor/SaveAsDialog.qml
Normal file
@@ -0,0 +1,106 @@
|
||||
// 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 EffectComposerBackend
|
||||
|
||||
StudioControls.Dialog {
|
||||
id: root
|
||||
|
||||
property alias fileName: nameText.text
|
||||
|
||||
signal save(string fileName)
|
||||
|
||||
closePolicy: Popup.CloseOnEscape
|
||||
implicitHeight: 160
|
||||
implicitWidth: 350
|
||||
modal: true
|
||||
title: qsTr("Save node graph")
|
||||
|
||||
contentItem: Item {
|
||||
Column {
|
||||
spacing: 2
|
||||
|
||||
Row {
|
||||
id: row
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: StudioTheme.Values.themeTextColor
|
||||
text: qsTr("Node graph name: ")
|
||||
}
|
||||
|
||||
StudioControls.TextField {
|
||||
id: nameText
|
||||
|
||||
actionIndicator.visible: false
|
||||
translationIndicator.visible: false
|
||||
|
||||
Keys.onEnterPressed: btnSave.onClicked()
|
||||
Keys.onEscapePressed: root.reject()
|
||||
Keys.onReturnPressed: btnSave.onClicked()
|
||||
onTextChanged: {
|
||||
let errMsg = "";
|
||||
if (/[^A-Za-z0-9_]+/.test(text))
|
||||
errMsg = qsTr("Name contains invalid characters.");
|
||||
// if (!/^[A-Z]/.test(text))
|
||||
// errMsg = qsTr("Name must start with a capital letter.")
|
||||
if (text.length < 1)
|
||||
errMsg = qsTr("Name must have at least 1 character.");
|
||||
else if (/\s/.test(text))
|
||||
errMsg = qsTr("Name cannot contain white space.");
|
||||
|
||||
emptyText.text = errMsg;
|
||||
btnSave.enabled = errMsg.length === 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: emptyText
|
||||
|
||||
anchors.right: row.right
|
||||
color: StudioTheme.Values.themeError
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
spacing: 2
|
||||
|
||||
HelperWidgets.Button {
|
||||
id: btnSave
|
||||
|
||||
enabled: nameText.text !== ""
|
||||
text: qsTr("Save")
|
||||
|
||||
onClicked: {
|
||||
if (!enabled) // needed since this event handler can be triggered from keyboard events
|
||||
return;
|
||||
root.save(nameText.text);
|
||||
root.accept(); // TODO: confirm before overriding node graph with same name
|
||||
}
|
||||
}
|
||||
|
||||
HelperWidgets.Button {
|
||||
text: qsTr("Cancel")
|
||||
|
||||
onClicked: {
|
||||
root.reject();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onOpened: {
|
||||
nameText.text = "";
|
||||
nameText.selectAll();
|
||||
nameText.forceActiveFocus();
|
||||
emptyText.text = "";
|
||||
}
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
// 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 EffectComposerBackend
|
||||
|
||||
StudioControls.Dialog {
|
||||
id: root
|
||||
|
||||
signal discard
|
||||
signal save
|
||||
|
||||
closePolicy: Popup.CloseOnEscape
|
||||
implicitHeight: 130
|
||||
implicitWidth: 300
|
||||
modal: true
|
||||
title: qsTr("Save Changes")
|
||||
|
||||
contentItem: Item {
|
||||
Text {
|
||||
color: StudioTheme.Values.themeTextColor
|
||||
text: qsTr("Current node graph has unsaved changes.")
|
||||
}
|
||||
|
||||
HelperWidgets.Button {
|
||||
anchors.bottom: parent.bottom
|
||||
text: qsTr("Cancel")
|
||||
width: 60
|
||||
|
||||
onClicked: root.reject()
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
spacing: 2
|
||||
|
||||
HelperWidgets.Button {
|
||||
text: qsTr("Save")
|
||||
width: 50
|
||||
|
||||
onClicked: {
|
||||
root.save();
|
||||
root.accept();
|
||||
}
|
||||
}
|
||||
|
||||
HelperWidgets.Button {
|
||||
text: qsTr("Discard Changes")
|
||||
width: 110
|
||||
|
||||
onClicked: {
|
||||
root.discard();
|
||||
root.accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,6 +4,7 @@
|
||||
import QtQuick
|
||||
|
||||
import QuickQanava as Qan
|
||||
import NodeGraphEditorBackend
|
||||
|
||||
Qan.Graph {
|
||||
id: root
|
||||
@@ -34,7 +35,7 @@ Qan.Graph {
|
||||
});
|
||||
}
|
||||
}
|
||||
onNodeRemoved: node => {}
|
||||
onNodeRemoved: node => {NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = true;}
|
||||
onOnEdgeRemoved: edge => {
|
||||
const srcNode = edge.getSource();
|
||||
const dstNode = edge.getDestination();
|
||||
@@ -43,5 +44,13 @@ Qan.Graph {
|
||||
|
||||
// TODO: add reset binding function
|
||||
dstNode.item.value[dstPortItem.dataName] = dstNode.item.reset[dstPortItem.dataName];
|
||||
NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = true;
|
||||
}
|
||||
|
||||
onEdgeInserted: {
|
||||
NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = true;
|
||||
}
|
||||
onNodeInserted: {
|
||||
NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = true;
|
||||
}
|
||||
}
|
||||
|
@@ -11,4 +11,5 @@ Qan.Port {
|
||||
property var dataBinding: null
|
||||
property string dataName: ""
|
||||
property string dataType: ""
|
||||
property string dataId: ""
|
||||
}
|
||||
|
@@ -23,7 +23,8 @@ Qan.NodeItem {
|
||||
property var pin: []
|
||||
property var pout: []
|
||||
}
|
||||
readonly property string uuid: NodeGraphEditorBackend.widget.generateUUID()
|
||||
property string type
|
||||
property string uuid: NodeGraphEditorBackend.widget.generateUUID()
|
||||
|
||||
Layout.preferredHeight: 60
|
||||
Layout.preferredWidth: 100
|
||||
@@ -34,6 +35,12 @@ Qan.NodeItem {
|
||||
Component.onCompleted: {
|
||||
internal.configurePorts(root.graph);
|
||||
}
|
||||
onXChanged: {
|
||||
NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = true;
|
||||
}
|
||||
onYChanged: {
|
||||
NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = true;
|
||||
}
|
||||
|
||||
Qan.RectNodeTemplate {
|
||||
anchors.fill: parent
|
||||
@@ -56,6 +63,7 @@ Qan.NodeItem {
|
||||
}
|
||||
portItem.dataName = data.alias;
|
||||
portItem.dataType = data.type;
|
||||
portItem.dataId = data.id;
|
||||
};
|
||||
|
||||
root.portsMetaData.pin.forEach(data => {
|
||||
|
@@ -24,6 +24,7 @@ Base {
|
||||
|
||||
Layout.preferredHeight: 150
|
||||
Layout.preferredWidth: 150
|
||||
type: "BaseColor"
|
||||
|
||||
portsMetaData: QtObject {
|
||||
property var pin: [
|
||||
|
@@ -5,14 +5,17 @@ import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
import StudioControls as StudioControls
|
||||
import NodeGraphEditorBackend
|
||||
|
||||
Base {
|
||||
id: root
|
||||
|
||||
readonly property QtObject value: QtObject {
|
||||
property bool binary: checkBox.checked
|
||||
property bool binary
|
||||
}
|
||||
|
||||
type: "CheckBox"
|
||||
|
||||
portsMetaData: QtObject {
|
||||
property var pin: []
|
||||
property var pout: [
|
||||
@@ -34,5 +37,11 @@ Base {
|
||||
|
||||
actionIndicatorVisible: false
|
||||
anchors.centerIn: parent
|
||||
checked: root.value.binary
|
||||
|
||||
onCheckedChanged: {
|
||||
NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = true;
|
||||
root.value.binary = checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,14 +3,21 @@
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import NodeGraphEditorBackend
|
||||
|
||||
Base {
|
||||
id: root
|
||||
|
||||
readonly property QtObject value: QtObject {
|
||||
property color color
|
||||
|
||||
onColorChanged: {
|
||||
NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = true;
|
||||
}
|
||||
}
|
||||
|
||||
type: "Color"
|
||||
|
||||
portsMetaData: QtObject {
|
||||
property var pin: []
|
||||
property var pout: [
|
||||
|
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
import QtQuick
|
||||
|
||||
import HelperWidgets as HelperWidgets
|
||||
import StudioControls as StudioControls
|
||||
|
||||
@@ -18,7 +17,6 @@ StudioControls.PopupDialog {
|
||||
function open(showItem) {
|
||||
loader.ensureActive();
|
||||
colorPopup.show(showItem);
|
||||
|
||||
loader.updateOriginalColor();
|
||||
}
|
||||
|
||||
|
@@ -6,15 +6,21 @@ import QtQuick.Layouts
|
||||
|
||||
import HelperWidgets as HelperWidgets
|
||||
import StudioControls as StudioControls
|
||||
import NodeGraphEditorBackend
|
||||
|
||||
Base {
|
||||
id: root
|
||||
|
||||
property QtObject value: QtObject {
|
||||
property url text: `image://qmldesigner_nodegrapheditor/${comboBox.currentValue}`
|
||||
|
||||
onTextChanged: {
|
||||
NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = true;
|
||||
}
|
||||
}
|
||||
|
||||
Layout.preferredWidth: 175
|
||||
type: "ComboBox"
|
||||
|
||||
portsMetaData: QtObject {
|
||||
property var pin: []
|
||||
|
@@ -19,6 +19,7 @@ Base {
|
||||
|
||||
Layout.preferredHeight: 150
|
||||
Layout.preferredWidth: 150
|
||||
type: "Material"
|
||||
|
||||
portsMetaData: QtObject {
|
||||
property var pin: [
|
||||
|
@@ -22,6 +22,7 @@ Base {
|
||||
|
||||
Layout.preferredHeight: 150
|
||||
Layout.preferredWidth: 150
|
||||
type: "Metalness"
|
||||
|
||||
portsMetaData: QtObject {
|
||||
property var pin: [
|
||||
|
@@ -5,15 +5,17 @@ import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
import StudioControls as StudioControls
|
||||
import NodeGraphEditorBackend
|
||||
|
||||
Base {
|
||||
id: root
|
||||
|
||||
readonly property QtObject value: QtObject {
|
||||
property real floating: realSpinBox.realValue
|
||||
property real floating
|
||||
}
|
||||
|
||||
Layout.preferredWidth: 175
|
||||
type: "RealSpinBox"
|
||||
|
||||
portsMetaData: QtObject {
|
||||
property var pin: []
|
||||
@@ -37,5 +39,11 @@ Base {
|
||||
actionIndicatorVisible: false
|
||||
anchors.centerIn: parent
|
||||
decimals: 2
|
||||
realValue: root.value.floating
|
||||
|
||||
onRealValueChanged: {
|
||||
NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = true;
|
||||
root.value.floating = realValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -22,6 +22,7 @@ Base {
|
||||
|
||||
Layout.preferredHeight: 150
|
||||
Layout.preferredWidth: 150
|
||||
type: "Roughness"
|
||||
|
||||
portsMetaData: QtObject {
|
||||
property var pin: [
|
||||
|
@@ -7,6 +7,7 @@ import QtQuick3D as QtQuick3D
|
||||
|
||||
import QuickQanava as Qan
|
||||
|
||||
import NodeGraphEditorBackend
|
||||
import NodeGraphEditorBackend
|
||||
|
||||
Base {
|
||||
@@ -19,10 +20,14 @@ Base {
|
||||
|
||||
Layout.preferredHeight: 150
|
||||
Layout.preferredWidth: 150
|
||||
type: "Texture"
|
||||
|
||||
Component.onCompleted: {
|
||||
node.label = "Texture";
|
||||
}
|
||||
onValueChanged: {
|
||||
NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = true;
|
||||
}
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
|
@@ -33,7 +33,6 @@ option(ENABLE_METAINFO_TRACING "Enable meta info tracing" ${ENV_QTC_ENABLE_METAI
|
||||
add_feature_info("Meta info tracing" ${ENABLE_METAINFO_TRACING} "")
|
||||
|
||||
add_subdirectory(libs)
|
||||
|
||||
add_qtc_plugin(QmlDesigner
|
||||
PLUGIN_RECOMMENDS QmlPreview
|
||||
CONDITION TARGET QmlDesignerCore AND TARGET Qt::QuickWidgets AND TARGET Qt::Svg
|
||||
|
@@ -124,7 +124,7 @@ QPair<QPixmap, qint64> AssetsLibraryIconProvider::fetchPixmap(const QString &id,
|
||||
type = "sound";
|
||||
else if (asset.isVideo())
|
||||
type = "video";
|
||||
else if (asset.isEffect())
|
||||
else if (asset.isEffect() || asset.isNodeGraph())
|
||||
type = QmlDesigner::ModelNodeOperations::getEffectIcon(id);
|
||||
|
||||
QString pathTemplate = QString(":/AssetsLibrary/images/asset_%1%2.png").arg(type);
|
||||
|
@@ -612,6 +612,11 @@ QSet<QString> AssetsLibraryWidget::supportedAssetSuffixes(bool complex)
|
||||
return suffixes;
|
||||
}
|
||||
|
||||
void AssetsLibraryWidget::openNodeGraphEditor(const QString &filePath)
|
||||
{
|
||||
ModelNodeOperations::openNodeGraphEditor(filePath);
|
||||
}
|
||||
|
||||
void AssetsLibraryWidget::openEffectComposer(const QString &filePath)
|
||||
{
|
||||
ModelNodeOperations::openEffectComposer(filePath);
|
||||
@@ -695,6 +700,9 @@ QPair<QString, QByteArray> AssetsLibraryWidget::getAssetTypeAndData(const QStrin
|
||||
} else if (asset.isEffect()) {
|
||||
// Data: Effect Composer format (suffix)
|
||||
return {Constants::MIME_TYPE_ASSET_EFFECT, asset.suffix().toUtf8()};
|
||||
} else if (asset.isNodeGraph()) {
|
||||
// Data: Node Grpah Editor format (suffix)
|
||||
return {Constants::MIME_TYPE_ASSET_NODEGRAPH, asset.suffix().toUtf8()};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
|
@@ -85,6 +85,7 @@ public:
|
||||
const QString &targetDirPath = {});
|
||||
Q_INVOKABLE QSet<QString> supportedAssetSuffixes(bool complex);
|
||||
Q_INVOKABLE void openEffectComposer(const QString &filePath);
|
||||
Q_INVOKABLE void openNodeGraphEditor(const QString &filePath);
|
||||
Q_INVOKABLE int qtVersion() const;
|
||||
Q_INVOKABLE void invalidateThumbnail(const QString &id);
|
||||
Q_INVOKABLE QSize imageSize(const QString &id);
|
||||
|
@@ -2213,6 +2213,13 @@ void openEffectComposer(const QString &filePath)
|
||||
}
|
||||
}
|
||||
|
||||
void openNodeGraphEditor(const QString &filePath)
|
||||
{
|
||||
QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("NodeGraphEditor", true);
|
||||
QmlDesignerPlugin::instance()->viewManager()
|
||||
.emitCustomNotification("open_nodegrapheditor_graph", {}, {filePath});
|
||||
}
|
||||
|
||||
void openOldEffectMaker(const QString &filePath)
|
||||
{
|
||||
const ProjectExplorer::Target *target = ProjectExplorer::ProjectTree::currentTarget();
|
||||
@@ -2282,6 +2289,7 @@ QString getEffectsDefaultDirectory(const QString &defaultDir)
|
||||
return getAssetDefaultDirectory("effects", defaultDir);
|
||||
}
|
||||
|
||||
|
||||
QString getEffectIcon(const QString &effectPath)
|
||||
{
|
||||
Utils::FilePath effectFile = QmlDesignerPlugin::instance()->documentManager()
|
||||
|
@@ -212,6 +212,7 @@ void editInEffectComposer(const SelectionContext &selectionContext);
|
||||
QMLDESIGNERCOMPONENTS_EXPORT Utils::FilePath getEffectsImportDirectory();
|
||||
QMLDESIGNERCOMPONENTS_EXPORT QString getEffectsDefaultDirectory(const QString &defaultDir = {});
|
||||
void openEffectComposer(const QString &filePath);
|
||||
void openNodeGraphEditor(const QString &filePath);
|
||||
void openOldEffectMaker(const QString &filePath);
|
||||
QString getEffectIcon(const QString &effectPath);
|
||||
bool useLayerEffect();
|
||||
|
@@ -5,6 +5,9 @@
|
||||
|
||||
#include "nodegrapheditorview.h"
|
||||
|
||||
#include "nodegrapheditorview.h"
|
||||
#include <qmldesignerplugin.h>
|
||||
#include <QFileInfo>
|
||||
namespace QmlDesigner {
|
||||
|
||||
NodeGraphEditorModel::NodeGraphEditorModel(NodeGraphEditorView *nodeGraphEditorView)
|
||||
@@ -13,4 +16,113 @@ NodeGraphEditorModel::NodeGraphEditorModel(NodeGraphEditorView *nodeGraphEditorV
|
||||
{
|
||||
}
|
||||
|
||||
void NodeGraphEditorModel::saveFile(QString fileName){
|
||||
auto directory = saveDirectory();
|
||||
auto saveFile = QFile(directory + '/' + fileName + ".qng");
|
||||
if (!saveFile.open(QIODevice::WriteOnly)) {
|
||||
QString error = QString("Error: Couldn't save node graph file");
|
||||
qCritical() << error;
|
||||
return;
|
||||
}
|
||||
auto str = m_graphData.toStdString();
|
||||
saveFile.write(str.c_str());
|
||||
saveFile.close();
|
||||
}
|
||||
|
||||
|
||||
void NodeGraphEditorModel::openFile(QString filePath){
|
||||
const QString nodeGraphName = QFileInfo(filePath).baseName();
|
||||
setCurrentFileName(nodeGraphName);
|
||||
QFile nodeGraphFile(filePath);
|
||||
if (!nodeGraphFile.open(QIODevice::ReadOnly)) {
|
||||
QString error = QString("Couldn't open node graph file: '%1'").arg(filePath);
|
||||
qWarning() << qPrintable(error);
|
||||
return;
|
||||
}
|
||||
QByteArray data = nodeGraphFile.readAll();
|
||||
m_graphData = QString::fromStdString(data.toStdString());
|
||||
graphDataChanged();
|
||||
nodeGraphLoaded();
|
||||
|
||||
nodeGraphFile.close();
|
||||
if (data.isEmpty())
|
||||
return;
|
||||
}
|
||||
QString NodeGraphEditorModel::saveDirectory(){
|
||||
if (m_saveDirectory!="") {
|
||||
return m_saveDirectory;
|
||||
} else {
|
||||
const QString assetDir = "nodegraphs";
|
||||
QString targetDir = QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath().toString();
|
||||
QString adjustedDefaultDirectory = targetDir;
|
||||
Utils::FilePath contentPath = QmlDesignerPlugin::instance()->documentManager().currentResourcePath();
|
||||
Utils::FilePath assetPath = contentPath.pathAppended(assetDir);
|
||||
if (!assetPath.exists())
|
||||
assetPath.createDir();
|
||||
|
||||
if (assetPath.exists() && assetPath.isDir())
|
||||
adjustedDefaultDirectory = assetPath.toString();
|
||||
m_saveDirectory = adjustedDefaultDirectory;
|
||||
}
|
||||
return m_saveDirectory;
|
||||
}
|
||||
|
||||
void NodeGraphEditorModel::openFileName(QString fileName){
|
||||
auto directory = saveDirectory();
|
||||
|
||||
const QString nodeGraphName = QFileInfo(fileName).baseName();
|
||||
setCurrentFileName(nodeGraphName);
|
||||
QFile nodeGraphFile(directory + "/" + fileName + ".qng");
|
||||
if (!nodeGraphFile.open(QIODevice::ReadOnly)) {
|
||||
QString error = QString("Couldn't open node graph file: '%1'").arg(nodeGraphFile.fileName());
|
||||
qWarning() << qPrintable(error);
|
||||
return;
|
||||
}
|
||||
QByteArray data = nodeGraphFile.readAll();
|
||||
m_graphData = QString::fromStdString(data.toStdString());
|
||||
graphDataChanged();
|
||||
nodeGraphLoaded();
|
||||
|
||||
nodeGraphFile.close();
|
||||
if (data.isEmpty())
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
bool NodeGraphEditorModel::hasUnsavedChanges() const
|
||||
{
|
||||
return m_hasUnsavedChanges;
|
||||
}
|
||||
|
||||
void NodeGraphEditorModel::setHasUnsavedChanges(bool val)
|
||||
{
|
||||
if (m_hasUnsavedChanges == val)
|
||||
return;
|
||||
|
||||
m_hasUnsavedChanges = val;
|
||||
emit hasUnsavedChangesChanged();
|
||||
}
|
||||
|
||||
|
||||
QString NodeGraphEditorModel::currentFileName() const{
|
||||
return m_currentFileName;
|
||||
}
|
||||
|
||||
void NodeGraphEditorModel::setCurrentFileName(const QString &newCurrentFileName){
|
||||
if (m_currentFileName == newCurrentFileName)
|
||||
return;
|
||||
|
||||
m_currentFileName = newCurrentFileName;
|
||||
emit currentFileNameChanged();
|
||||
}
|
||||
|
||||
void NodeGraphEditorModel::setGraphData(QString val){
|
||||
if (m_graphData == val)
|
||||
return;
|
||||
|
||||
m_graphData = val;
|
||||
emit graphDataChanged();
|
||||
};
|
||||
|
||||
QString NodeGraphEditorModel::graphData(){return m_graphData;}
|
||||
} // namespace QmlDesigner
|
||||
|
@@ -16,9 +16,34 @@ class NodeGraphEditorModel : public QStandardItemModel
|
||||
|
||||
public:
|
||||
NodeGraphEditorModel(NodeGraphEditorView *nodeGraphEditorView);
|
||||
|
||||
Q_INVOKABLE void openFile(QString filePath);
|
||||
Q_INVOKABLE void openFileName(QString filePath);
|
||||
Q_INVOKABLE void saveFile(QString fileName);
|
||||
private:
|
||||
QPointer<NodeGraphEditorView> m_editorView;
|
||||
|
||||
|
||||
Q_PROPERTY(bool hasUnsavedChanges MEMBER m_hasUnsavedChanges WRITE setHasUnsavedChanges NOTIFY hasUnsavedChangesChanged)
|
||||
Q_PROPERTY(QString currentFileName READ currentFileName WRITE setCurrentFileName NOTIFY currentFileNameChanged)
|
||||
Q_PROPERTY(QString graphData READ graphData WRITE setGraphData NOTIFY graphDataChanged)
|
||||
signals:
|
||||
void graphDataChanged();
|
||||
void nodeGraphLoaded();
|
||||
void hasUnsavedChangesChanged();
|
||||
void currentFileNameChanged();
|
||||
public:
|
||||
void setGraphData(QString val);
|
||||
QString graphData();
|
||||
bool hasUnsavedChanges() const;
|
||||
void setHasUnsavedChanges(bool val);
|
||||
QString currentFileName() const;
|
||||
void setCurrentFileName(const QString &newCurrentFileName);
|
||||
private:
|
||||
QString saveDirectory();
|
||||
QString m_graphData{""};
|
||||
QString m_currentFileName{""};
|
||||
QString m_saveDirectory{""};
|
||||
bool m_hasUnsavedChanges{false};
|
||||
};
|
||||
|
||||
} // namespace QmlDesigner
|
||||
|
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "nodegrapheditorview.h"
|
||||
|
||||
#include "nodegrapheditormodel.h"
|
||||
#include "nodegrapheditorwidget.h"
|
||||
|
||||
@@ -31,4 +30,16 @@ WidgetInfo NodeGraphEditorView::widgetInfo()
|
||||
tr("Node Graph"));
|
||||
}
|
||||
|
||||
void NodeGraphEditorView::customNotification([[maybe_unused]] const AbstractView *view,
|
||||
const QString &identifier,
|
||||
[[maybe_unused]] const QList<QmlDesigner::ModelNode> &nodeList,
|
||||
const QList<QVariant> &data)
|
||||
{
|
||||
if (data.size() < 1)
|
||||
return;
|
||||
if (identifier == "open_nodegrapheditor_graph") {
|
||||
const QString nodeGraphPath = data[0].toString();
|
||||
m_editorWidget->openNodeGraph(nodeGraphPath);
|
||||
}
|
||||
}
|
||||
} // namespace QmlDesigner
|
||||
|
@@ -21,8 +21,11 @@ public:
|
||||
WidgetInfo widgetInfo() override;
|
||||
|
||||
private:
|
||||
void customNotification(const AbstractView *view, const QString &identifier,
|
||||
const QList<QmlDesigner::ModelNode> &nodeList, const QList<QVariant> &data) override;
|
||||
QPointer<NodeGraphEditorModel> m_editorModel;
|
||||
QPointer<NodeGraphEditorWidget> m_editorWidget;
|
||||
|
||||
};
|
||||
|
||||
} // namespace QmlDesigner
|
||||
|
@@ -41,9 +41,28 @@ static Utils::FilePath materialsPath()
|
||||
return DocumentManager::currentResourcePath().pathAppended("materials");
|
||||
}
|
||||
|
||||
void NodeGraphEditorWidget::doOpenNodeGraph(){
|
||||
m_model->openFile(m_filePath);
|
||||
}
|
||||
|
||||
void NodeGraphEditorWidget::openNodeGraph(const QString &path)
|
||||
{
|
||||
m_filePath = path;
|
||||
if (m_model->hasUnsavedChanges()){
|
||||
/*Pass new path to update if saved*/
|
||||
auto newFile = QFileInfo(path).baseName();
|
||||
QMetaObject::invokeMethod(quickWidget()->rootObject(), "promptToSaveBeforeOpen",Q_ARG(QVariant, newFile));
|
||||
}
|
||||
else{
|
||||
doOpenNodeGraph();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
NodeGraphEditorWidget::NodeGraphEditorWidget(NodeGraphEditorView *nodeGraphEditorView,
|
||||
NodeGraphEditorModel *nodeGraphEditorModel)
|
||||
: m_editorView(nodeGraphEditorView)
|
||||
, m_model(nodeGraphEditorModel)
|
||||
, m_imageProvider(nullptr)
|
||||
, m_qmlSourceUpdateShortcut(nullptr)
|
||||
{
|
||||
@@ -64,6 +83,7 @@ NodeGraphEditorWidget::NodeGraphEditorWidget(NodeGraphEditorView *nodeGraphEdito
|
||||
auto map = registerPropertyMap("NodeGraphEditorBackend");
|
||||
map->setProperties({{"nodeGraphEditorModel", QVariant::fromValue(nodeGraphEditorModel)}});
|
||||
map->setProperties({{"widget", QVariant::fromValue(this)}});
|
||||
connect(m_qmlSourceUpdateShortcut, &QShortcut::activated, this, &NodeGraphEditorWidget::reloadQmlSource);
|
||||
|
||||
Theme::setupTheme(engine());
|
||||
|
||||
|
@@ -30,9 +30,11 @@ public:
|
||||
NodeGraphEditorWidget(NodeGraphEditorView *nodeGraphEditorView, NodeGraphEditorModel *nodeGraphEditorModel);
|
||||
~NodeGraphEditorWidget() override = default;
|
||||
|
||||
static QString qmlSourcesPath();
|
||||
|
||||
Q_INVOKABLE QString generateUUID() const;
|
||||
static QString qmlSourcesPath();
|
||||
Q_INVOKABLE void doOpenNodeGraph();
|
||||
void openNodeGraph(const QString &path);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *) override;
|
||||
@@ -41,12 +43,13 @@ protected:
|
||||
|
||||
private:
|
||||
void reloadQmlSource();
|
||||
|
||||
private:
|
||||
QPointer<NodeGraphEditorView> m_editorView;
|
||||
QPointer<NodeGraphEditorModel> m_model;
|
||||
Internal::NodeGraphEditorImageProvider *m_imageProvider;
|
||||
QShortcut *m_qmlSourceUpdateShortcut;
|
||||
QElapsedTimer m_usageTimer;
|
||||
QString m_filePath;
|
||||
};
|
||||
|
||||
} // namespace QmlDesigner
|
||||
|
@@ -85,6 +85,13 @@ const QStringList &Asset::supportedEffectComposerSuffixes()
|
||||
return retList;
|
||||
}
|
||||
|
||||
const QStringList &Asset::supportedNodeGraphSuffixes()
|
||||
{
|
||||
// These are file types only supported by Node Graph Editor
|
||||
static QStringList retList {"*.qng"};
|
||||
return retList;
|
||||
}
|
||||
|
||||
const QStringList &Asset::supportedMaterialSuffixes()
|
||||
{
|
||||
static QStringList retList {"*.mat"};
|
||||
@@ -112,6 +119,7 @@ const QSet<QString> &Asset::supportedSuffixes()
|
||||
insertSuffixes(supportedVideoSuffixes());
|
||||
insertSuffixes(supportedTexture3DSuffixes());
|
||||
insertSuffixes(supportedEffectComposerSuffixes());
|
||||
insertSuffixes(supportedNodeGraphSuffixes());
|
||||
insertSuffixes(supportedMaterialSuffixes());
|
||||
insertSuffixes(supportedImported3dSuffixes());
|
||||
}
|
||||
@@ -196,6 +204,10 @@ bool Asset::isEffect() const
|
||||
return m_type == Asset::Type::Effect;
|
||||
}
|
||||
|
||||
bool Asset::isNodeGraph() const
|
||||
{
|
||||
return m_type == Asset::Type::NodeGraph;
|
||||
}
|
||||
bool Asset::isMaterial() const
|
||||
{
|
||||
return m_type == Asset::Type::Material;
|
||||
@@ -252,6 +264,8 @@ void Asset::resolveType()
|
||||
m_type = Asset::Type::Texture3D;
|
||||
else if (supportedEffectComposerSuffixes().contains(m_suffix))
|
||||
m_type = Asset::Type::Effect;
|
||||
else if (supportedNodeGraphSuffixes().contains(m_suffix))
|
||||
m_type = Asset::Type::NodeGraph;
|
||||
else if (supportedMaterialSuffixes().contains(m_suffix))
|
||||
m_type = Asset::Type::Material;
|
||||
else if (supportedImported3dSuffixes().contains(m_suffix))
|
||||
|
@@ -26,6 +26,7 @@ public:
|
||||
Video,
|
||||
Texture3D,
|
||||
Effect,
|
||||
NodeGraph,
|
||||
Material,
|
||||
Imported3D
|
||||
};
|
||||
@@ -41,6 +42,7 @@ public:
|
||||
static const QStringList &supportedVideoSuffixes();
|
||||
static const QStringList &supportedTexture3DSuffixes();
|
||||
static const QStringList &supportedEffectComposerSuffixes();
|
||||
static const QStringList &supportedNodeGraphSuffixes();
|
||||
static const QStringList &supportedMaterialSuffixes();
|
||||
static const QStringList &supportedImported3dSuffixes();
|
||||
static const QSet<QString> &supportedSuffixes();
|
||||
@@ -64,6 +66,7 @@ public:
|
||||
bool isHdrFile() const;
|
||||
bool isKtxFile() const;
|
||||
bool isEffect() const;
|
||||
bool isNodeGraph() const;
|
||||
bool isMaterial() const;
|
||||
bool isImported3D() const;
|
||||
bool isSupported() const;
|
||||
|
@@ -88,6 +88,7 @@ inline constexpr char MIME_TYPE_ASSET_TEXTURE3D[]
|
||||
= "application/vnd.qtdesignstudio.asset.texture3d";
|
||||
inline constexpr char MIME_TYPE_MODELNODE_LIST[] = "application/vnd.qtdesignstudio.modelnode.list";
|
||||
inline constexpr char MIME_TYPE_ASSET_EFFECT[] = "application/vnd.qtdesignstudio.asset.effect";
|
||||
inline constexpr char MIME_TYPE_ASSET_NODEGRAPH[] = "application/vnd.qtdesignstudio.asset.nodegraph";
|
||||
|
||||
// Menus
|
||||
inline constexpr char M_VIEW_WORKSPACES[] = "QmlDesigner.Menu.View.Workspaces";
|
||||
|
Reference in New Issue
Block a user