From e17d213580d29f8b7b186073bfec9aad1e662240 Mon Sep 17 00:00:00 2001 From: Rafal Andrusieczko Date: Thu, 12 Dec 2024 22:55:42 +0100 Subject: [PATCH] QmlDesigner: add nodes to the Node Graph Editor Nodes: - Material => PrincipledMaterial - BaseColor => part of PrincipledMaterial - Metalness => part of PrincipledMaterial - Roughness => part of PrincipledMaterial - CheckBox - Color - ComboBox - RealSpinBox - Texture Nodes can transfer data, which is set using input nodes Change-Id: I36a5ea95d6ee5b7c04dab47264db295c73a65874 Reviewed-by: Rafal Andrusieczko --- .../qmldesigner/nodegrapheditor/Main.qml | 83 +++++++++++- .../imports/Editor/ContextMenu.qml | 91 +++++++++++++ .../nodegrapheditor/imports/Editor/Graph.qml | 47 +++++++ .../imports/Editor/NewNodeGraphDialog.qml | 99 ++++++++++++++ .../nodegrapheditor/imports/Editor/Port.qml | 14 ++ .../nodegrapheditor/imports/Editor/qmldir | 5 + .../nodegrapheditor/imports/Nodes/Base.qml | 72 ++++++++++ .../imports/Nodes/BaseColor.qml | 68 ++++++++++ .../imports/Nodes/CheckBox.qml | 38 ++++++ .../nodegrapheditor/imports/Nodes/Color.qml | 50 +++++++ .../imports/Nodes/ColorEditorPopup.qml | 64 +++++++++ .../imports/Nodes/ComboBox.qml | 75 +++++++++++ .../imports/Nodes/Components.qml | 44 ++++++ .../imports/Nodes/Material.qml | 127 ++++++++++++++++++ .../imports/Nodes/Metalness.qml | 60 +++++++++ .../imports/Nodes/RealSpinBox.qml | 41 ++++++ .../imports/Nodes/Roughness.qml | 60 +++++++++ .../nodegrapheditor/imports/Nodes/Texture.qml | 33 +++++ .../nodegrapheditor/imports/Nodes/qmldir | 2 + src/plugins/qmldesigner/CMakeLists.txt | 3 + .../nodegrapheditorimageprovider.cpp | 14 +- .../nodegrapheditor/nodegrapheditorwidget.cpp | 15 +++ .../nodegrapheditor/nodegrapheditorwidget.h | 2 + 23 files changed, 1099 insertions(+), 8 deletions(-) create mode 100644 share/qtcreator/qmldesigner/nodegrapheditor/imports/Editor/ContextMenu.qml create mode 100644 share/qtcreator/qmldesigner/nodegrapheditor/imports/Editor/Graph.qml create mode 100644 share/qtcreator/qmldesigner/nodegrapheditor/imports/Editor/NewNodeGraphDialog.qml create mode 100644 share/qtcreator/qmldesigner/nodegrapheditor/imports/Editor/Port.qml create mode 100644 share/qtcreator/qmldesigner/nodegrapheditor/imports/Editor/qmldir create mode 100644 share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/Base.qml create mode 100644 share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/BaseColor.qml create mode 100644 share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/CheckBox.qml create mode 100644 share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/Color.qml create mode 100644 share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/ColorEditorPopup.qml create mode 100644 share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/ComboBox.qml create mode 100644 share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/Components.qml create mode 100644 share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/Material.qml create mode 100644 share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/Metalness.qml create mode 100644 share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/RealSpinBox.qml create mode 100644 share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/Roughness.qml create mode 100644 share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/Texture.qml create mode 100644 share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/qmldir diff --git a/share/qtcreator/qmldesigner/nodegrapheditor/Main.qml b/share/qtcreator/qmldesigner/nodegrapheditor/Main.qml index 0ebd9138e08..0f50b1eff64 100644 --- a/share/qtcreator/qmldesigner/nodegrapheditor/Main.qml +++ b/share/qtcreator/qmldesigner/nodegrapheditor/Main.qml @@ -2,18 +2,87 @@ // 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 import StudioTheme as StudioTheme + +import QuickQanava as Qan +import Editor as Editor + import NodeGraphEditorBackend -Rectangle { +Item { id: root - color: StudioTheme.Values.themePanelBackground + Editor.ContextMenu { + id: contextMenu - Rectangle { - anchors.centerIn: parent - color: "yellow" - height: 100 - width: 100 + graph: graphView.graph + } + + Column { + anchors.fill: parent + spacing: 0 + + Rectangle { + id: toolbar + + color: StudioTheme.Values.themeToolbarBackground + height: StudioTheme.Values.toolbarHeight + width: parent.width + + Row { + anchors.bottomMargin: StudioTheme.Values.toolbarVerticalMargin + anchors.fill: parent + anchors.leftMargin: StudioTheme.Values.toolbarHorizontalMargin + anchors.rightMargin: StudioTheme.Values.toolbarHorizontalMargin + anchors.topMargin: StudioTheme.Values.toolbarVerticalMargin + spacing: 6 + + HelperWidgets.AbstractButton { + buttonIcon: StudioTheme.Constants.add_medium + style: StudioTheme.Values.viewBarButtonStyle + tooltip: qsTr("Add a new graph node.") + + onClicked: () => { + newNodeGraphDialog.open(); + } + } + } + } + + Rectangle { + id: graphContent + + clip: true + color: StudioTheme.Values.themePanelBackground + height: parent.height - toolbar.height + width: parent.width + + Qan.GraphView { + id: graphView + + anchors.fill: parent + navigable: true + + graph: Editor.Graph { + } + + Keys.onPressed: event => { + if (event.key === Qt.Key_Delete) { + graph.removeSelection(); + } + } + onRightClicked: function (pos) { + contextMenu.popup(); + } + } + + Editor.NewNodeGraphDialog { + id: newNodeGraphDialog + + } + } } } diff --git a/share/qtcreator/qmldesigner/nodegrapheditor/imports/Editor/ContextMenu.qml b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Editor/ContextMenu.qml new file mode 100644 index 00000000000..934232fe2f2 --- /dev/null +++ b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Editor/ContextMenu.qml @@ -0,0 +1,91 @@ +// 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 Nodes as Nodes + +StudioControls.Menu { + id: contextMenu + + required property var graph + + closePolicy: Popup.CloseOnPressOutside | Popup.CloseOnEscape + + StudioControls.MenuItem { + text: qsTr("BaseColor") + + onTriggered: () => { + contextMenu.graph.insertNode(Nodes.Components.baseColor); + } + } + + StudioControls.MenuItem { + text: qsTr("Metalness") + + onTriggered: () => { + contextMenu.graph.insertNode(Nodes.Components.metalness); + } + } + + StudioControls.MenuItem { + text: qsTr("Roughness") + + onTriggered: () => { + contextMenu.graph.insertNode(Nodes.Components.roughness); + } + } + + StudioControls.MenuItem { + text: qsTr("CheckBox") + + onTriggered: () => { + contextMenu.graph.insertNode(Nodes.Components.checkBox); + } + } + + StudioControls.MenuItem { + text: qsTr("Color") + + onTriggered: () => { + contextMenu.graph.insertNode(Nodes.Components.color); + } + } + + StudioControls.MenuItem { + text: qsTr("ComboBox") + + onTriggered: () => { + contextMenu.graph.insertNode(Nodes.Components.comboBox); + } + } + + StudioControls.MenuItem { + text: qsTr("Material") + + onTriggered: () => { + contextMenu.graph.insertNode(Nodes.Components.material); + } + } + + StudioControls.MenuItem { + text: qsTr("RealSpinBox") + + onTriggered: () => { + contextMenu.graph.insertNode(Nodes.Components.realSpinBox); + } + } + + StudioControls.MenuItem { + text: qsTr("Texture") + + onTriggered: () => { + contextMenu.graph.insertNode(Nodes.Components.texture); + } + } +} diff --git a/share/qtcreator/qmldesigner/nodegrapheditor/imports/Editor/Graph.qml b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Editor/Graph.qml new file mode 100644 index 00000000000..9907c3bb9a9 --- /dev/null +++ b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Editor/Graph.qml @@ -0,0 +1,47 @@ +// 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 QuickQanava as Qan + +Qan.Graph { + id: root + + connectorCreateDefaultEdge: false + connectorEnabled: true + + portDelegate: Port { + } + + onConnectorRequestEdgeCreation: (srcNode, dstNode, srcPortItem, dstPortItem) => { + if (srcPortItem.dataType !== dstPortItem.dataType) { + return; + } + + const edge = root.insertEdge(srcNode, dstNode); + root.bindEdge(edge, srcPortItem, dstPortItem); + + if (dstPortItem.dataBinding) { + dstPortItem.dataBinding(srcNode.item.value); + } else { + // TODO: change old implementation + dstNode.item.value[dstPortItem.dataName] = Qt.binding(() => { + if (srcPortItem.dataName !== "") + return srcNode.item.value[srcPortItem.dataName]; + + return srcNode.item.value; + }); + } + } + onNodeRemoved: node => {} + onOnEdgeRemoved: edge => { + const srcNode = edge.getSource(); + const dstNode = edge.getDestination(); + const srcPortItem = edge.item.sourceItem; + const dstPortItem = edge.item.destinationItem; + + // TODO: add reset binding function + dstNode.item.value[dstPortItem.dataName] = dstNode.item.reset[dstPortItem.dataName]; + } +} diff --git a/share/qtcreator/qmldesigner/nodegrapheditor/imports/Editor/NewNodeGraphDialog.qml b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Editor/NewNodeGraphDialog.qml new file mode 100644 index 00000000000..a760a3836e9 --- /dev/null +++ b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Editor/NewNodeGraphDialog.qml @@ -0,0 +1,99 @@ +// Copyright (C) 2022 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 + +StudioControls.Dialog { + id: root + + anchors.centerIn: parent + closePolicy: Popup.CloseOnEscape + modal: true + title: qsTr("Create Node Graph") + + contentItem: Column { + spacing: 2 + + Row { + Text { + anchors.verticalCenter: parent.verticalCenter + color: StudioTheme.Values.themeTextColor + text: qsTr("Name: ") + } + + StudioControls.TextField { + id: tfName + + actionIndicator.visible: false + translationIndicator.visible: false + validator: nameValidator + + Keys.onEnterPressed: btnCreate.onClicked() + Keys.onEscapePressed: root.reject() + Keys.onReturnPressed: btnCreate.onClicked() + onTextChanged: () => {} + } + } + + Row { + Text { + anchors.verticalCenter: parent.verticalCenter + color: StudioTheme.Values.themeTextColor + text: qsTr("Type: ") + } + + StudioControls.ComboBox { + id: cbType + + actionIndicatorVisible: false + model: [ + { + value: "principled_material", + text: "Principled Material" + }, + ] + textRole: "text" + valueRole: "value" + + onActivated: () => {} + } + } + + Item { + height: 20 + width: 1 + } + + Row { + anchors.right: parent.right + + HelperWidgets.Button { + id: btnCreate + + text: qsTr("Create") + + onClicked: () => {} + } + + HelperWidgets.Button { + text: qsTr("Cancel") + + onClicked: root.reject() + } + } + } + + onOpened: () => {} + onRejected: () => {} + + HelperWidgets.RegExpValidator { + id: nameValidator + + regExp: /^(\w[^*/> {} + // } + property var pin: [] + property var pout: [] + } + readonly property string uuid: NodeGraphEditorBackend.widget.generateUUID() + + Layout.preferredHeight: 60 + Layout.preferredWidth: 100 + connectable: Qan.NodeItem.UnConnectable + height: Layout.preferredHeight + width: Layout.preferredWidth + + Component.onCompleted: { + internal.configurePorts(root.graph); + } + + Qan.RectNodeTemplate { + anchors.fill: parent + nodeItem: parent + + Item { + id: content + + anchors.fill: parent + } + } + + QtObject { + id: internal + + function configurePorts(graph) { + const initPort = (portItem, data) => { + if (data.binding) { + portItem.dataBinding = data.binding; + } + portItem.dataName = data.alias; + portItem.dataType = data.type; + }; + + root.portsMetaData.pin.forEach(data => { + const portItem = graph.insertPort(root.node, Qan.NodeItem.Left, Qan.PortItem.In, `${data.name} (${data.type})`, data.id); + initPort(portItem, data); + }); + + root.portsMetaData.pout.forEach(data => { + const portItem = graph.insertPort(root.node, Qan.NodeItem.Right, Qan.PortItem.Out, `${data.name}`, data.id); + initPort(portItem, data); + }); + } + } +} diff --git a/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/BaseColor.qml b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/BaseColor.qml new file mode 100644 index 00000000000..c9e261af529 --- /dev/null +++ b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/BaseColor.qml @@ -0,0 +1,68 @@ +// Copyright (C) 2024 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.Layouts + +import QtQuick3D as QtQuick3D + +Base { + id: root + + readonly property QtObject reset: QtObject { + property int channel + property color color + property QtQuick3D.Texture map + property bool singleChannelEnabled + } + readonly property QtObject value: QtObject { + property int channel + property color color + property QtQuick3D.Texture map + property bool singleChannelEnabled + } + + Layout.preferredHeight: 150 + Layout.preferredWidth: 150 + + portsMetaData: QtObject { + property var pin: [ + { + id: "basecolor_in_baseColor", + alias: "color", + name: "Color", + type: "QColor" + }, + { + id: "basecolor_in_baseColorMap", + alias: "map", + name: "Map", + type: "Texture" + }, + { + id: "basecolor_in_baseColorSingleChannelEnabled", + alias: "singleChannelEnabled", + name: "Single Channel Enabled", + type: "bool" + }, + { + id: "basecolor_in_baseColorChannel", + alias: "channel", + name: "Channel", + type: "QQuick3DMaterial::TextureChannelMapping" + }, + ] + property var pout: [ + { + id: "basecolor_out", + alias: "", + name: "OUT", + type: "nge::BaseColor" + }, + ] + } + + Component.onCompleted: { + node.label = "Base Color"; + } +} diff --git a/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/CheckBox.qml b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/CheckBox.qml new file mode 100644 index 00000000000..20422b30149 --- /dev/null +++ b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/CheckBox.qml @@ -0,0 +1,38 @@ +// Copyright (C) 2024 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.Layouts + +import StudioControls as StudioControls + +Base { + id: root + + readonly property QtObject value: QtObject { + property bool binary: checkBox.checked + } + + portsMetaData: QtObject { + property var pin: [] + property var pout: [ + { + id: "checkbox_out", + alias: "binary", + name: "OUT", + type: "bool" + }, + ] + } + + Component.onCompleted: { + node.label = "CheckBox (bool)"; + } + + StudioControls.CheckBox { + id: checkBox + + actionIndicatorVisible: false + anchors.centerIn: parent + } +} diff --git a/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/Color.qml b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/Color.qml new file mode 100644 index 00000000000..290e5c0e2e2 --- /dev/null +++ b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/Color.qml @@ -0,0 +1,50 @@ +// Copyright (C) 2024 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.Layouts + +Base { + id: root + + readonly property QtObject value: QtObject { + property color color + } + + portsMetaData: QtObject { + property var pin: [] + property var pout: [ + { + id: "checkbox_out", + alias: "color", + name: "OUT", + type: "QColor" + }, + ] + } + + Component.onCompleted: { + node.label = "Color (QColor)"; + } + + Rectangle { + anchors.centerIn: parent + color: root.value.color + height: 32 + width: 32 + + MouseArea { + anchors.fill: parent + + onClicked: colorPopup.open(root) + } + + ColorEditorPopup { + id: colorPopup + + currentColor: root.value.color + + onActivateColor: color => root.value.color = color + } + } +} diff --git a/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/ColorEditorPopup.qml b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/ColorEditorPopup.qml new file mode 100644 index 00000000000..e89d74dc30d --- /dev/null +++ b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/ColorEditorPopup.qml @@ -0,0 +1,64 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// 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 + +StudioControls.PopupDialog { + id: colorPopup + + required property color currentColor + property QtObject loaderItem: loader.item + property color originalColor + + signal activateColor(color: color) + + function open(showItem) { + loader.ensureActive(); + colorPopup.show(showItem); + + loader.updateOriginalColor(); + } + + width: 260 + + onClosing: loader.active = false + onOriginalColorChanged: loader.updateOriginalColor() + + Loader { + id: loader + + function ensureActive() { + if (!loader.active) + loader.active = true; + } + + function updateOriginalColor() { + if (loader.status === Loader.Ready) + loader.item.originalColor = colorPopup.originalColor; + } + + sourceComponent: StudioControls.ColorEditorPopup { + visible: colorPopup.visible + width: colorPopup.contentWidth + + onActivateColor: color => { + colorPopup.activateColor(color); + } + } + + onLoaded: { + loader.updateOriginalColor(); + colorPopup.titleBar = loader.item.titleBarContent; + } + + Binding { + property: "color" + target: loader.item + value: colorPopup.currentColor + when: loader.status === Loader.Ready + } + } +} diff --git a/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/ComboBox.qml b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/ComboBox.qml new file mode 100644 index 00000000000..e555ecc26e4 --- /dev/null +++ b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/ComboBox.qml @@ -0,0 +1,75 @@ +// Copyright (C) 2024 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.Layouts + +import HelperWidgets as HelperWidgets +import StudioControls as StudioControls + +Base { + id: root + + property QtObject value: QtObject { + property url text: `image://qmldesigner_nodegrapheditor/${comboBox.currentValue}` + } + + Layout.preferredWidth: 175 + + portsMetaData: QtObject { + property var pin: [] + property var pout: [ + { + id: "combobox_out", + alias: "text", + name: "OUT", + type: "QUrl" + }, + ] + } + + Component.onCompleted: { + node.label = "ComboBox"; + } + + HelperWidgets.FileResourcesModel { + id: fileModel + + filter: "*.png *.gif *.jpg *.bmp *.jpeg *.svg *.pbm *.pgm *.ppm *.xbm *.xpm *.hdr *.ktx *.webp" + modelNodeBackendProperty: modelNodeBackend + } + + StudioControls.ComboBox { + id: comboBox + + actionIndicatorVisible: false + anchors.centerIn: parent + model: fileModel.model + textRole: "fileName" + valueRole: "relativeFilePath" + + // valueRole: "absoluteFilePath" + + // model: [ + // { + // text: "#1 (ONE)", + // value: "one" + // }, + // { + // text: "#2 (TWO)", + // value: "two" + // }, + // { + // text: "#3 (THREE)", + // value: "three" + // } + // ] + // textRole: "text" + // valueRole: "value" + + // onActivated: () => { + // // root.value.text = comboBox.currentValue; + // root.value.text = `image://qmldesigner_nodegrapheditor/${comboBox.currentValue}`; + // } + } +} diff --git a/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/Components.qml b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/Components.qml new file mode 100644 index 00000000000..2472b3de914 --- /dev/null +++ b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/Components.qml @@ -0,0 +1,44 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +pragma Singleton + +import QtQuick + +QtObject { + readonly property Component baseColor: Component { + BaseColor { + } + } + readonly property Component checkBox: Component { + CheckBox { + } + } + readonly property Component color: Component { + Color { + } + } + readonly property Component comboBox: Component { + ComboBox { + } + } + readonly property Component material: Component { + Material { + } + } + readonly property Component metalness: Component { + Metalness { + } + } + readonly property Component realSpinBox: Component { + RealSpinBox { + } + } + readonly property Component roughness: Component { + Roughness { + } + } + readonly property Component texture: Component { + Texture { + } + } +} diff --git a/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/Material.qml b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/Material.qml new file mode 100644 index 00000000000..b33c4380ccb --- /dev/null +++ b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/Material.qml @@ -0,0 +1,127 @@ +// 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.Layouts +import QtQuick3D as QtQuick3D + +import QuickQanava as Qan + +import NodeGraphEditorBackend + +Base { + id: root + + readonly property QtQuick3D.PrincipledMaterial reset: QtQuick3D.PrincipledMaterial { + } + readonly property QtQuick3D.PrincipledMaterial value: QtQuick3D.PrincipledMaterial { + } + + Layout.preferredHeight: 150 + Layout.preferredWidth: 150 + + portsMetaData: QtObject { + property var pin: [ + { + id: "principledmaterial_in_basecolor", + alias: "", + name: "Base Color", + type: "nge::BaseColor", + binding: values => { + root.value.baseColor = Qt.binding(() => { + return values.color; + }); + root.value.baseColorChannel = Qt.binding(() => { + return values.channel; + }); + root.value.baseColorMap = Qt.binding(() => { + return values.map; + }); + root.value.baseColorSingleChannelEnabled = Qt.binding(() => { + return values.singleChannelEnabled; + }); + } + }, + { + id: "principledmaterial_in_metalness", + alias: "", + name: "Metalness", + type: "nge::Metalness", + binding: values => { + root.value.metalness = Qt.binding(() => { + return values.metalness; + }); + root.value.metalnessChannel = Qt.binding(() => { + return values.channel; + }); + root.value.metalnessMap = Qt.binding(() => { + return values.map; + }); + } + }, + { + id: "principledmaterial_in_roughness", + alias: "", + name: "Roughness", + type: "nge::Roughness", + binding: values => { + root.value.roughness = Qt.binding(() => { + return values.roughness; + }); + root.value.roughnessChannel = Qt.binding(() => { + return values.channel; + }); + root.value.roughnessMap = Qt.binding(() => { + return values.map; + }); + } + }, + ] + property var pout: [] + } + + Component.onCompleted: { + node.label = "Principled Material"; + } + + QtQuick3D.View3D { + id: view3D + + anchors.fill: parent + + camera: QtQuick3D.PerspectiveCamera { + clipFar: 1000 + clipNear: 1 + eulerRotation.x: -5.71 + y: 70 + z: 200 + } + environment: QtQuick3D.SceneEnvironment { + antialiasingMode: QtQuick3D.SceneEnvironment.MSAA + antialiasingQuality: QtQuick3D.SceneEnvironment.High + backgroundMode: QtQuick3D.SceneEnvironment.Transparent + clearColor: "#000000" + } + + QtQuick3D.Node { + QtQuick3D.DirectionalLight { + brightness: 1 + eulerRotation.x: -26 + eulerRotation.y: -50 + } + + QtQuick3D.Node { + y: 60 + + QtQuick3D.Model { + id: model + + eulerRotation.y: 45 + materials: root.value + scale: Qt.vector3d(1.7, 1.7, 1.7) + source: "#Sphere" + } + } + } + } +} diff --git a/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/Metalness.qml b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/Metalness.qml new file mode 100644 index 00000000000..a8262e9cf66 --- /dev/null +++ b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/Metalness.qml @@ -0,0 +1,60 @@ +// Copyright (C) 2024 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.Layouts + +import QtQuick3D as QtQuick3D + +Base { + id: root + + readonly property QtObject reset: QtObject { + property int channel + property QtQuick3D.Texture map + property real metalness + } + readonly property QtObject value: QtObject { + property int channel + property QtQuick3D.Texture map + property real metalness + } + + Layout.preferredHeight: 150 + Layout.preferredWidth: 150 + + portsMetaData: QtObject { + property var pin: [ + { + id: "metalness_in_metalness", + alias: "metalness", + name: "Metalness", + type: "real" + }, + { + id: "metalness_in_metalnessChannel", + alias: "channel", + name: "Channel", + type: "QQuick3DMaterial::TextureChannelMapping" + }, + { + id: "metalness_in_metalnessMap ", + alias: "map", + name: "Map", + type: "Texture" + }, + ] + property var pout: [ + { + id: "metalness_out", + alias: "", + name: "OUT", + type: "nge::Metalness" + }, + ] + } + + Component.onCompleted: { + node.label = "Metalness"; + } +} diff --git a/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/RealSpinBox.qml b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/RealSpinBox.qml new file mode 100644 index 00000000000..cca15c19312 --- /dev/null +++ b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/RealSpinBox.qml @@ -0,0 +1,41 @@ +// Copyright (C) 2024 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.Layouts + +import StudioControls as StudioControls + +Base { + id: root + + readonly property QtObject value: QtObject { + property real floating: realSpinBox.realValue + } + + Layout.preferredWidth: 175 + + portsMetaData: QtObject { + property var pin: [] + property var pout: [ + { + id: "realspinbox_out", + alias: "floating", + name: "OUT", + type: "real" + }, + ] + } + + Component.onCompleted: { + node.label = "RealSpinBox (real)"; + } + + StudioControls.RealSpinBox { + id: realSpinBox + + actionIndicatorVisible: false + anchors.centerIn: parent + decimals: 2 + } +} diff --git a/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/Roughness.qml b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/Roughness.qml new file mode 100644 index 00000000000..4d673dcb1f0 --- /dev/null +++ b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/Roughness.qml @@ -0,0 +1,60 @@ +// Copyright (C) 2024 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.Layouts + +import QtQuick3D as QtQuick3D + +Base { + id: root + + readonly property QtObject reset: QtObject { + property int channel + property QtQuick3D.Texture map + property real roughness + } + readonly property QtObject value: QtObject { + property int channel + property QtQuick3D.Texture map + property real roughness + } + + Layout.preferredHeight: 150 + Layout.preferredWidth: 150 + + portsMetaData: QtObject { + property var pin: [ + { + id: "roughness_in_roughness", + alias: "roughness", + name: "Roughness", + type: "real" + }, + { + id: "roughness_in_roughnessChannel", + alias: "channel", + name: "Channel", + type: "QQuick3DMaterial::TextureChannelMapping" + }, + { + id: "roughness_in_roughnessMap ", + alias: "map", + name: "Map", + type: "Texture" + }, + ] + property var pout: [ + { + id: "roughness_out", + alias: "", + name: "OUT", + type: "nge::Roughness" + }, + ] + } + + Component.onCompleted: { + node.label = "Roughness"; + } +} diff --git a/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/Texture.qml b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/Texture.qml new file mode 100644 index 00000000000..2473fe61121 --- /dev/null +++ b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/Texture.qml @@ -0,0 +1,33 @@ +// 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.Layouts +import QtQuick3D as QtQuick3D + +import QuickQanava as Qan + +import NodeGraphEditorBackend + +Base { + id: root + + readonly property QtQuick3D.Texture reset: QtQuick3D.Texture { + } + readonly property QtQuick3D.Texture value: QtQuick3D.Texture { + } + + Layout.preferredHeight: 150 + Layout.preferredWidth: 150 + + Component.onCompleted: { + node.label = "Texture"; + } + + Image { + anchors.centerIn: parent + height: 96 + source: root.value.source + width: 96 + } +} diff --git a/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/qmldir b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/qmldir new file mode 100644 index 00000000000..cf6434d0345 --- /dev/null +++ b/share/qtcreator/qmldesigner/nodegrapheditor/imports/Nodes/qmldir @@ -0,0 +1,2 @@ +module Nodes +singleton Components 1.0 Components.qml diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index c394762fd90..13a44c69e0e 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -369,6 +369,9 @@ extend_qtc_plugin(QmlDesigner extend_qtc_plugin(QmlDesigner SOURCES_PREFIX components/nodegrapheditor + DEPENDS + QuickQanava + QuickQanavaplugin SOURCES nodegrapheditorimageprovider.cpp nodegrapheditorimageprovider.h nodegrapheditormodel.cpp nodegrapheditormodel.h diff --git a/src/plugins/qmldesigner/components/nodegrapheditor/nodegrapheditorimageprovider.cpp b/src/plugins/qmldesigner/components/nodegrapheditor/nodegrapheditorimageprovider.cpp index e92f3b544f5..61e14ece65b 100644 --- a/src/plugins/qmldesigner/components/nodegrapheditor/nodegrapheditorimageprovider.cpp +++ b/src/plugins/qmldesigner/components/nodegrapheditor/nodegrapheditorimageprovider.cpp @@ -3,6 +3,9 @@ #include "nodegrapheditorimageprovider.h" +#include +#include + namespace QmlDesigner::Internal { NodeGraphEditorImageProvider::NodeGraphEditorImageProvider() @@ -10,8 +13,17 @@ NodeGraphEditorImageProvider::NodeGraphEditorImageProvider() { } -QImage NodeGraphEditorImageProvider::requestImage(const QString &/*id*/, QSize */*size*/, const QSize &requestedSize) +QImage NodeGraphEditorImageProvider::requestImage(const QString &id, QSize */*size*/, const QSize &requestedSize) { + Utils::FilePath path = DocumentManager::currentResourcePath().pathAppended(id); + + QImage img; + img.load(path.toString()); + + if (!img.isNull()) { + return img; + } + const QSize newSize = requestedSize.isEmpty() ? QSize(100, 100) : requestedSize; diff --git a/src/plugins/qmldesigner/components/nodegrapheditor/nodegrapheditorwidget.cpp b/src/plugins/qmldesigner/components/nodegrapheditor/nodegrapheditorwidget.cpp index e8346fcd26b..0f52097e0db 100644 --- a/src/plugins/qmldesigner/components/nodegrapheditor/nodegrapheditorwidget.cpp +++ b/src/plugins/qmldesigner/components/nodegrapheditor/nodegrapheditorwidget.cpp @@ -23,6 +23,8 @@ #include #include +#include + namespace QmlDesigner { static QString propertyEditorResourcesPath() @@ -34,6 +36,11 @@ static QString propertyEditorResourcesPath() return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toString(); } +static Utils::FilePath materialsPath() +{ + return DocumentManager::currentResourcePath().pathAppended("materials"); +} + NodeGraphEditorWidget::NodeGraphEditorWidget(NodeGraphEditorView *nodeGraphEditorView, NodeGraphEditorModel *nodeGraphEditorModel) : m_editorView(nodeGraphEditorView) @@ -56,12 +63,15 @@ NodeGraphEditorWidget::NodeGraphEditorWidget(NodeGraphEditorView *nodeGraphEdito auto map = registerPropertyMap("NodeGraphEditorBackend"); map->setProperties({{"nodeGraphEditorModel", QVariant::fromValue(nodeGraphEditorModel)}}); + map->setProperties({{"widget", QVariant::fromValue(this)}}); Theme::setupTheme(engine()); setWindowTitle(tr("Node Graph", "Title of Editor widget")); setMinimumSize(QSize(256, 256)); + QuickQanava::initialize(engine()); + // init the first load of the QML UI elements reloadQmlSource(); } @@ -75,6 +85,11 @@ QString NodeGraphEditorWidget::qmlSourcesPath() return Core::ICore::resourcePath("qmldesigner/nodegrapheditor").toString(); } +QString NodeGraphEditorWidget::generateUUID() const +{ + return QUuid::createUuid().toString(); +} + void NodeGraphEditorWidget::showEvent(QShowEvent *event) { StudioQuickWidget::showEvent(event); diff --git a/src/plugins/qmldesigner/components/nodegrapheditor/nodegrapheditorwidget.h b/src/plugins/qmldesigner/components/nodegrapheditor/nodegrapheditorwidget.h index c0f047238c0..a0e491dd4a6 100644 --- a/src/plugins/qmldesigner/components/nodegrapheditor/nodegrapheditorwidget.h +++ b/src/plugins/qmldesigner/components/nodegrapheditor/nodegrapheditorwidget.h @@ -32,6 +32,8 @@ public: static QString qmlSourcesPath(); + Q_INVOKABLE QString generateUUID() const; + protected: void showEvent(QShowEvent *) override; void focusOutEvent(QFocusEvent *focusEvent) override;