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 string suffix: model.fileName.substr(-4)
|
||||||
readonly property bool isFont: root.suffix === ".ttf" || root.suffix === ".otf"
|
readonly property bool isFont: root.suffix === ".ttf" || root.suffix === ".otf"
|
||||||
readonly property bool isEffect: root.suffix === ".qep"
|
readonly property bool isEffect: root.suffix === ".qep"
|
||||||
|
readonly property bool isNodeGraph: root.suffix === ".qng"
|
||||||
property bool currFileSelected: false
|
property bool currFileSelected: false
|
||||||
property int initialDepth: -1
|
property int initialDepth: -1
|
||||||
property bool __isDirectory: assetsModel.isDirectory(model.filePath)
|
property bool __isDirectory: assetsModel.isDirectory(model.filePath)
|
||||||
@@ -202,6 +203,10 @@ TreeViewDelegate {
|
|||||||
AssetsLibraryBackend.tooltipBackend.hideTooltip()
|
AssetsLibraryBackend.tooltipBackend.hideTooltip()
|
||||||
if (mouse.button === Qt.LeftButton && root.isEffect)
|
if (mouse.button === Qt.LeftButton && root.isEffect)
|
||||||
AssetsLibraryBackend.rootView.openEffectComposer(filePath)
|
AssetsLibraryBackend.rootView.openEffectComposer(filePath)
|
||||||
|
|
||||||
|
if (mouse.button === Qt.LeftButton && root.isNodeGraph){
|
||||||
|
AssetsLibraryBackend.rootView.openNodeGraphEditor(filePath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolTip {
|
ToolTip {
|
||||||
|
@@ -12,6 +12,7 @@ GridItem {
|
|||||||
readonly property bool isFont: root.suffix === ".ttf" || root.suffix === ".otf"
|
readonly property bool isFont: root.suffix === ".ttf" || root.suffix === ".otf"
|
||||||
readonly property bool isEffect: root.suffix === ".qep"
|
readonly property bool isEffect: root.suffix === ".qep"
|
||||||
readonly property bool isMaterial: root.suffix === ".mat"
|
readonly property bool isMaterial: root.suffix === ".mat"
|
||||||
|
readonly property bool isNodeGraph: root.suffix === ".qng"
|
||||||
|
|
||||||
icon.source: "image://qmldesigner_assets/" + model.filePath
|
icon.source: "image://qmldesigner_assets/" + model.filePath
|
||||||
|
|
||||||
@@ -48,10 +49,15 @@ GridItem {
|
|||||||
|
|
||||||
mouseArea.onDoubleClicked: (mouse) => {
|
mouseArea.onDoubleClicked: (mouse) => {
|
||||||
if (mouse.button === Qt.LeftButton) {
|
if (mouse.button === Qt.LeftButton) {
|
||||||
if (root.isEffect)
|
if (root.isEffect) {
|
||||||
root.rootView.openEffectComposer(filePath)
|
root.rootView.openEffectComposer(filePath)
|
||||||
else if (root.isMaterial)
|
}
|
||||||
|
else if (root.isMaterial) {
|
||||||
root.rootView.openMaterialEditor(filePath)
|
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 QuickQanava as Qan
|
||||||
import Editor as Editor
|
import Editor as Editor
|
||||||
|
import Nodes as Nodes
|
||||||
|
|
||||||
import NodeGraphEditorBackend
|
import NodeGraphEditorBackend
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
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 {
|
Editor.ContextMenu {
|
||||||
id: contextMenu
|
id: contextMenu
|
||||||
|
|
||||||
@@ -49,6 +232,47 @@ Item {
|
|||||||
newNodeGraphDialog.open();
|
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 QtQuick
|
||||||
|
|
||||||
import QuickQanava as Qan
|
import QuickQanava as Qan
|
||||||
|
import NodeGraphEditorBackend
|
||||||
|
|
||||||
Qan.Graph {
|
Qan.Graph {
|
||||||
id: root
|
id: root
|
||||||
@@ -34,7 +35,7 @@ Qan.Graph {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onNodeRemoved: node => {}
|
onNodeRemoved: node => {NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = true;}
|
||||||
onOnEdgeRemoved: edge => {
|
onOnEdgeRemoved: edge => {
|
||||||
const srcNode = edge.getSource();
|
const srcNode = edge.getSource();
|
||||||
const dstNode = edge.getDestination();
|
const dstNode = edge.getDestination();
|
||||||
@@ -43,5 +44,13 @@ Qan.Graph {
|
|||||||
|
|
||||||
// TODO: add reset binding function
|
// TODO: add reset binding function
|
||||||
dstNode.item.value[dstPortItem.dataName] = dstNode.item.reset[dstPortItem.dataName];
|
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 var dataBinding: null
|
||||||
property string dataName: ""
|
property string dataName: ""
|
||||||
property string dataType: ""
|
property string dataType: ""
|
||||||
|
property string dataId: ""
|
||||||
}
|
}
|
||||||
|
@@ -23,7 +23,8 @@ Qan.NodeItem {
|
|||||||
property var pin: []
|
property var pin: []
|
||||||
property var pout: []
|
property var pout: []
|
||||||
}
|
}
|
||||||
readonly property string uuid: NodeGraphEditorBackend.widget.generateUUID()
|
property string type
|
||||||
|
property string uuid: NodeGraphEditorBackend.widget.generateUUID()
|
||||||
|
|
||||||
Layout.preferredHeight: 60
|
Layout.preferredHeight: 60
|
||||||
Layout.preferredWidth: 100
|
Layout.preferredWidth: 100
|
||||||
@@ -34,6 +35,12 @@ Qan.NodeItem {
|
|||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
internal.configurePorts(root.graph);
|
internal.configurePorts(root.graph);
|
||||||
}
|
}
|
||||||
|
onXChanged: {
|
||||||
|
NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = true;
|
||||||
|
}
|
||||||
|
onYChanged: {
|
||||||
|
NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
Qan.RectNodeTemplate {
|
Qan.RectNodeTemplate {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -56,6 +63,7 @@ Qan.NodeItem {
|
|||||||
}
|
}
|
||||||
portItem.dataName = data.alias;
|
portItem.dataName = data.alias;
|
||||||
portItem.dataType = data.type;
|
portItem.dataType = data.type;
|
||||||
|
portItem.dataId = data.id;
|
||||||
};
|
};
|
||||||
|
|
||||||
root.portsMetaData.pin.forEach(data => {
|
root.portsMetaData.pin.forEach(data => {
|
||||||
|
@@ -24,6 +24,7 @@ Base {
|
|||||||
|
|
||||||
Layout.preferredHeight: 150
|
Layout.preferredHeight: 150
|
||||||
Layout.preferredWidth: 150
|
Layout.preferredWidth: 150
|
||||||
|
type: "BaseColor"
|
||||||
|
|
||||||
portsMetaData: QtObject {
|
portsMetaData: QtObject {
|
||||||
property var pin: [
|
property var pin: [
|
||||||
|
@@ -5,14 +5,17 @@ import QtQuick
|
|||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
|
||||||
import StudioControls as StudioControls
|
import StudioControls as StudioControls
|
||||||
|
import NodeGraphEditorBackend
|
||||||
|
|
||||||
Base {
|
Base {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property QtObject value: QtObject {
|
readonly property QtObject value: QtObject {
|
||||||
property bool binary: checkBox.checked
|
property bool binary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type: "CheckBox"
|
||||||
|
|
||||||
portsMetaData: QtObject {
|
portsMetaData: QtObject {
|
||||||
property var pin: []
|
property var pin: []
|
||||||
property var pout: [
|
property var pout: [
|
||||||
@@ -34,5 +37,11 @@ Base {
|
|||||||
|
|
||||||
actionIndicatorVisible: false
|
actionIndicatorVisible: false
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
checked: root.value.binary
|
||||||
|
|
||||||
|
onCheckedChanged: {
|
||||||
|
NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = true;
|
||||||
|
root.value.binary = checked;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,14 +3,21 @@
|
|||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import NodeGraphEditorBackend
|
||||||
|
|
||||||
Base {
|
Base {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property QtObject value: QtObject {
|
readonly property QtObject value: QtObject {
|
||||||
property color color
|
property color color
|
||||||
|
|
||||||
|
onColorChanged: {
|
||||||
|
NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type: "Color"
|
||||||
|
|
||||||
portsMetaData: QtObject {
|
portsMetaData: QtObject {
|
||||||
property var pin: []
|
property var pin: []
|
||||||
property var pout: [
|
property var pout: [
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
|
||||||
import HelperWidgets as HelperWidgets
|
import HelperWidgets as HelperWidgets
|
||||||
import StudioControls as StudioControls
|
import StudioControls as StudioControls
|
||||||
|
|
||||||
@@ -18,7 +17,6 @@ StudioControls.PopupDialog {
|
|||||||
function open(showItem) {
|
function open(showItem) {
|
||||||
loader.ensureActive();
|
loader.ensureActive();
|
||||||
colorPopup.show(showItem);
|
colorPopup.show(showItem);
|
||||||
|
|
||||||
loader.updateOriginalColor();
|
loader.updateOriginalColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,15 +6,21 @@ import QtQuick.Layouts
|
|||||||
|
|
||||||
import HelperWidgets as HelperWidgets
|
import HelperWidgets as HelperWidgets
|
||||||
import StudioControls as StudioControls
|
import StudioControls as StudioControls
|
||||||
|
import NodeGraphEditorBackend
|
||||||
|
|
||||||
Base {
|
Base {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property QtObject value: QtObject {
|
property QtObject value: QtObject {
|
||||||
property url text: `image://qmldesigner_nodegrapheditor/${comboBox.currentValue}`
|
property url text: `image://qmldesigner_nodegrapheditor/${comboBox.currentValue}`
|
||||||
|
|
||||||
|
onTextChanged: {
|
||||||
|
NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Layout.preferredWidth: 175
|
Layout.preferredWidth: 175
|
||||||
|
type: "ComboBox"
|
||||||
|
|
||||||
portsMetaData: QtObject {
|
portsMetaData: QtObject {
|
||||||
property var pin: []
|
property var pin: []
|
||||||
|
@@ -19,6 +19,7 @@ Base {
|
|||||||
|
|
||||||
Layout.preferredHeight: 150
|
Layout.preferredHeight: 150
|
||||||
Layout.preferredWidth: 150
|
Layout.preferredWidth: 150
|
||||||
|
type: "Material"
|
||||||
|
|
||||||
portsMetaData: QtObject {
|
portsMetaData: QtObject {
|
||||||
property var pin: [
|
property var pin: [
|
||||||
|
@@ -22,6 +22,7 @@ Base {
|
|||||||
|
|
||||||
Layout.preferredHeight: 150
|
Layout.preferredHeight: 150
|
||||||
Layout.preferredWidth: 150
|
Layout.preferredWidth: 150
|
||||||
|
type: "Metalness"
|
||||||
|
|
||||||
portsMetaData: QtObject {
|
portsMetaData: QtObject {
|
||||||
property var pin: [
|
property var pin: [
|
||||||
|
@@ -5,15 +5,17 @@ import QtQuick
|
|||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
|
||||||
import StudioControls as StudioControls
|
import StudioControls as StudioControls
|
||||||
|
import NodeGraphEditorBackend
|
||||||
|
|
||||||
Base {
|
Base {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property QtObject value: QtObject {
|
readonly property QtObject value: QtObject {
|
||||||
property real floating: realSpinBox.realValue
|
property real floating
|
||||||
}
|
}
|
||||||
|
|
||||||
Layout.preferredWidth: 175
|
Layout.preferredWidth: 175
|
||||||
|
type: "RealSpinBox"
|
||||||
|
|
||||||
portsMetaData: QtObject {
|
portsMetaData: QtObject {
|
||||||
property var pin: []
|
property var pin: []
|
||||||
@@ -37,5 +39,11 @@ Base {
|
|||||||
actionIndicatorVisible: false
|
actionIndicatorVisible: false
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
decimals: 2
|
decimals: 2
|
||||||
|
realValue: root.value.floating
|
||||||
|
|
||||||
|
onRealValueChanged: {
|
||||||
|
NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = true;
|
||||||
|
root.value.floating = realValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,6 +22,7 @@ Base {
|
|||||||
|
|
||||||
Layout.preferredHeight: 150
|
Layout.preferredHeight: 150
|
||||||
Layout.preferredWidth: 150
|
Layout.preferredWidth: 150
|
||||||
|
type: "Roughness"
|
||||||
|
|
||||||
portsMetaData: QtObject {
|
portsMetaData: QtObject {
|
||||||
property var pin: [
|
property var pin: [
|
||||||
|
@@ -7,6 +7,7 @@ import QtQuick3D as QtQuick3D
|
|||||||
|
|
||||||
import QuickQanava as Qan
|
import QuickQanava as Qan
|
||||||
|
|
||||||
|
import NodeGraphEditorBackend
|
||||||
import NodeGraphEditorBackend
|
import NodeGraphEditorBackend
|
||||||
|
|
||||||
Base {
|
Base {
|
||||||
@@ -19,10 +20,14 @@ Base {
|
|||||||
|
|
||||||
Layout.preferredHeight: 150
|
Layout.preferredHeight: 150
|
||||||
Layout.preferredWidth: 150
|
Layout.preferredWidth: 150
|
||||||
|
type: "Texture"
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
node.label = "Texture";
|
node.label = "Texture";
|
||||||
}
|
}
|
||||||
|
onValueChanged: {
|
||||||
|
NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
anchors.centerIn: parent
|
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_feature_info("Meta info tracing" ${ENABLE_METAINFO_TRACING} "")
|
||||||
|
|
||||||
add_subdirectory(libs)
|
add_subdirectory(libs)
|
||||||
|
|
||||||
add_qtc_plugin(QmlDesigner
|
add_qtc_plugin(QmlDesigner
|
||||||
PLUGIN_RECOMMENDS QmlPreview
|
PLUGIN_RECOMMENDS QmlPreview
|
||||||
CONDITION TARGET QmlDesignerCore AND TARGET Qt::QuickWidgets AND TARGET Qt::Svg
|
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";
|
type = "sound";
|
||||||
else if (asset.isVideo())
|
else if (asset.isVideo())
|
||||||
type = "video";
|
type = "video";
|
||||||
else if (asset.isEffect())
|
else if (asset.isEffect() || asset.isNodeGraph())
|
||||||
type = QmlDesigner::ModelNodeOperations::getEffectIcon(id);
|
type = QmlDesigner::ModelNodeOperations::getEffectIcon(id);
|
||||||
|
|
||||||
QString pathTemplate = QString(":/AssetsLibrary/images/asset_%1%2.png").arg(type);
|
QString pathTemplate = QString(":/AssetsLibrary/images/asset_%1%2.png").arg(type);
|
||||||
|
@@ -612,6 +612,11 @@ QSet<QString> AssetsLibraryWidget::supportedAssetSuffixes(bool complex)
|
|||||||
return suffixes;
|
return suffixes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AssetsLibraryWidget::openNodeGraphEditor(const QString &filePath)
|
||||||
|
{
|
||||||
|
ModelNodeOperations::openNodeGraphEditor(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
void AssetsLibraryWidget::openEffectComposer(const QString &filePath)
|
void AssetsLibraryWidget::openEffectComposer(const QString &filePath)
|
||||||
{
|
{
|
||||||
ModelNodeOperations::openEffectComposer(filePath);
|
ModelNodeOperations::openEffectComposer(filePath);
|
||||||
@@ -695,6 +700,9 @@ QPair<QString, QByteArray> AssetsLibraryWidget::getAssetTypeAndData(const QStrin
|
|||||||
} else if (asset.isEffect()) {
|
} else if (asset.isEffect()) {
|
||||||
// Data: Effect Composer format (suffix)
|
// Data: Effect Composer format (suffix)
|
||||||
return {Constants::MIME_TYPE_ASSET_EFFECT, asset.suffix().toUtf8()};
|
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 {};
|
return {};
|
||||||
|
@@ -85,6 +85,7 @@ public:
|
|||||||
const QString &targetDirPath = {});
|
const QString &targetDirPath = {});
|
||||||
Q_INVOKABLE QSet<QString> supportedAssetSuffixes(bool complex);
|
Q_INVOKABLE QSet<QString> supportedAssetSuffixes(bool complex);
|
||||||
Q_INVOKABLE void openEffectComposer(const QString &filePath);
|
Q_INVOKABLE void openEffectComposer(const QString &filePath);
|
||||||
|
Q_INVOKABLE void openNodeGraphEditor(const QString &filePath);
|
||||||
Q_INVOKABLE int qtVersion() const;
|
Q_INVOKABLE int qtVersion() const;
|
||||||
Q_INVOKABLE void invalidateThumbnail(const QString &id);
|
Q_INVOKABLE void invalidateThumbnail(const QString &id);
|
||||||
Q_INVOKABLE QSize imageSize(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)
|
void openOldEffectMaker(const QString &filePath)
|
||||||
{
|
{
|
||||||
const ProjectExplorer::Target *target = ProjectExplorer::ProjectTree::currentTarget();
|
const ProjectExplorer::Target *target = ProjectExplorer::ProjectTree::currentTarget();
|
||||||
@@ -2282,6 +2289,7 @@ QString getEffectsDefaultDirectory(const QString &defaultDir)
|
|||||||
return getAssetDefaultDirectory("effects", defaultDir);
|
return getAssetDefaultDirectory("effects", defaultDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QString getEffectIcon(const QString &effectPath)
|
QString getEffectIcon(const QString &effectPath)
|
||||||
{
|
{
|
||||||
Utils::FilePath effectFile = QmlDesignerPlugin::instance()->documentManager()
|
Utils::FilePath effectFile = QmlDesignerPlugin::instance()->documentManager()
|
||||||
|
@@ -212,6 +212,7 @@ void editInEffectComposer(const SelectionContext &selectionContext);
|
|||||||
QMLDESIGNERCOMPONENTS_EXPORT Utils::FilePath getEffectsImportDirectory();
|
QMLDESIGNERCOMPONENTS_EXPORT Utils::FilePath getEffectsImportDirectory();
|
||||||
QMLDESIGNERCOMPONENTS_EXPORT QString getEffectsDefaultDirectory(const QString &defaultDir = {});
|
QMLDESIGNERCOMPONENTS_EXPORT QString getEffectsDefaultDirectory(const QString &defaultDir = {});
|
||||||
void openEffectComposer(const QString &filePath);
|
void openEffectComposer(const QString &filePath);
|
||||||
|
void openNodeGraphEditor(const QString &filePath);
|
||||||
void openOldEffectMaker(const QString &filePath);
|
void openOldEffectMaker(const QString &filePath);
|
||||||
QString getEffectIcon(const QString &effectPath);
|
QString getEffectIcon(const QString &effectPath);
|
||||||
bool useLayerEffect();
|
bool useLayerEffect();
|
||||||
|
@@ -5,6 +5,9 @@
|
|||||||
|
|
||||||
#include "nodegrapheditorview.h"
|
#include "nodegrapheditorview.h"
|
||||||
|
|
||||||
|
#include "nodegrapheditorview.h"
|
||||||
|
#include <qmldesignerplugin.h>
|
||||||
|
#include <QFileInfo>
|
||||||
namespace QmlDesigner {
|
namespace QmlDesigner {
|
||||||
|
|
||||||
NodeGraphEditorModel::NodeGraphEditorModel(NodeGraphEditorView *nodeGraphEditorView)
|
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
|
} // namespace QmlDesigner
|
||||||
|
@@ -16,9 +16,34 @@ class NodeGraphEditorModel : public QStandardItemModel
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
NodeGraphEditorModel(NodeGraphEditorView *nodeGraphEditorView);
|
NodeGraphEditorModel(NodeGraphEditorView *nodeGraphEditorView);
|
||||||
|
Q_INVOKABLE void openFile(QString filePath);
|
||||||
|
Q_INVOKABLE void openFileName(QString filePath);
|
||||||
|
Q_INVOKABLE void saveFile(QString fileName);
|
||||||
private:
|
private:
|
||||||
QPointer<NodeGraphEditorView> m_editorView;
|
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
|
} // 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
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
#include "nodegrapheditorview.h"
|
#include "nodegrapheditorview.h"
|
||||||
|
|
||||||
#include "nodegrapheditormodel.h"
|
#include "nodegrapheditormodel.h"
|
||||||
#include "nodegrapheditorwidget.h"
|
#include "nodegrapheditorwidget.h"
|
||||||
|
|
||||||
@@ -31,4 +30,16 @@ WidgetInfo NodeGraphEditorView::widgetInfo()
|
|||||||
tr("Node Graph"));
|
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
|
} // namespace QmlDesigner
|
||||||
|
@@ -21,8 +21,11 @@ public:
|
|||||||
WidgetInfo widgetInfo() override;
|
WidgetInfo widgetInfo() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void customNotification(const AbstractView *view, const QString &identifier,
|
||||||
|
const QList<QmlDesigner::ModelNode> &nodeList, const QList<QVariant> &data) override;
|
||||||
QPointer<NodeGraphEditorModel> m_editorModel;
|
QPointer<NodeGraphEditorModel> m_editorModel;
|
||||||
QPointer<NodeGraphEditorWidget> m_editorWidget;
|
QPointer<NodeGraphEditorWidget> m_editorWidget;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QmlDesigner
|
} // namespace QmlDesigner
|
||||||
|
@@ -41,9 +41,28 @@ static Utils::FilePath materialsPath()
|
|||||||
return DocumentManager::currentResourcePath().pathAppended("materials");
|
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,
|
NodeGraphEditorWidget::NodeGraphEditorWidget(NodeGraphEditorView *nodeGraphEditorView,
|
||||||
NodeGraphEditorModel *nodeGraphEditorModel)
|
NodeGraphEditorModel *nodeGraphEditorModel)
|
||||||
: m_editorView(nodeGraphEditorView)
|
: m_editorView(nodeGraphEditorView)
|
||||||
|
, m_model(nodeGraphEditorModel)
|
||||||
, m_imageProvider(nullptr)
|
, m_imageProvider(nullptr)
|
||||||
, m_qmlSourceUpdateShortcut(nullptr)
|
, m_qmlSourceUpdateShortcut(nullptr)
|
||||||
{
|
{
|
||||||
@@ -64,6 +83,7 @@ NodeGraphEditorWidget::NodeGraphEditorWidget(NodeGraphEditorView *nodeGraphEdito
|
|||||||
auto map = registerPropertyMap("NodeGraphEditorBackend");
|
auto map = registerPropertyMap("NodeGraphEditorBackend");
|
||||||
map->setProperties({{"nodeGraphEditorModel", QVariant::fromValue(nodeGraphEditorModel)}});
|
map->setProperties({{"nodeGraphEditorModel", QVariant::fromValue(nodeGraphEditorModel)}});
|
||||||
map->setProperties({{"widget", QVariant::fromValue(this)}});
|
map->setProperties({{"widget", QVariant::fromValue(this)}});
|
||||||
|
connect(m_qmlSourceUpdateShortcut, &QShortcut::activated, this, &NodeGraphEditorWidget::reloadQmlSource);
|
||||||
|
|
||||||
Theme::setupTheme(engine());
|
Theme::setupTheme(engine());
|
||||||
|
|
||||||
|
@@ -30,9 +30,11 @@ public:
|
|||||||
NodeGraphEditorWidget(NodeGraphEditorView *nodeGraphEditorView, NodeGraphEditorModel *nodeGraphEditorModel);
|
NodeGraphEditorWidget(NodeGraphEditorView *nodeGraphEditorView, NodeGraphEditorModel *nodeGraphEditorModel);
|
||||||
~NodeGraphEditorWidget() override = default;
|
~NodeGraphEditorWidget() override = default;
|
||||||
|
|
||||||
static QString qmlSourcesPath();
|
|
||||||
|
|
||||||
Q_INVOKABLE QString generateUUID() const;
|
Q_INVOKABLE QString generateUUID() const;
|
||||||
|
static QString qmlSourcesPath();
|
||||||
|
Q_INVOKABLE void doOpenNodeGraph();
|
||||||
|
void openNodeGraph(const QString &path);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void showEvent(QShowEvent *) override;
|
void showEvent(QShowEvent *) override;
|
||||||
@@ -41,12 +43,13 @@ protected:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void reloadQmlSource();
|
void reloadQmlSource();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QPointer<NodeGraphEditorView> m_editorView;
|
QPointer<NodeGraphEditorView> m_editorView;
|
||||||
|
QPointer<NodeGraphEditorModel> m_model;
|
||||||
Internal::NodeGraphEditorImageProvider *m_imageProvider;
|
Internal::NodeGraphEditorImageProvider *m_imageProvider;
|
||||||
QShortcut *m_qmlSourceUpdateShortcut;
|
QShortcut *m_qmlSourceUpdateShortcut;
|
||||||
QElapsedTimer m_usageTimer;
|
QElapsedTimer m_usageTimer;
|
||||||
|
QString m_filePath;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QmlDesigner
|
} // namespace QmlDesigner
|
||||||
|
@@ -85,6 +85,13 @@ const QStringList &Asset::supportedEffectComposerSuffixes()
|
|||||||
return retList;
|
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()
|
const QStringList &Asset::supportedMaterialSuffixes()
|
||||||
{
|
{
|
||||||
static QStringList retList {"*.mat"};
|
static QStringList retList {"*.mat"};
|
||||||
@@ -112,6 +119,7 @@ const QSet<QString> &Asset::supportedSuffixes()
|
|||||||
insertSuffixes(supportedVideoSuffixes());
|
insertSuffixes(supportedVideoSuffixes());
|
||||||
insertSuffixes(supportedTexture3DSuffixes());
|
insertSuffixes(supportedTexture3DSuffixes());
|
||||||
insertSuffixes(supportedEffectComposerSuffixes());
|
insertSuffixes(supportedEffectComposerSuffixes());
|
||||||
|
insertSuffixes(supportedNodeGraphSuffixes());
|
||||||
insertSuffixes(supportedMaterialSuffixes());
|
insertSuffixes(supportedMaterialSuffixes());
|
||||||
insertSuffixes(supportedImported3dSuffixes());
|
insertSuffixes(supportedImported3dSuffixes());
|
||||||
}
|
}
|
||||||
@@ -196,6 +204,10 @@ bool Asset::isEffect() const
|
|||||||
return m_type == Asset::Type::Effect;
|
return m_type == Asset::Type::Effect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Asset::isNodeGraph() const
|
||||||
|
{
|
||||||
|
return m_type == Asset::Type::NodeGraph;
|
||||||
|
}
|
||||||
bool Asset::isMaterial() const
|
bool Asset::isMaterial() const
|
||||||
{
|
{
|
||||||
return m_type == Asset::Type::Material;
|
return m_type == Asset::Type::Material;
|
||||||
@@ -252,6 +264,8 @@ void Asset::resolveType()
|
|||||||
m_type = Asset::Type::Texture3D;
|
m_type = Asset::Type::Texture3D;
|
||||||
else if (supportedEffectComposerSuffixes().contains(m_suffix))
|
else if (supportedEffectComposerSuffixes().contains(m_suffix))
|
||||||
m_type = Asset::Type::Effect;
|
m_type = Asset::Type::Effect;
|
||||||
|
else if (supportedNodeGraphSuffixes().contains(m_suffix))
|
||||||
|
m_type = Asset::Type::NodeGraph;
|
||||||
else if (supportedMaterialSuffixes().contains(m_suffix))
|
else if (supportedMaterialSuffixes().contains(m_suffix))
|
||||||
m_type = Asset::Type::Material;
|
m_type = Asset::Type::Material;
|
||||||
else if (supportedImported3dSuffixes().contains(m_suffix))
|
else if (supportedImported3dSuffixes().contains(m_suffix))
|
||||||
|
@@ -26,6 +26,7 @@ public:
|
|||||||
Video,
|
Video,
|
||||||
Texture3D,
|
Texture3D,
|
||||||
Effect,
|
Effect,
|
||||||
|
NodeGraph,
|
||||||
Material,
|
Material,
|
||||||
Imported3D
|
Imported3D
|
||||||
};
|
};
|
||||||
@@ -41,6 +42,7 @@ public:
|
|||||||
static const QStringList &supportedVideoSuffixes();
|
static const QStringList &supportedVideoSuffixes();
|
||||||
static const QStringList &supportedTexture3DSuffixes();
|
static const QStringList &supportedTexture3DSuffixes();
|
||||||
static const QStringList &supportedEffectComposerSuffixes();
|
static const QStringList &supportedEffectComposerSuffixes();
|
||||||
|
static const QStringList &supportedNodeGraphSuffixes();
|
||||||
static const QStringList &supportedMaterialSuffixes();
|
static const QStringList &supportedMaterialSuffixes();
|
||||||
static const QStringList &supportedImported3dSuffixes();
|
static const QStringList &supportedImported3dSuffixes();
|
||||||
static const QSet<QString> &supportedSuffixes();
|
static const QSet<QString> &supportedSuffixes();
|
||||||
@@ -64,6 +66,7 @@ public:
|
|||||||
bool isHdrFile() const;
|
bool isHdrFile() const;
|
||||||
bool isKtxFile() const;
|
bool isKtxFile() const;
|
||||||
bool isEffect() const;
|
bool isEffect() const;
|
||||||
|
bool isNodeGraph() const;
|
||||||
bool isMaterial() const;
|
bool isMaterial() const;
|
||||||
bool isImported3D() const;
|
bool isImported3D() const;
|
||||||
bool isSupported() const;
|
bool isSupported() const;
|
||||||
|
@@ -88,6 +88,7 @@ inline constexpr char MIME_TYPE_ASSET_TEXTURE3D[]
|
|||||||
= "application/vnd.qtdesignstudio.asset.texture3d";
|
= "application/vnd.qtdesignstudio.asset.texture3d";
|
||||||
inline constexpr char MIME_TYPE_MODELNODE_LIST[] = "application/vnd.qtdesignstudio.modelnode.list";
|
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_EFFECT[] = "application/vnd.qtdesignstudio.asset.effect";
|
||||||
|
inline constexpr char MIME_TYPE_ASSET_NODEGRAPH[] = "application/vnd.qtdesignstudio.asset.nodegraph";
|
||||||
|
|
||||||
// Menus
|
// Menus
|
||||||
inline constexpr char M_VIEW_WORKSPACES[] = "QmlDesigner.Menu.View.Workspaces";
|
inline constexpr char M_VIEW_WORKSPACES[] = "QmlDesigner.Menu.View.Workspaces";
|
||||||
|
Reference in New Issue
Block a user