diff options
author | rtk0c <[email protected]> | 2021-05-14 20:52:00 -0700 |
---|---|---|
committer | rtk0c <[email protected]> | 2021-05-14 20:52:00 -0700 |
commit | fa13cad2bf12d8f7f63f7ff6aab07dd2eaf6ec6d (patch) | |
tree | 994198f3636030b3974f73170fc59b04a794681d /core/src | |
parent | 765df313e065f8401319c68ba70cd41b0bc34c9d (diff) |
Node graph editor basic functionality
Diffstat (limited to 'core/src')
-rw-r--r-- | core/src/Model/Workflow/Nodes/DocumentNodes.cpp | 2 | ||||
-rw-r--r-- | core/src/Model/Workflow/Nodes/NumericNodes.cpp | 4 | ||||
-rw-r--r-- | core/src/Model/Workflow/Nodes/TextNodes.cpp | 2 | ||||
-rw-r--r-- | core/src/Model/Workflow/Nodes/UserInputNodes.cpp | 4 | ||||
-rw-r--r-- | core/src/Model/Workflow/Workflow.hpp | 29 | ||||
-rw-r--r-- | core/src/Model/Workflow/Workflow_Main.cpp | 57 | ||||
-rw-r--r-- | core/src/UI/Localization.hpp | 1 | ||||
-rw-r--r-- | core/src/UI/UI_Workflows.cpp | 100 | ||||
-rw-r--r-- | core/src/Utils/Color.hpp | 4 |
9 files changed, 158 insertions, 45 deletions
diff --git a/core/src/Model/Workflow/Nodes/DocumentNodes.cpp b/core/src/Model/Workflow/Nodes/DocumentNodes.cpp index b858b49..3925dec 100644 --- a/core/src/Model/Workflow/Nodes/DocumentNodes.cpp +++ b/core/src/Model/Workflow/Nodes/DocumentNodes.cpp @@ -9,7 +9,7 @@ bool DocumentTemplateExpansionNode::IsInstance(const WorkflowNode* node) } DocumentTemplateExpansionNode::DocumentTemplateExpansionNode() - : WorkflowNode(KD_DocumentTemplateExpansion) + : WorkflowNode(KD_DocumentTemplateExpansion, false) { } diff --git a/core/src/Model/Workflow/Nodes/NumericNodes.cpp b/core/src/Model/Workflow/Nodes/NumericNodes.cpp index 083172e..ad51fb4 100644 --- a/core/src/Model/Workflow/Nodes/NumericNodes.cpp +++ b/core/src/Model/Workflow/Nodes/NumericNodes.cpp @@ -36,7 +36,7 @@ bool NumericOperationNode::IsInstance(const WorkflowNode* node) } NumericOperationNode::NumericOperationNode(OperationType type) - : WorkflowNode(OperationTypeToNodeKind(type)) + : WorkflowNode(OperationTypeToNodeKind(type), false) , mType{ type } { mInputs.resize(2); @@ -85,7 +85,7 @@ bool NumericExpressionNode::IsInstance(const WorkflowNode* node) } NumericExpressionNode::NumericExpressionNode() - : WorkflowNode(KD_NumericExpression) + : WorkflowNode(KD_NumericExpression, false) { } diff --git a/core/src/Model/Workflow/Nodes/TextNodes.cpp b/core/src/Model/Workflow/Nodes/TextNodes.cpp index b70290d..6c71309 100644 --- a/core/src/Model/Workflow/Nodes/TextNodes.cpp +++ b/core/src/Model/Workflow/Nodes/TextNodes.cpp @@ -53,7 +53,7 @@ bool TextFormatterNode::IsInstance(const WorkflowNode* node) } TextFormatterNode::TextFormatterNode() - : WorkflowNode(KD_TextFormatting) + : WorkflowNode(KD_TextFormatting, false) { } diff --git a/core/src/Model/Workflow/Nodes/UserInputNodes.cpp b/core/src/Model/Workflow/Nodes/UserInputNodes.cpp index 3a30631..f8d3e01 100644 --- a/core/src/Model/Workflow/Nodes/UserInputNodes.cpp +++ b/core/src/Model/Workflow/Nodes/UserInputNodes.cpp @@ -9,7 +9,7 @@ bool FormInputNode::IsInstance(const WorkflowNode* node) } FormInputNode::FormInputNode() - : WorkflowNode(KD_FormInput) + : WorkflowNode(KD_FormInput, false) { } @@ -23,7 +23,7 @@ bool DatabaseRowsInputNode::IsInstance(const WorkflowNode* node) } DatabaseRowsInputNode::DatabaseRowsInputNode() - : WorkflowNode(KD_DatabaseRowsInput) + : WorkflowNode(KD_DatabaseRowsInput, false) { } diff --git a/core/src/Model/Workflow/Workflow.hpp b/core/src/Model/Workflow/Workflow.hpp index 79c2b2f..847aa72 100644 --- a/core/src/Model/Workflow/Workflow.hpp +++ b/core/src/Model/Workflow/Workflow.hpp @@ -4,6 +4,7 @@ #include "Utils/Vector.hpp" #include "cplt_fwd.hpp" +#include <imgui_node_editor.h> #include <cstddef> #include <cstdint> #include <iosfwd> @@ -14,6 +15,8 @@ #include <variant> #include <vector> +namespace ImNodes = ax::NodeEditor; + class WorkflowConnection { public: @@ -31,7 +34,7 @@ public: bool IsValid() const; /// Used for `LinkId` when interfacing with imgui node editor. Runtime only (not saved to disk and generated when loading). - size_t GetLinkId() const; + ImNodes::LinkId GetLinkId() const; void DrawDebugInfo() const; void ReadFrom(std::istream& stream); @@ -99,13 +102,14 @@ protected: uint32_t mId; Kind mKind; int mDepth; + bool mLocked; public: static const char* FormatKind(Kind kind); static const char* FormatType(Type type); static std::unique_ptr<WorkflowNode> CreateByKind(Kind kind); - WorkflowNode(Kind kind); + WorkflowNode(Kind kind, bool locked); virtual ~WorkflowNode() = default; WorkflowNode(const WorkflowNode&) = delete; @@ -118,23 +122,28 @@ public: uint32_t GetId() const; /// Used for `NodeId` when interfacing with imgui node editor. Runtime only (not saved to disk and generated when loading). - size_t GetNodeId() const; + ImNodes::NodeId GetNodeId() const; Kind GetKind() const; int GetDepth() const; + bool IsLocked() const; Type GetType() const; bool IsInputNode() const; bool IsOutputNode() const; - void ConnectInput(uint32_t pinId, WorkflowNode& output, uint32_t outputNodeId); + void ConnectInput(uint32_t pinId, WorkflowNode& srcNode, uint32_t srcPinId); void DisconnectInput(uint32_t pinId); + + void DrawInputPinDebugInfo(uint32_t pinId) const; const InputPin& GetInputPin(uint32_t pinId)const ; - size_t GetInputPinUniqueId(uint32_t pinId) const; + ImNodes::PinId GetInputPinUniqueId(uint32_t pinId) const; - void ConnectOutput(uint32_t pinId, WorkflowNode& input, uint32_t inputNodeId); + void ConnectOutput(uint32_t pinId, WorkflowNode& dstNode, uint32_t dstPinId); void DisconnectOutput(uint32_t pinId); + + void DrawOutputPinDebugInfo(uint32_t pinId)const; const OutputPin& GetOutputPin(uint32_t pinId)const ; - size_t GetOutputPinUniqueId(uint32_t pinId) const; + ImNodes::PinId GetOutputPinUniqueId(uint32_t pinId) const; virtual void Evaluate(WorkflowEvaluationContext& ctx) = 0; @@ -187,7 +196,9 @@ public: std::vector<std::unique_ptr<BaseValue>>& GetConstants(); WorkflowConnection* GetConnectionById(uint32_t id); + WorkflowConnection* GetConnectionByLinkId(ImNodes::LinkId linkId); WorkflowNode* GetNodeById(uint32_t id); + WorkflowNode* GetNodeByNodeId(ImNodes::NodeId nodeId); BaseValue* GetConstantById(uint32_t id); struct GlobalPinId @@ -200,8 +211,8 @@ public: }; /// `pinId` should be the `UniqueId` of a pin from a node that's within this workflow. - GlobalPinId DisassembleGlobalPinId(size_t id); - size_t FabricateGlobalPinId(const WorkflowNode& node, uint32_t pinId, bool isOutput) const; + GlobalPinId DisassembleGlobalPinId(ImNodes::PinId id); + ImNodes::PinId FabricateGlobalPinId(const WorkflowNode& node, uint32_t pinId, bool isOutput) const; const std::vector<std::vector<uint32_t>>& GetDepthGroups() const; bool DoesDepthNeedsUpdate() const; diff --git a/core/src/Model/Workflow/Workflow_Main.cpp b/core/src/Model/Workflow/Workflow_Main.cpp index 678b4b8..1b50064 100644 --- a/core/src/Model/Workflow/Workflow_Main.cpp +++ b/core/src/Model/Workflow/Workflow_Main.cpp @@ -24,7 +24,7 @@ bool WorkflowConnection::IsValid() const return Id != 0; } -size_t WorkflowConnection::GetLinkId() const +ImNodes::LinkId WorkflowConnection::GetLinkId() const { // Our id is 0-based (represents an index directly) // but imgui-node-editor uses the value 0 to represent a null id, so we need to offset by 1 @@ -33,12 +33,10 @@ size_t WorkflowConnection::GetLinkId() const void WorkflowConnection::DrawDebugInfo() const { - ImGui::BeginTooltip(); ImGui::Text("Source (node with output pin):"); ImGui::Text("{ Node = %u, Pin = %u }", SourceNode, SourcePin); ImGui::Text("Destination (node with input pin):"); ImGui::Text("{ Node = %u, Pin = %u }", DestinationNode, DestinationPin); - ImGui::EndTooltip(); } void WorkflowConnection::ReadFrom(std::istream& stream) @@ -78,9 +76,10 @@ BaseValue::Kind WorkflowNode::OutputPin::GetMatchingType() const return MatchingType; } -WorkflowNode::WorkflowNode(Kind kind) +WorkflowNode::WorkflowNode(Kind kind, bool locked) : mKind{ kind } , mDepth{ -1 } + , mLocked(locked) { } @@ -99,7 +98,7 @@ uint32_t WorkflowNode::GetId() const return mId; } -size_t WorkflowNode::GetNodeId() const +ImNodes::NodeId WorkflowNode::GetNodeId() const { // See WorkflowConnection::GetLinkId for the rationale return mId + 1; @@ -115,6 +114,11 @@ int WorkflowNode::GetDepth() const return mDepth; } +bool WorkflowNode::IsLocked() const +{ + return mLocked; +} + WorkflowNode::Type WorkflowNode::GetType() const { if (IsInputNode()) { @@ -136,9 +140,9 @@ bool WorkflowNode::IsOutputNode() const return mOutputs.size() == 0; } -void WorkflowNode::ConnectInput(uint32_t pinId, WorkflowNode& output, uint32_t outputNodeId) +void WorkflowNode::ConnectInput(uint32_t pinId, WorkflowNode& srcNode, uint32_t srcPinId) { - mWorkflow->Connect(*this, pinId, output, outputNodeId); + mWorkflow->Connect(*this, pinId, srcNode, srcPinId); } void WorkflowNode::DisconnectInput(uint32_t pinId) @@ -146,19 +150,25 @@ void WorkflowNode::DisconnectInput(uint32_t pinId) mWorkflow->DisconnectByDestination(*this, pinId); } +void WorkflowNode::DrawInputPinDebugInfo(uint32_t pinId) const +{ + ImGui::Text("Node ID: %d", mId); + ImGui::Text("Pin ID: (input) %d", pinId); +} + const WorkflowNode::InputPin& WorkflowNode::GetInputPin(uint32_t pinId) const { return mInputs[pinId]; } -size_t WorkflowNode::GetInputPinUniqueId(uint32_t pinId) const +ImNodes::PinId WorkflowNode::GetInputPinUniqueId(uint32_t pinId) const { return mWorkflow->FabricateGlobalPinId(*this, pinId, false); } -void WorkflowNode::ConnectOutput(uint32_t pinId, WorkflowNode& input, uint32_t inputNodeId) +void WorkflowNode::ConnectOutput(uint32_t pinId, WorkflowNode& dstNode, uint32_t dstPinId) { - mWorkflow->Connect(input, inputNodeId, *this, pinId); + mWorkflow->Connect(dstNode, dstPinId, *this, pinId); } void WorkflowNode::DisconnectOutput(uint32_t pinId) @@ -166,12 +176,18 @@ void WorkflowNode::DisconnectOutput(uint32_t pinId) mWorkflow->DisconnectBySource(*this, pinId); } +void WorkflowNode::DrawOutputPinDebugInfo(uint32_t pinId) const +{ + ImGui::Text("Node ID: %d", mId); + ImGui::Text("Pin ID: (output) %d", pinId); +} + const WorkflowNode::OutputPin& WorkflowNode::GetOutputPin(uint32_t pinId) const { return mOutputs[pinId]; } -size_t WorkflowNode::GetOutputPinUniqueId(uint32_t pinId) const +ImNodes::PinId WorkflowNode::GetOutputPinUniqueId(uint32_t pinId) const { return mWorkflow->FabricateGlobalPinId(*this, pinId, true); } @@ -180,12 +196,14 @@ void WorkflowNode::Draw() { for (uint32_t i = 0; i < mInputs.size(); ++i) { auto& pin = mInputs[i]; + auto& typeInfo = BaseValue::QueryInfo(pin.MatchingType); ImNodes::BeginPin(GetInputPinUniqueId(i), ImNodes::PinKind::Input); // TODO ImNodes::EndPin(); } for (uint32_t i = 0; i < mOutputs.size(); ++i) { auto& pin = mOutputs[i]; + auto& typeInfo = BaseValue::QueryInfo(pin.MatchingType); ImNodes::BeginPin(GetOutputPinUniqueId(i), ImNodes::PinKind::Output); // TODO ImNodes::EndPin(); @@ -194,13 +212,11 @@ void WorkflowNode::Draw() void WorkflowNode::DrawDebugInfo() const { - ImGui::BeginTooltip(); ImGui::Text("Node kind: %s", FormatKind(mKind)); ImGui::Text("Node type: %s", FormatType(GetType())); ImGui::Text("Node ID: %u", mId); ImGui::Text("Depth: %d", mDepth); DrawExtraDebugInfo(); - ImGui::EndTooltip(); } void WorkflowNode::ReadFrom(std::istream& stream) @@ -338,17 +354,27 @@ WorkflowConnection* Workflow::GetConnectionById(uint32_t id) return &mConnections[id]; } +WorkflowConnection* Workflow::GetConnectionByLinkId(ImNodes::LinkId id) +{ + return &mConnections[(uint32_t)(size_t)id - 1]; +} + WorkflowNode* Workflow::GetNodeById(uint32_t id) { return mNodes[id].get(); } +WorkflowNode* Workflow::GetNodeByNodeId(ImNodes::NodeId id) +{ + return mNodes[(uint32_t)(size_t)id - 1].get(); +} + BaseValue* Workflow::GetConstantById(uint32_t id) { return mConstants[id].get(); } -Workflow::GlobalPinId Workflow::DisassembleGlobalPinId(size_t id) +Workflow::GlobalPinId Workflow::DisassembleGlobalPinId(ImNodes::PinId pinId) { // imgui-node-editor requires all pins to have a global, unique id // but in our model the pin are typed (input vs output) and associated with a node: there is no built-in global id @@ -362,6 +388,7 @@ Workflow::GlobalPinId Workflow::DisassembleGlobalPinId(size_t id) // 1 is added to pin id to prevent the 0th node's 0th input pin resulting in a 0 global pin id // (this is problematic because imgui-node-editor use 0 to represent null) + auto id = static_cast<uint64_t>(pinId); GlobalPinId result; result.Node = mNodes[id >> 32].get(); @@ -371,7 +398,7 @@ Workflow::GlobalPinId Workflow::DisassembleGlobalPinId(size_t id) return result; } -size_t Workflow::FabricateGlobalPinId(const WorkflowNode& node, uint32_t pinId, bool isOutput) const +ImNodes::PinId Workflow::FabricateGlobalPinId(const WorkflowNode& node, uint32_t pinId, bool isOutput) const { // See this->DisassembleGlobalPinId for format details and rationale diff --git a/core/src/UI/Localization.hpp b/core/src/UI/Localization.hpp index 65fcdfd..b421d24 100644 --- a/core/src/UI/Localization.hpp +++ b/core/src/UI/Localization.hpp @@ -19,6 +19,7 @@ public: BasicTranslation Add{ "Generic.Add"sv }; BasicTranslation Edit{ "Generic.Edit"sv }; BasicTranslation Delete{ "Generic.Delete"sv }; + BasicTranslation Disconnect{ "Generic.Disconnect"sv }; BasicTranslation Close{ "Generic.Close"sv }; BasicTranslation DialogConfirm{ "Generic.Dialog.Confirm"sv }; BasicTranslation DialogCancel{ "Generic.Dialog.Cancel"sv }; diff --git a/core/src/UI/UI_Workflows.cpp b/core/src/UI/UI_Workflows.cpp index 7aefeab..1a4294f 100644 --- a/core/src/UI/UI_Workflows.cpp +++ b/core/src/UI/UI_Workflows.cpp @@ -138,6 +138,10 @@ private: ImNodes::EditorContext* mContext; WorkflowDatabase mWorkflowDatabase; + ImNodes::NodeId mContextMenuNodeId = 0; + ImNodes::PinId mContextMenuPinId = 0; + ImNodes::LinkId mContextMenuLinkId = 0; + public: WorkflowUI() { @@ -152,9 +156,12 @@ public: void Draw() { + auto ls = LocaleStrings::Instance.get(); + ImNodes::SetCurrentEditor(mContext); ImNodes::Begin(""); + // Defer creation of tooltip because within the node editor, cursor positioning is going to be off const char* tooltipMessage = nullptr; for (auto& node : mWorkflow->GetNodes()) { @@ -180,8 +187,8 @@ public: goto createError; } - auto [srcNode, srcPinId, srcIsOutput] = mWorkflow->DisassembleGlobalPinId((size_t)src); - auto [dstNode, dstPinId, dstIsOutput] = mWorkflow->DisassembleGlobalPinId((size_t)dst); + auto [srcNode, srcPinId, srcIsOutput] = mWorkflow->DisassembleGlobalPinId(src); + auto [dstNode, dstPinId, dstIsOutput] = mWorkflow->DisassembleGlobalPinId(dst); if (srcNode == dstNode) { ImNodes::RejectNewItem(); @@ -208,7 +215,7 @@ public: ImNodes::PinId newNodePin = 0; if (ImNodes::QueryNewNode(&newNodePin)) { - auto [node, pinId, isOutput] = mWorkflow->DisassembleGlobalPinId((size_t)newNodePin); + auto [node, pinId, isOutput] = mWorkflow->DisassembleGlobalPinId(newNodePin); if ((isOutput && node->GetOutputPin(pinId).IsConnected()) || (!isOutput && node->GetInputPin(pinId).IsConnected())) @@ -230,10 +237,22 @@ public: if (ImNodes::BeginDelete()) { ImNodes::LinkId deletedLinkId; if (ImNodes::QueryDeletedLink(&deletedLinkId)) { - // Link id is the same as our connection id - auto& conn = *mWorkflow->GetConnectionById((size_t)deletedLinkId); + auto& conn = *mWorkflow->GetConnectionByLinkId(deletedLinkId); + mWorkflow->RemoveConnection(conn.Id); + } + + ImNodes::NodeId deletedNodeId; + if (ImNodes::QueryDeletedNode(&deletedNodeId)) { + auto node = mWorkflow->GetNodeByNodeId(deletedNodeId); + if (!node) { + ImNodes::RejectDeletedItem(); + goto deleteError; + } - // TODO + if (node->IsLocked()) { + ImNodes::RejectDeletedItem(); + goto deleteError; + } } } deleteError: @@ -241,23 +260,77 @@ public: // Popups ImNodes::Suspend(); + if (ImNodes::ShowNodeContextMenu(&mContextMenuNodeId)) { + ImGui::OpenPopup("Node Context Menu"); + } else if (ImNodes::ShowPinContextMenu(&mContextMenuPinId)) { + ImGui::OpenPopup("Pin Context Menu"); + } else if (ImNodes::ShowLinkContextMenu(&mContextMenuLinkId)) { + ImGui::OpenPopup("Link Context Menu"); + } + if (ImGui::BeginPopup("Node Context Menu")) { - // TODO + auto& node = *mWorkflow->GetNodeByNodeId(mContextMenuNodeId); + node.DrawDebugInfo(); + + if (ImGui::MenuItem(ls->Delete.Get())) { + ImNodes::DeleteNode(mContextMenuNodeId); + } + ImGui::EndPopup(); } + + if (ImGui::BeginPopup("Pin Context Menu")) { + auto [node, pinId, isOutput] = mWorkflow->DisassembleGlobalPinId(mContextMenuPinId); + if (isOutput) { + node->DrawOutputPinDebugInfo(pinId); + } else { + node->DrawInputPinDebugInfo(pinId); + } + + if (ImGui::MenuItem(ls->Disconnect.Get())) { + if (isOutput) { + auto& pin = node->GetOutputPin(pinId); + if (pin.IsConnected()) { + auto linkId = mWorkflow->GetConnectionById(pin.Connection)->GetLinkId(); + ImNodes::DeleteLink(linkId); + } + } else { + auto& pin = node->GetInputPin(pinId); + if (pin.IsConstantConnection()) { + // TODO + } else if (pin.IsConnected()) { + auto linkId = mWorkflow->GetConnectionById(pin.Connection)->GetLinkId(); + ImNodes::DeleteLink(linkId); + } + } + } + + ImGui::EndPopup(); + } + if (ImGui::BeginPopup("Link Context Menu")) { - // TODO + auto& conn = *mWorkflow->GetConnectionByLinkId(mContextMenuLinkId); + conn.DrawDebugInfo(); + + if (ImGui::MenuItem(ls->Delete.Get())) { + ImNodes::DeleteLink(mContextMenuLinkId); + } + ImGui::EndPopup(); } + if (ImGui::BeginPopup("Create Node")) { auto DisplayCandidatesCategory = [&](const char* name, std::span<const WorkflowDatabase::Candidate> candidates) { - ImGui::TreeNode(name); - for (auto& candidate : candidates) { - if (ImGui::MenuItem(candidate.Name.c_str())) { - // TODO + if (ImGui::BeginMenu(name)) { + for (auto& candidate : candidates) { + if (ImGui::MenuItem(candidate.Name.c_str())) { + // Create node + auto uptr = candidate.Constructor(); + mWorkflow->AddNode(std::move(uptr)); + } } + ImGui::EndMenu(); } - ImGui::TreePop(); }; DisplayCandidatesCategory("Numeric nodes", mWorkflowDatabase.GetNumericNodes()); @@ -268,6 +341,7 @@ public: DisplayCandidatesCategory("Output nodes", mWorkflowDatabase.GetOutputNodes()); ImGui::EndPopup(); } + if (tooltipMessage) { ImGui::BeginTooltip(); ImGui::TextUnformatted(tooltipMessage); diff --git a/core/src/Utils/Color.hpp b/core/src/Utils/Color.hpp index 26cfdd4..5f4691f 100644 --- a/core/src/Utils/Color.hpp +++ b/core/src/Utils/Color.hpp @@ -164,8 +164,8 @@ constexpr HsvColor RgbaColor::ToHsv() const noexcept float fb = GetNormalizedGreen();
float fa = GetNormalizedAlpha();
- auto p = fg < fb ? ImVec4(fb, fg, -1, 2.0f / 3.0f) : ImVec4(fg, fb, 0, -1.0f / 3.0f);
- auto q = fr < p.x ? ImVec4(p.x, p.y, p.w, fr) : ImVec4(fr, p.y, p.z, p.x);
+ auto p = fg < fb ? Vec4f{ fb, fg, -1, 2.0f / 3.0f } : Vec4f{ fg, fb, 0, -1.0f / 3.0f };
+ auto q = fr < p.x ? Vec4f{ p.x, p.y, p.w, fr } : Vec4f{ fr, p.y, p.z, p.x };
float c = q.x - std::min(q.w, q.y);
float h = std::abs((q.w - q.y) / (6 * c + std::numeric_limits<float>::epsilon()) + q.z);
|