diff --git a/src/gui-qml/qml/DissolveMain.qml b/src/gui-qml/qml/DissolveMain.qml index f3ec88c35b..29f213413c 100644 --- a/src/gui-qml/qml/DissolveMain.qml +++ b/src/gui-qml/qml/DissolveMain.qml @@ -11,6 +11,7 @@ import "../../Dissolve" ApplicationWindow { id: dissolveWindow + property vector3d scale: Qt.vector3d(Math.min(graphView.width / 2.5, graphView.height / 2.5), Math.min(graphView.width / 2.5, graphView.height / 2.5), 200) height: 743 @@ -18,8 +19,51 @@ ApplicationWindow { visible: true width: 819 + menuBar: MenuBar { + id: mainMenu + + Menu { + title: "&File" + + MenuItem { + text: "&New" + } + Action { + shortcut: "Ctrl+O" + text: "&Open..." + + onTriggered: openDialog.open() + } + MenuItem { + text: "Open R&ecent" + } + MenuItem { + text: "Save" + } + MenuItem { + text: "Save As..." + } + MenuItem { + text: "Load Restart Point..." + } + MenuItem { + text: "Save Restart Point..." + } + MenuItem { + text: "Close" + } + Action { + shortcut: "Ctrl+Q" + text: "&Quit" + + onTriggered: Qt.quit() + } + } + } + TabBar { id: tabBar + width: parent.width // DEFAULT TABS @@ -48,20 +92,24 @@ ApplicationWindow { Item { id: messagesTab + Text { text: "Messages" } } Item { id: forcefieldTab + Text { text: "Forcefields" } } Item { id: examplePlotTab + Node { id: standAloneScene + DirectionalLight { ambientColor: Qt.rgba(0.5, 0.5, 0.5, 1.0) brightness: 1.0 @@ -69,6 +117,7 @@ ApplicationWindow { } ScatterModel { id: plotLine + color: "red" scale: dissolveWindow.scale thickness: 0.1 @@ -90,6 +139,7 @@ ApplicationWindow { axis: Axis { id: xAxis + direction: true maximum: 2.0 minimum: -2.0 @@ -102,6 +152,7 @@ ApplicationWindow { axis: Axis { id: yAxis + direction: false maximum: 2.0 minimum: -2.0 @@ -111,9 +162,16 @@ ApplicationWindow { } View3D { id: graphView + anchors.fill: parent importScene: standAloneScene + camera: OrthographicCamera { + id: cameraOrthographicLeft + + z: 600 + } + MouseArea { anchors.fill: parent @@ -122,32 +180,31 @@ ApplicationWindow { yAxis.nudge(-0.01 * event.pixelDelta.y); } } - - camera: OrthographicCamera { - id: cameraOrthographicLeft - z: 600 - } } } Item { id: exampleGraphTab + ModuleGraphModel { id: graphModel + world: dissolve } Connections { - target: dissolve.configurationsModel - function onModelReset() { graphModel.handleReset(); } + + target: dissolve.configurationsModel } GeneratorDelegate { id: exampleDelegate + rootModel: graphModel } Pane { id: toolBar + RowLayout { anchors.left: parent.left anchors.right: parent.right @@ -155,6 +212,7 @@ ApplicationWindow { FileDialog { id: openDialog + onAccepted: { dissolve.file = selectedFile; } @@ -172,6 +230,7 @@ ApplicationWindow { } SpinBox { id: nodeValue + from: 0 } Button { @@ -197,6 +256,7 @@ ApplicationWindow { } SpinBox { id: conSrc + from: 0 to: graphModel.nodeCount - 1 } @@ -205,11 +265,13 @@ ApplicationWindow { } SpinBox { id: conDest + from: 0 to: graphModel.nodeCount - 1 } Button { id: connectButton + text: "Connect" onClicked: { @@ -223,6 +285,7 @@ ApplicationWindow { } Button { id: disconnectButton + text: "Disconnect" onClicked: { @@ -238,6 +301,7 @@ ApplicationWindow { } GraphView { id: graph + anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right @@ -249,45 +313,4 @@ ApplicationWindow { } } } - - menuBar: MenuBar { - id: mainMenu - Menu { - title: "&File" - - MenuItem { - text: "&New" - } - Action { - shortcut: "Ctrl+O" - text: "&Open..." - - onTriggered: openDialog.open() - } - MenuItem { - text: "Open R&ecent" - } - MenuItem { - text: "Save" - } - MenuItem { - text: "Save As..." - } - MenuItem { - text: "Load Restart Point..." - } - MenuItem { - text: "Save Restart Point..." - } - MenuItem { - text: "Close" - } - Action { - shortcut: "Ctrl+Q" - text: "&Quit" - - onTriggered: Qt.quit() - } - } - } } diff --git a/src/gui/models/nodeGraph/exampleGraphModel.cpp b/src/gui/models/nodeGraph/exampleGraphModel.cpp index 63077c8a6f..0ff086aec4 100644 --- a/src/gui/models/nodeGraph/exampleGraphModel.cpp +++ b/src/gui/models/nodeGraph/exampleGraphModel.cpp @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (c) 2024 Team Dissolve and contributors +// Copyright (c) 2025 Team Dissolve and contributors #include "exampleGraphModel.h" #include "nodeWrapper.h" @@ -7,7 +7,7 @@ #include // The value of the node -template <> QVariant nodeGetValue(const nodeValue value) +QVariant nodeGetValue(const nodeValue &value) { return std::visit( [](auto arg) -> QVariant @@ -19,7 +19,7 @@ template <> QVariant nodeGetValue(const nodeValue value) else { if (arg) - return nodeGetValue(*arg); + return nodeGetValue(*arg); return {}; } }, @@ -27,7 +27,7 @@ template <> QVariant nodeGetValue(const nodeValue value) }; // The name of the type (for delegate dispatch) -template <> std::string nodeTypeName(const nodeValue &value) +std::string nodeTypeName(const nodeValue &value) { return std::visit( [](auto arg) -> std::string @@ -45,7 +45,7 @@ template <> std::string nodeTypeName(const nodeValue &value) } // The path to the icon for the node -template <> std::string nodeTypeIcon(const nodeValue &value) +std::string nodeTypeIcon(const nodeValue &value) { return std::visit( [](auto arg) -> std::string @@ -63,34 +63,34 @@ template <> std::string nodeTypeIcon(const nodeValue &value) } // The title of the node -template <> std::string nodeName(const nodeValue &value) { return value.name; } +std::string nodeName(const nodeValue &value) { return value.name; } // Change the title of the node -template <> void setNodeName(nodeValue &value, const std::string name) { value.name = name; } +void setNodeName(nodeValue &value, const std::string name) { value.name = name; } // Link an indexed position on the source to an indexed position on the destination -template <> bool nodeConnect(nodeValue &source, int sourceIndex, nodeValue &destionation, int destinationIndex) +bool nodeConnect(nodeValue &source, int sourceIndex, nodeValue &destionation, int destinationIndex) { destionation.value = &source; return true; } // Confirm that a connection is possible (e.g. types match and index isn't already connected) -template <> -bool nodeConnectable(const nodeValue &source, int sourceIndex, const nodeValue &destination, int destinationIndex) + +bool nodeConnectable(const nodeValue &source, int sourceIndex, const nodeValue &destination, int destinationIndex) { return std::holds_alternative(source.value) && std::holds_alternative(destination.value); } // Unlink an indexed position on the source to an indexed position on the destination -template <> bool nodeDisconnect(nodeValue &source, int sourceIndex, nodeValue &destination, int destinationIndex) +bool nodeDisconnect(nodeValue &source, int sourceIndex, nodeValue &destination, int destinationIndex) { destination.value = nullptr; return true; } // Append the roles for the type onto the QHash -template <> QHash &nodeRoleNames(QHash &roles) +QHash &nodeRoleNames(Phantom proxy, QHash &roles) { const auto base = Qt::UserRole + GraphNodeModelBase::ownedRoles; roles[base] = "value"; @@ -98,7 +98,7 @@ template <> QHash &nodeRoleNames(QHash QVariant nodeData(const nodeValue &item, int role) +QVariant nodeData(const nodeValue &item, int role) { switch (role) { @@ -110,10 +110,10 @@ template <> QVariant nodeData(const nodeValue &item, int role) } // Set a specific piece of information from a node by index -template <> bool nodeSetData(nodeValue &item, const QVariant &value, int role) { return false; } +bool nodeSetData(nodeValue &item, const QVariant &value, int role) { return false; } // Delete the node -template <> bool nodeDelete(nodeValue &value, GraphNodeContext::type &context) { return true; } +bool nodeDelete(nodeValue &value, Phantom &context) { return true; } // Create node from variant nodeValue::nodeValue(QVariant var) diff --git a/src/gui/models/nodeGraph/exampleGraphModel.h b/src/gui/models/nodeGraph/exampleGraphModel.h index 035336b879..17df4de7f4 100644 --- a/src/gui/models/nodeGraph/exampleGraphModel.h +++ b/src/gui/models/nodeGraph/exampleGraphModel.h @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (c) 2024 Team Dissolve and contributors +// Copyright (c) 2025 Team Dissolve and contributors #pragma once @@ -29,5 +29,18 @@ class nodeValue std::variant value; }; +std::string nodeName(const nodeValue &value); +std::string nodeTypeName(const nodeValue &value); +std::string nodeTypeIcon(const nodeValue &value); +void setNodeName(nodeValue &value, std::string); +QVariant nodeGetValue(const nodeValue &value); +bool nodeConnect(nodeValue &source, int sourceIndex, nodeValue &destionation, int destinationIndex); +bool nodeConnectable(const nodeValue &source, int sourceIndex, const nodeValue &destination, int destinationIndex); +bool nodeDisconnect(nodeValue &source, int sourceIndex, nodeValue &destination, int destinationIndex); +QHash &nodeRoleNames(Phantom proxy, QHash &roles); +bool nodeDelete(nodeValue &value, Phantom &context); +QVariant nodeData(const nodeValue &item, int role); +bool nodeSetData(nodeValue &item, const QVariant &value, int role); + // The graph model for the example -typedef GraphModel ExampleGraphModel; +typedef GraphModel> ExampleGraphModel; diff --git a/src/gui/models/nodeGraph/generatorGraphModel.cpp b/src/gui/models/nodeGraph/generatorGraphModel.cpp index 726ab203b4..9303569a29 100644 --- a/src/gui/models/nodeGraph/generatorGraphModel.cpp +++ b/src/gui/models/nodeGraph/generatorGraphModel.cpp @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (c) 2024 Team Dissolve and contributors +// Copyright (c) 2025 Team Dissolve and contributors #include "generatorGraphModel.h" #include "expression/variable.h" diff --git a/src/gui/models/nodeGraph/generatorGraphModel.h b/src/gui/models/nodeGraph/generatorGraphModel.h index a862aac132..19c78704af 100644 --- a/src/gui/models/nodeGraph/generatorGraphModel.h +++ b/src/gui/models/nodeGraph/generatorGraphModel.h @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (c) 2024 Team Dissolve and contributors +// Copyright (c) 2025 Team Dissolve and contributors #pragma once @@ -10,26 +10,8 @@ #include "gui/models/nodeGraph/instances/all.h" #include "nodeWrapper.h" -// The variant of all of the types that we will examine -using GeneratorGraphInnerType = std::variant; - -// A class to contain the inner type, since we need a constructor that -// take a QVariant -class GeneratorGraphNode -{ - public: - GeneratorGraphNode(QVariant var = {}); - GeneratorGraphInnerType value; -}; - -// All of these types may require access to CoreData -template <> struct GraphNodeContext -{ - using type = CoreData *; -}; - // A graph node model for looking at the generators on a configuration -class GeneratorGraphModel : public GraphModel +class GeneratorGraphModel : public GraphModel { Q_OBJECT // The Dissolve Model that contains the Dissolve object instance we diff --git a/src/gui/models/nodeGraph/graphEdgeModel.cpp b/src/gui/models/nodeGraph/graphEdgeModel.cpp index e44a633216..99ea38ba0a 100644 --- a/src/gui/models/nodeGraph/graphEdgeModel.cpp +++ b/src/gui/models/nodeGraph/graphEdgeModel.cpp @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (c) 2024 Team Dissolve and contributors +// Copyright (c) 2025 Team Dissolve and contributors #include "gui/models/nodeGraph/graphEdgeModel.h" diff --git a/src/gui/models/nodeGraph/graphEdgeModel.h b/src/gui/models/nodeGraph/graphEdgeModel.h index aa96c7e51f..ecb24cce7e 100644 --- a/src/gui/models/nodeGraph/graphEdgeModel.h +++ b/src/gui/models/nodeGraph/graphEdgeModel.h @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (c) 2024 Team Dissolve and contributors +// Copyright (c) 2025 Team Dissolve and contributors #pragma once diff --git a/src/gui/models/nodeGraph/graphModel.h b/src/gui/models/nodeGraph/graphModel.h index b08854939c..247edbd424 100644 --- a/src/gui/models/nodeGraph/graphModel.h +++ b/src/gui/models/nodeGraph/graphModel.h @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (c) 2024 Team Dissolve and contributors +// Copyright (c) 2025 Team Dissolve and contributors #pragma once @@ -37,15 +37,17 @@ **/ // This is the base class for any node graph type -template class GraphModel : public GraphModelBase +template + requires Graphable +class GraphModel : public GraphModelBase { public: GraphModel() : nodes_(this) {} protected: - typename GraphNodeContext::type context_; + Context context_; // The nodes in the model - std::vector> items_; + std::vector> items_; // Get index of name int indexByName(std::string name) override { @@ -55,7 +57,7 @@ template class GraphModel : public GraphModelBase public: // Access the acutal nodes in the model - std::vector> &items() { return items_; } + std::vector> &items() { return items_; } // Access the GraphNodeModel QAbstractListModel *nodes() override { return &nodes_; } @@ -120,5 +122,5 @@ template class GraphModel : public GraphModelBase protected: // The abstract data model for the nodes - GraphNodeModel nodes_; + GraphNodeModel nodes_; }; diff --git a/src/gui/models/nodeGraph/graphModelBase.cpp b/src/gui/models/nodeGraph/graphModelBase.cpp index 6bd9b3074f..853d9f728c 100644 --- a/src/gui/models/nodeGraph/graphModelBase.cpp +++ b/src/gui/models/nodeGraph/graphModelBase.cpp @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (c) 2024 Team Dissolve and contributors +// Copyright (c) 2025 Team Dissolve and contributors #include "graphEdgeModel.h" #include "graphModel.h" diff --git a/src/gui/models/nodeGraph/graphModelBase.h b/src/gui/models/nodeGraph/graphModelBase.h index aa0fcd11d8..43e0a36f84 100644 --- a/src/gui/models/nodeGraph/graphModelBase.h +++ b/src/gui/models/nodeGraph/graphModelBase.h @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (c) 2024 Team Dissolve and contributors +// Copyright (c) 2025 Team Dissolve and contributors #pragma once diff --git a/src/gui/models/nodeGraph/graphNodeModel.h b/src/gui/models/nodeGraph/graphNodeModel.h index bde80a506e..476f59fa2c 100644 --- a/src/gui/models/nodeGraph/graphNodeModel.h +++ b/src/gui/models/nodeGraph/graphNodeModel.h @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (c) 2024 Team Dissolve and contributors +// Copyright (c) 2025 Team Dissolve and contributors #pragma once @@ -8,7 +8,9 @@ #include #include -template class GraphModel; +template + requires Graphable +class GraphModel; // A base to add a static terms or properties to GraphNodeModel class GraphNodeModelBase : public QAbstractListModel @@ -22,20 +24,22 @@ class GraphNodeModelBase : public QAbstractListModel // The model for accessing the node data (which is *held* in the // GraphModel class) -template class GraphNodeModel : public GraphNodeModelBase +template + requires Graphable +class GraphNodeModel : public GraphNodeModelBase { - friend GraphModel; + friend GraphModel; public: - GraphNodeModel(GraphModel *parent = nullptr) : parent_(parent) {} - GraphNodeModel(const GraphNodeModel &other) : parent_(other.parent_) {} + GraphNodeModel(GraphModel *parent = nullptr) : parent_(parent) {} + GraphNodeModel(const GraphNodeModel &other) : parent_(other.parent_) {} - GraphNodeModel &operator=(const GraphNodeModel &other) + GraphNodeModel &operator=(const GraphNodeModel &other) { parent_ = other.parent_; return *this; } - bool operator!=(const GraphNodeModel &other) { return &parent_ != &other.parent_; } + bool operator!=(const GraphNodeModel &other) { return &parent_ != &other.parent_; } // Number of nodes (required by QAbstractListModel) int rowCount(const QModelIndex &parent = QModelIndex()) const override { return parent_->items().size(); } @@ -43,13 +47,14 @@ template class GraphNodeModel : public GraphNodeModelBase // Labels for QML roles (required by QAbstractListModel) QHash roleNames() const override { + Phantom phantom; QHash roles; roles[Qt::UserRole] = "name"; roles[Qt::UserRole + 1] = "posX"; roles[Qt::UserRole + 2] = "posY"; roles[Qt::UserRole + 3] = "type"; roles[Qt::UserRole + 4] = "icon"; - return nodeRoleNames(roles); + return nodeRoleNames(phantom, roles); } // Data accessor (required by QAbstractListModel) @@ -101,5 +106,5 @@ template class GraphNodeModel : public GraphNodeModelBase private: // The GraphModel that this is part of (which will hold the actual vector of nodes - GraphModel *parent_; + GraphModel *parent_; }; diff --git a/src/gui/models/nodeGraph/instances/all.h b/src/gui/models/nodeGraph/instances/all.h index 1277a1c206..9ba25e0e39 100644 --- a/src/gui/models/nodeGraph/instances/all.h +++ b/src/gui/models/nodeGraph/instances/all.h @@ -1,10 +1,35 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (c) 2024 Team Dissolve and contributors +// Copyright (c) 2025 Team Dissolve and contributors #pragma once #include "configuration.h" #include "generator.h" #include "generatorNode.h" +#include -// This file exists to easily include all of the templated methods for all of the types for the Node Graph +// The variant of all of the types that we will examine +using GeneratorGraphInnerType = std::variant; + +// A class to contain the inner type, since we need a constructor that +// take a QVariant +class GeneratorGraphNode +{ + public: + GeneratorGraphNode(QVariant var = {}); + GeneratorGraphInnerType value; +}; + +std::string nodeName(const GeneratorGraphNode &value); +std::string nodeTypeName(const GeneratorGraphNode &value); +std::string nodeTypeIcon(const GeneratorGraphNode &value); +void setNodeName(GeneratorGraphNode &value, std::string); +QVariant nodeGetValue(const GeneratorGraphNode &value); +bool nodeConnect(GeneratorGraphNode &source, int sourceIndex, GeneratorGraphNode &destionation, int destinationIndex); +bool nodeConnectable(const GeneratorGraphNode &source, int sourceIndex, const GeneratorGraphNode &destination, + int destinationIndex); +bool nodeDisconnect(GeneratorGraphNode &source, int sourceIndex, GeneratorGraphNode &destination, int destinationIndex); +QHash &nodeRoleNames(Phantom proxy, QHash &roles); +bool nodeDelete(GeneratorGraphNode &item, CoreData *coreData); +QVariant nodeData(const GeneratorGraphNode &item, int role); +bool nodeSetData(GeneratorGraphNode &item, const QVariant &value, int role); diff --git a/src/gui/models/nodeGraph/instances/configuration.cpp b/src/gui/models/nodeGraph/instances/configuration.cpp index 67c1f471a7..e1c9ddb76a 100644 --- a/src/gui/models/nodeGraph/instances/configuration.cpp +++ b/src/gui/models/nodeGraph/instances/configuration.cpp @@ -4,16 +4,13 @@ #include "gui/models/nodeGraph/nodeWrapper.h" // The name of the type (for delegate dispatch) -template <> std::string nodeTypeName(Configuration *const &value) { return "Configuration"; } +std::string nodeTypeName(const Configuration *value) { return "Configuration"; } // The path to the icon for the node -template <> std::string nodeTypeIcon(Configuration *const &value) -{ - return "qrc:/Dissolve/icons/configuration.svg"; -} +std::string nodeTypeIcon(const Configuration *value) { return "qrc:/Dissolve/icons/configuration.svg"; } // The title of the node -template <> std::string nodeName(Configuration *const &value) +std::string nodeName(const Configuration *value) { if (!value) @@ -23,7 +20,7 @@ template <> std::string nodeName(Configuration *const &value) } // Get a specific piece of information from a node by index -template <> QVariant nodeData(Configuration *const &value, int role) +QVariant nodeData(const Configuration *value, int role) { using names = GeneratorGraphModel::PropertyIndex; if (!value) @@ -42,7 +39,7 @@ template <> QVariant nodeData(Configuration *const &value, int role) } // Set a specific piece of information from a node by index -template <> bool nodeSetData(Configuration *&item, const QVariant &value, int role) +bool nodeSetData(Configuration *item, const QVariant &value, int role) { using names = GeneratorGraphModel::PropertyIndex; switch (role) @@ -56,7 +53,7 @@ template <> bool nodeSetData(Configuration *&item, const QVariant &value, int ro } // Delete the node -template <> bool nodeDelete(Configuration *&item, typename GraphNodeContext::type &coreData) +bool nodeDelete(Configuration *item, CoreData *coreData) { coreData->removeConfiguration(item); return true; diff --git a/src/gui/models/nodeGraph/instances/configuration.h b/src/gui/models/nodeGraph/instances/configuration.h index 070c52e0cf..e686c01991 100644 --- a/src/gui/models/nodeGraph/instances/configuration.h +++ b/src/gui/models/nodeGraph/instances/configuration.h @@ -1,14 +1,18 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (c) 2024 Team Dissolve and contributors +// Copyright (c) 2025 Team Dissolve and contributors #pragma once #include "classes/configuration.h" -#include "gui/models/nodeGraph/nodeWrapper.h" - -// Configurations need access to the CoreData to access all of their -// children. -template <> struct GraphNodeContext -{ - using type = CoreData *; -}; +#include "gui/models/nodeGraph/phantom.h" + +std::string nodeName(const Configuration *value); +std::string nodeTypeName(const Configuration *value); +std::string nodeTypeIcon(const Configuration *value); + +bool nodeConnect(Configuration *source, int sourceIndex, Configuration *destination, int destinationIndex); +bool nodeConnectable(const Configuration *source, int sourceIndex, const Configuration *destination, int destinationIndex); +bool nodeDisconnect(Configuration *source, int sourceIndex, Configuration *destination, int destinationIndex); +bool nodeDelete(Configuration *item, CoreData *coreData); +QVariant nodeData(const Configuration *value, int role); +bool nodeSetData(Configuration *item, const QVariant &value, int role); diff --git a/src/gui/models/nodeGraph/instances/generator.cpp b/src/gui/models/nodeGraph/instances/generator.cpp index 59305802e2..cb84f6e436 100644 --- a/src/gui/models/nodeGraph/instances/generator.cpp +++ b/src/gui/models/nodeGraph/instances/generator.cpp @@ -4,16 +4,16 @@ #include "gui/models/nodeGraph/nodeWrapper.h" // The name of the type (for delegate dispatch) -template <> std::string nodeTypeName(Generator *const &value) { return "Generator"; } +std::string nodeTypeName(Generator *const &value) { return "Generator"; } // The path to the icon for the node -template <> std::string nodeTypeIcon(Generator *const &value) { return "qrc:/Dissolve/icons/generator.svg"; } +std::string nodeTypeIcon(Generator *const &value) { return "qrc:/Dissolve/icons/generator.svg"; } // The title of the node -template <> std::string nodeName(Generator *const &value) { return "Generator"; } +std::string nodeName(Generator *const &value) { return "Generator"; } // Get a specific piece of information from a node by index -template <> QVariant nodeData(Generator *const &value, int role) +QVariant nodeData(Generator *value, int role) { using names = GeneratorGraphModel::PropertyIndex; if (!value) @@ -30,7 +30,7 @@ template <> QVariant nodeData(Generator *const &value, int role) } // Set a specific piece of information from a node by index -template <> bool nodeSetData(Generator *&item, const QVariant &value, int role) { return false; } +bool nodeSetData(Generator *item, const QVariant &value, int role) { return false; } // Delete the node -template <> bool nodeDelete(Generator *&item, typename GraphNodeContext::type &coreData) { return false; } +bool nodeDelete(Generator *item, CoreData *coreData) { return false; } diff --git a/src/gui/models/nodeGraph/instances/generator.h b/src/gui/models/nodeGraph/instances/generator.h index fa29de2365..85ab1f95da 100644 --- a/src/gui/models/nodeGraph/instances/generator.h +++ b/src/gui/models/nodeGraph/instances/generator.h @@ -1,14 +1,14 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (c) 2024 Team Dissolve and contributors +// Copyright (c) 2025 Team Dissolve and contributors #pragma once #include "generator/generator.h" -#include "gui/models/nodeGraph/nodeWrapper.h" +#include "gui/models/nodeGraph/phantom.h" -// Generators need access to the CoreData to access all of their -// children. -template <> struct GraphNodeContext -{ - using type = CoreData *; -}; +std::string nodeName(Generator *const &value); +std::string nodeTypeName(Generator *const &value); +std::string nodeTypeIcon(Generator *const &value); +bool nodeDelete(Generator *item, CoreData *coreData); +QVariant nodeData(Generator *value, int role); +bool nodeSetData(Generator *item, const QVariant &value, int role); diff --git a/src/gui/models/nodeGraph/instances/generatorGraphNode.cpp b/src/gui/models/nodeGraph/instances/generatorGraphNode.cpp index f7b230319d..04f5cc72aa 100644 --- a/src/gui/models/nodeGraph/instances/generatorGraphNode.cpp +++ b/src/gui/models/nodeGraph/instances/generatorGraphNode.cpp @@ -3,7 +3,7 @@ #include "gui/models/nodeGraph/generatorGraphModel.h" // The value of the node -template <> QVariant nodeGetValue(const GeneratorGraphNode value) +QVariant nodeGetValue(const GeneratorGraphNode value) { return std::visit( [](auto *arg) -> QVariant @@ -16,7 +16,7 @@ template <> QVariant nodeGetValue(const GeneratorGraphNode v } // The name of the type (for delegate dispatch) -template <> std::string nodeTypeName(const GeneratorGraphNode &value) +std::string nodeTypeName(const GeneratorGraphNode &value) { return std::visit( [](auto *arg) -> std::string @@ -29,60 +29,55 @@ template <> std::string nodeTypeName(const GeneratorGraphNod } // Link an indexed position on the source to an indexed position on the destination -template <> -bool nodeConnect(GeneratorGraphNode &source, int sourceIndex, GeneratorGraphNode &destination, - int destinationIndex) +bool nodeConnect(GeneratorGraphNode &source, int sourceIndex, GeneratorGraphNode &destination, int destinationIndex) { return false; } // Confirm that a connection is possible (e.g. types match and index isn't already connected) -template <> -bool nodeConnectable(const GeneratorGraphNode &source, int sourceIndex, - const GeneratorGraphNode &destination, int destinationIndex) +bool nodeConnectable(const GeneratorGraphNode &source, int sourceIndex, const GeneratorGraphNode &destination, + int destinationIndex) { return false; } // Unlink an indexed position on the source to an indexed position on the destination -template <> -bool nodeDisconnect(GeneratorGraphNode &source, int sourceIndex, GeneratorGraphNode &destination, - int destinationIndex) +bool nodeDisconnect(GeneratorGraphNode &source, int sourceIndex, GeneratorGraphNode &destination, int destinationIndex) { return true; } // The path to the icon for the node -template <> std::string nodeTypeIcon(const GeneratorGraphNode &value) +std::string nodeTypeIcon(const GeneratorGraphNode &value) { return std::visit([](auto *arg) { return nodeTypeIcon(arg); }, value.value); } // The title of the node -template <> std::string nodeName(const GeneratorGraphNode &value) +std::string nodeName(const GeneratorGraphNode &value) { return std::visit([](auto *arg) { return nodeName(arg); }, value.value); } // Change the title of the node -template <> void setNodeName(GeneratorGraphNode &value, const std::string name) {} +void setNodeName(GeneratorGraphNode &value, const std::string name) {} // Set a specific piece of information from a node by index -template <> bool nodeSetData(GeneratorGraphNode &item, const QVariant &value, int role) +bool nodeSetData(GeneratorGraphNode &item, const QVariant &value, int role) { using names = GeneratorGraphModel::PropertyIndex; return std::visit([&value, role](auto *arg) { return nodeSetData(arg, value, role); }, item.value); } // Get a specific piece of information from a node by index -template <> QVariant nodeData(const GeneratorGraphNode &item, int role) +QVariant nodeData(const GeneratorGraphNode &item, int role) { using names = GeneratorGraphModel::PropertyIndex; return std::visit([role](auto *arg) -> QVariant { return nodeData(arg, role); }, item.value); } // Append the roles for the type onto the QHash -template <> QHash &nodeRoleNames(QHash &roles) +QHash &nodeRoleNames(Phantom proxy, QHash &roles) { auto base = Qt::UserRole + GraphNodeModelBase::ownedRoles; using names = GeneratorGraphModel::PropertyIndex; @@ -94,8 +89,7 @@ template <> QHash &nodeRoleNames(QHash -bool nodeDelete(GeneratorGraphNode &item, typename GraphNodeContext::type &coreData) +bool nodeDelete(GeneratorGraphNode &item, CoreData *coreData) { return std::visit([&](auto *arg) -> bool { return nodeDelete(arg, coreData); }, item.value); } diff --git a/src/gui/models/nodeGraph/instances/generatorGraphNode.h b/src/gui/models/nodeGraph/instances/generatorGraphNode.h index c6f0068ab0..a0a1bfb551 100644 --- a/src/gui/models/nodeGraph/instances/generatorGraphNode.h +++ b/src/gui/models/nodeGraph/instances/generatorGraphNode.h @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (c) 2024 Team Dissolve and contributors - -#include "gui/models/nodeGraph/generatorGraphModel.h" -#include "gui/models/nodeGraph/nodeWrapper.h" +// Copyright (c) 2025 Team Dissolve and contributors #pragma once + +#include "gui/models/nodeGraph/generatorGraphModel.h" diff --git a/src/gui/models/nodeGraph/instances/generatorNode.cpp b/src/gui/models/nodeGraph/instances/generatorNode.cpp index 784b702ae9..6d27b26c74 100644 --- a/src/gui/models/nodeGraph/instances/generatorNode.cpp +++ b/src/gui/models/nodeGraph/instances/generatorNode.cpp @@ -6,24 +6,24 @@ #include "keywords/nodeValue.h" // The name of the type (for delegate dispatch) -template <> std::string nodeTypeName(GeneratorNode *const &value) { return "GeneratorNode"; } +std::string nodeTypeName(GeneratorNode *const &value) { return "GeneratorNode"; } // The path to the icon for the node -template <> std::string nodeTypeIcon(GeneratorNode *const &value) +std::string nodeTypeIcon(GeneratorNode *const &value) { auto name = GeneratorNode::nodeTypes().keyword(value->type()); return "qrc:/Dissolve/icons/nodes/" + name + ".svg"; } // The title of the node -template <> std::string nodeName(GeneratorNode *const &value) +std::string nodeName(GeneratorNode *const &value) { std::string result = {value->name().begin(), value->name().end()}; return result; } // Get a specific piece of information from a node by index -template <> QVariant nodeData(GeneratorNode *const &value, int role) +QVariant nodeData(const GeneratorNode *value, int role) { using names = GeneratorGraphModel::PropertyIndex; if (!value) @@ -50,10 +50,10 @@ template <> QVariant nodeData(GeneratorNode *const &value, int role) } // Set a specific piece of information from a node by index -template <> bool nodeSetData(GeneratorNode *&item, const QVariant &value, int role) { return false; } +bool nodeSetData(GeneratorNode *item, const QVariant &value, int role) { return false; } // Delete the node -template <> bool nodeDelete(GeneratorNode *&item, typename GraphNodeContext::type &coreData) +bool nodeDelete(GeneratorNode *item, CoreData *coreData) { for (auto &conf : coreData->configurations()) { diff --git a/src/gui/models/nodeGraph/instances/generatorNode.h b/src/gui/models/nodeGraph/instances/generatorNode.h index 59482a855b..7254a3c5aa 100644 --- a/src/gui/models/nodeGraph/instances/generatorNode.h +++ b/src/gui/models/nodeGraph/instances/generatorNode.h @@ -1,13 +1,17 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (c) 2024 Team Dissolve and contributors +// Copyright (c) 2025 Team Dissolve and contributors #pragma once -#include "gui/models/nodeGraph/generatorGraphModel.h" +#include "classes/coreData.h" +#include "gui/models/nodeGraph/phantom.h" +#include -// GeneratorNodes need access to the CoreData to access all of their -// children. -template <> struct GraphNodeContext -{ - using type = CoreData *; -}; +class GeneratorNode; + +std::string nodeName(GeneratorNode *const &value); +std::string nodeTypeName(GeneratorNode *const &value); +std::string nodeTypeIcon(GeneratorNode *const &value); +bool nodeDelete(GeneratorNode *item, CoreData *coreData); +QVariant nodeData(const GeneratorNode *value, int role); +bool nodeSetData(GeneratorNode *item, const QVariant &value, int role); diff --git a/src/gui/models/nodeGraph/nodeWrapper.h b/src/gui/models/nodeGraph/nodeWrapper.h index 0f047b152d..a4aa90138b 100644 --- a/src/gui/models/nodeGraph/nodeWrapper.h +++ b/src/gui/models/nodeGraph/nodeWrapper.h @@ -1,16 +1,20 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (c) 2024 Team Dissolve and contributors +// Copyright (c) 2025 Team Dissolve and contributors #pragma once +#include "gui/models/nodeGraph/instances/all.h" #include +#include #include /** - This file contains a series of templates that need to be overloaded - to allow a type to be displayed as a node. This is essentially a - C++ Concept, but without all the compiler checks and niceties that - we will get with C++20. + This file contains a C++20 concept which defines whether a type can + be shown as a graph. Any type T which contains implementations for + all of the functions listed below can be displayed in a graph. A + second template parameter Context was also necessary to implement + deletion as most nodes would need to know *where* they were being + deleted from. A concept was chosen over class methods because: @@ -23,38 +27,59 @@ part of the class even in the command line version. As some of the methods require Qt types (e.g. QHash, QVariant), then Qt would suddenly become a dependency of the command line code. + + This code is a direct conversion of the template code that came + before. Truth be told, the connection functions should likely be + split out into a separate Concept (allowing us to check for + connection correctness as compile time), but that is outside of the + scope of this PR. **/ -// A template that we can specialise to associate a a context type -// with a type. -template struct GraphNodeContext -{ - using type = GraphNodeContext; +template +concept Graphable = requires(T a, Context context, const std::string name, T b, int sourceIndex, int destinationIndex, + Phantom proxy, QHash &baseHash, int role, QVariant value) { + { + nodeDelete(a, context) + } -> std::same_as; + { + nodeRoleNames(proxy, baseHash) + } -> std::same_as &>; + { + nodeName(a) + } -> std::same_as; + { + nodeTypeName(a) + } -> std::same_as; + { + nodeTypeIcon(a) + } -> std::same_as; + { + setNodeName(a, name) + }; + { + nodeGetValue(a) + } -> std::same_as; + { + nodeConnect(a, sourceIndex, b, destinationIndex) + } -> std::same_as; + { + nodeConnectable(a, sourceIndex, b, destinationIndex) + } -> std::same_as; + { + nodeDisconnect(a, sourceIndex, b, destinationIndex) + } -> std::same_as; + { + nodeData(a, role) + } -> std::convertible_to; + { + nodeSetData(a, value, role) + } -> std::convertible_to; }; -// Append the roles for the type onto the QHash -template QHash &nodeRoleNames(QHash &base); -// The name of the type (for delegate dispatch) -template std::string nodeTypeName(const T &value); -// The path to the icon for the node -template std::string nodeTypeIcon(const T &value); -// Delete the node -template bool nodeDelete(T &value, typename GraphNodeContext::type &context); -// The title of the node -template std::string nodeName(const T &value); -// Change the title of the node -template void setNodeName(T &value, const std::string); -// The value of the node -template QVariant nodeGetValue(const T value); -// Link an indexed position on the source to an indexed position on the destination -template bool nodeConnect(T &source, int sourceIndex, T &destination, int destinationIndex); -// Confirm that a connection is possible (e.g. types match and index isn't already connected) -template bool nodeConnectable(const T &source, int sourceIndex, const T &destination, int destinationIndex); -// Unlink an indexed position on the source to an indexed position on the destination -template bool nodeDisconnect(T &source, int sourceIndex, T &destination, int destinationIndex); - // A wrapper with supplemental information for a node -template class NodeWrapper +template + requires Graphable +class NodeWrapper { public: NodeWrapper(QVariant value) : value_(value) {} @@ -72,9 +97,3 @@ template class NodeWrapper // The actual value of the node T value_; }; - -// Get a specific piece of information from a node by index -template QVariant nodeData(const T &, int role); - -// Set a specific piece of information from a node by index -template bool nodeSetData(T &, const QVariant &value, int role); diff --git a/src/gui/models/nodeGraph/phantom.h b/src/gui/models/nodeGraph/phantom.h new file mode 100644 index 0000000000..1f46bb7ecb --- /dev/null +++ b/src/gui/models/nodeGraph/phantom.h @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2025 Team Dissolve and contributors + +#pragma once + +/* + + The Phantom type is a compile time marker of zero size that allows + the passing of type info to a function. For example, you could have + the `getBits` function with overloads. + + >>> + int getBits(Phantom phantom) { return 8; } + int getBits(Phantom phantom) { return 64; } + <<< + + Now, there's two other ways that this could have been written + + >>> + int getBits(char phantom) { return 8; } + int getBits(double phantom) { return 64; } + <<< + + While this works well for our trivial example, imagine that we had a + type with an expensive constructor. If the information is purely + related to the type, there's no need to instantiate a copy of it to + determine what we already know. + + The other solution would be to use a function template + + >>> + template int getBits(); + template <> int getBits { return 8; } + template <> int getBits { return 64; } + <<< + + This works up until the moment that we have a type for which + `getBits` is undefined. We would not implement the template + overload for that type, but attempts to call that function would + return an odd linker error. After all, we *have* provided a + function header that declares that `getBits` is defined for *all* + types T. + + Using Phantom types, we can still get the overloads at compile time, + but we're only producing headers for explicitly the types that we + want to support. + + */ +template struct Phantom +{ +}; diff --git a/src/gui/qml/nodeGraph/ExampleDelegate.qml b/src/gui/qml/nodeGraph/ExampleDelegate.qml index 344c4b7790..987832b803 100644 --- a/src/gui/qml/nodeGraph/ExampleDelegate.qml +++ b/src/gui/qml/nodeGraph/ExampleDelegate.qml @@ -26,6 +26,7 @@ Item { Text { id: root + anchors.fill: parent height: contentHeight text: value @@ -52,6 +53,7 @@ Item { Text { id: root + anchors.fill: parent color: value != null ? "black" : "red" height: contentHeight @@ -76,16 +78,19 @@ Item { ColumnLayout { id: root + anchors.fill: parent Text { id: xnode + height: contentHeight text: "X: " + px width: contentWidth } Text { id: ynode + height: contentHeight text: "Y: " + py width: contentWidth diff --git a/src/gui/qml/nodeGraph/GeneratorDelegate.qml b/src/gui/qml/nodeGraph/GeneratorDelegate.qml index 991bdea126..2a6a6097a8 100644 --- a/src/gui/qml/nodeGraph/GeneratorDelegate.qml +++ b/src/gui/qml/nodeGraph/GeneratorDelegate.qml @@ -33,6 +33,7 @@ Item { } TextField { id: root + text: temperature } Text { @@ -67,6 +68,7 @@ Item { Text { id: root + text: name } Text { @@ -98,6 +100,7 @@ Item { Text { id: root + text: "name" } } @@ -122,6 +125,7 @@ Item { Text { id: root + anchors.fill: parent color: value != null ? "black" : "red" height: contentHeight @@ -146,16 +150,19 @@ Item { ColumnLayout { id: root + anchors.fill: parent Text { id: xnode + height: contentHeight text: "X: " + px width: contentWidth } Text { id: ynode + height: contentHeight text: "Y: " + py width: contentWidth diff --git a/src/gui/qml/nodeGraph/GraphView.qml b/src/gui/qml/nodeGraph/GraphView.qml index 995cb65e60..af7d754bea 100644 --- a/src/gui/qml/nodeGraph/GraphView.qml +++ b/src/gui/qml/nodeGraph/GraphView.qml @@ -6,12 +6,13 @@ import Qt.labs.qmlmodels Pane { id: graphRoot + property double curveOffset: 125 property variant delegate property variant edgeModel + property var nameLookup: ({}) property variant nodeModel property variant rootModel - property var nameLookup: ({}) // Edge connections Repeater { @@ -44,9 +45,11 @@ Pane { // Actual nodes Repeater { id: nodeRepeater + delegate: graphRoot.delegate model: nodeModel - onItemAdded: function(index, item) { + + onItemAdded: function (index, item) { nameLookup[item.nodeType] = index; } } diff --git a/src/gui/qml/nodeGraph/NodeBox.qml b/src/gui/qml/nodeGraph/NodeBox.qml index 362e252872..660705d4f1 100644 --- a/src/gui/qml/nodeGraph/NodeBox.qml +++ b/src/gui/qml/nodeGraph/NodeBox.qml @@ -5,6 +5,7 @@ import Qt.labs.qmlmodels GroupBox { id: root + property double basey: header.height property string image property string nodeType @@ -15,6 +16,7 @@ GroupBox { label: RowLayout { id: header + Image { clip: true fillMode: Image.PreserveAspectFit @@ -24,24 +26,25 @@ GroupBox { } TextField { id: titleLabel + font.pointSize: 14 text: root.nodeType } Button { - onClicked: root.deleted() - contentItem: Image { fillMode: Image.PreserveAspectFit source: "qrc:/Dissolve/icons/cross.svg" sourceSize.height: titleLabel.height sourceSize.width: titleLabel.height } + + onClicked: root.deleted() } DragHandler { target: null - xAxis.onActiveValueChanged: delta => posX += delta; - yAxis.onActiveValueChanged: delta => posY += delta; + xAxis.onActiveValueChanged: delta => posX += delta + yAxis.onActiveValueChanged: delta => posY += delta } } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 502c99a99f..1d9663601d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -47,7 +47,7 @@ function(dissolve_add_test) target_include_directories(${TEST_NAME} PRIVATE ${Qt6Widgets_INCLUDE_DIRS}) target_link_libraries( ${TEST_NAME} - PUBLIC keywordWidgets models widgets render delegates + PUBLIC keywordWidgets models widgets render delegates nodeGraphModel PRIVATE Qt6::Core Qt6::Widgets ) endif(DISSOLVE_UNIT_TEST_GUI) diff --git a/tests/gui/graphModel.cpp b/tests/gui/graphModel.cpp index 003eaa9c32..d6e869ad6f 100644 --- a/tests/gui/graphModel.cpp +++ b/tests/gui/graphModel.cpp @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (c) 2024 Team Dissolve and contributors +// Copyright (c) 2025 Team Dissolve and contributors #include "gui/models/dissolveModel.h" #include "gui/models/nodeGraph/exampleGraphModel.h"