diff options
author | rtk0c <[email protected]> | 2021-05-12 13:23:56 -0700 |
---|---|---|
committer | rtk0c <[email protected]> | 2021-05-12 13:34:43 -0700 |
commit | 765df313e065f8401319c68ba70cd41b0bc34c9d (patch) | |
tree | 44e0c781ed9f5ea0f98ac906e96c677d04befa27 | |
parent | 6ec8cc216282396ece535941ea6ca4a63d924e8f (diff) |
Start to work on actually rendering the node graph
-rw-r--r-- | .clang-format | 99 | ||||
-rw-r--r-- | core/src/Model/Workflow/Value.hpp | 12 | ||||
-rw-r--r-- | core/src/Model/Workflow/Value_RTTI.cpp | 29 | ||||
-rw-r--r-- | core/src/Model/Workflow/Workflow.hpp | 100 | ||||
-rw-r--r-- | core/src/Model/Workflow/Workflow_Main.cpp | 193 | ||||
-rw-r--r-- | core/src/UI/UI.hpp | 13 | ||||
-rw-r--r-- | core/src/UI/UI_Utils.cpp | 219 | ||||
-rw-r--r-- | core/src/UI/UI_Workflows.cpp | 122 | ||||
-rw-r--r-- | core/src/UI/fwd.hpp | 5 | ||||
-rw-r--r-- | core/src/Utils/Color.hpp | 191 | ||||
-rw-r--r-- | core/src/Utils/Enum.hpp | 140 | ||||
-rw-r--r-- | core/src/Utils/Macros.hpp | 2 | ||||
-rw-r--r-- | core/src/Utils/Vector.hpp | 63 | ||||
-rw-r--r-- | core/src/Utils/fwd.hpp | 14 |
14 files changed, 844 insertions, 358 deletions
diff --git a/.clang-format b/.clang-format index c7e0c26..9f015c9 100644 --- a/.clang-format +++ b/.clang-format @@ -1,26 +1,13 @@ -# Commented out parameters are those with the same value as base LLVM style -# We can uncomment them if we want to change their value, or enforce the -# chosen value in case the base style changes (last sync: Clang 6.0.1). ---- -### General config, applies to all languages ### -BasedOnStyle: LLVM +BasedOnStyle: LLVM AccessModifierOffset: -4 AlignAfterOpenBracket: DontAlign -# AlignConsecutiveAssignments: false -# AlignConsecutiveDeclarations: false -# AlignEscapedNewlines: Right AlignOperands: false AlignTrailingComments: false AllowAllParametersOfDeclarationOnNextLine: true AllowAllArgumentsOnNextLine: true -# AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: true -AllowShortFunctionsOnASingleLine: Inline +AllowShortFunctionsOnASingleLine: All AllowShortIfStatementsOnASingleLine: true -# AllowShortLoopsOnASingleLine: false -# AlwaysBreakAfterDefinitionReturnType: None -# AlwaysBreakAfterReturnType: None -# AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: MultiLine BinPackArguments: false BinPackParameters: false @@ -40,87 +27,25 @@ BraceWrapping: SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true -# BreakBeforeBinaryOperators: None BreakBeforeBraces: Custom -# BreakBeforeInheritanceComma: false BreakBeforeTernaryOperators: true BreakConstructorInitializers: BeforeComma -# BreakStringLiterals: true -ColumnLimit: 0 -# CommentPragmas: '^ IWYU pragma:' -# CompactNamespaces: false +ColumnLimit: 0 ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: false -# DerivePointerAlignment: false -# DisableFormat: false -# ExperimentalAutoDetectBinPacking: false -# FixNamespaceComments: true -# ForEachMacros: -# - foreach -# - Q_FOREACH -# - BOOST_FOREACH -# IncludeBlocks: Preserve IncludeCategories: - - Regex: '".*"' - Priority: 1 - - Regex: '^<.*\.h>' - Priority: 2 - - Regex: '^<.*' - Priority: 3 -# IncludeIsMainRegex: '(Test)?$' + - Regex: '".*"' + Priority: 1 + - Regex: '^<.*\.h>' + Priority: 2 + - Regex: '^<.*' + Priority: 3 IndentCaseLabels: true IndentPPDirectives: AfterHash -IndentWidth: 4 -# IndentWrappedFunctionNames: false -# JavaScriptQuotes: Leave -# JavaScriptWrapImports: true -# KeepEmptyLinesAtTheStartOfBlocks: true -# MacroBlockBegin: '' -# MacroBlockEnd: '' -# MaxEmptyLinesToKeep: 1 +IndentWidth: 4 NamespaceIndentation: Inner -# PenaltyBreakAssignment: 2 -# PenaltyBreakBeforeFirstCallParameter: 19 -# PenaltyBreakComment: 300 -# PenaltyBreakFirstLessLess: 120 -# PenaltyBreakString: 1000 -# PenaltyExcessCharacter: 1000000 -# PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Left -# RawStringFormats: -# - Delimiter: pb -# Language: TextProto -# BasedOnStyle: google -# ReflowComments: true -# SortIncludes: true -# SortUsingDeclarations: true -# SpaceAfterCStyleCast: false -# SpaceAfterTemplateKeyword: true -# SpaceBeforeAssignmentOperators: true -# SpaceBeforeParens: ControlStatements -# SpaceInEmptyParentheses: false -# SpacesBeforeTrailingComments: 1 -# SpacesInAngles: false -# SpacesInContainerLiterals: true -# SpacesInCStyleCastParentheses: false -# SpacesInParentheses: false -# SpacesInSquareBrackets: false -TabWidth: 4 -UseTab: Always ---- -### C++ specific config ### -Language: Cpp -Standard: Cpp11 ---- -### ObjC specific config ### -Language: ObjC -ObjCBlockIndentWidth: 4 -# ObjCSpaceAfterProperty: false -# ObjCSpaceBeforeProtocolList: true ---- -### Java specific config ### -Language: Java -# BreakAfterJavaFieldAnnotations: false -... +TabWidth: 4 +UseTab: Always diff --git a/core/src/Model/Workflow/Value.hpp b/core/src/Model/Workflow/Value.hpp index 6cd42f3..8b3e63a 100644 --- a/core/src/Model/Workflow/Value.hpp +++ b/core/src/Model/Workflow/Value.hpp @@ -1,5 +1,8 @@ #pragma once +#include "cplt_fwd.hpp" +#include "Utils/Color.hpp" + #include <iosfwd> #include <memory> @@ -17,11 +20,18 @@ public: KindCount = InvalidKind, }; + struct KindInfo + { + ImGui::IconType PinIcon; + RgbaColor PinColor; + }; + private: Kind mKind; public: - static const char* FormatKind(Kind kind); + static const KindInfo& QueryInfo(Kind kind); + static const char* Format(Kind kind); static std::unique_ptr<BaseValue> CreateByKind(Kind kind); BaseValue(Kind kind); diff --git a/core/src/Model/Workflow/Value_RTTI.cpp b/core/src/Model/Workflow/Value_RTTI.cpp index a0c3213..5e24ed7 100644 --- a/core/src/Model/Workflow/Value_RTTI.cpp +++ b/core/src/Model/Workflow/Value_RTTI.cpp @@ -1,9 +1,34 @@ #include "Value.hpp" #include "Model/Workflow/Values/BasicValues.hpp" -#include "Utils/Macros.hpp" +#include "UI/UI.hpp" -const char* BaseValue::FormatKind(Kind kind) +constexpr BaseValue::KindInfo kNumericInfo{ + .PinIcon = ImGui::IconType::Circle, + .PinColor = RgbaColor(147, 226, 74), +}; + +constexpr BaseValue::KindInfo kTextInfo{ + .PinIcon = ImGui::IconType::Circle, + .PinColor = RgbaColor(124, 21, 153), +}; + +constexpr BaseValue::KindInfo kDateTimeInfo{ + .PinIcon = ImGui::IconType::Diamond, + .PinColor = RgbaColor(147, 226, 74), +}; + +const BaseValue::KindInfo& BaseValue::QueryInfo(BaseValue::Kind kind) +{ + switch (kind) { + case KD_Numeric: return kNumericInfo; + case KD_Text: break; + case KD_DateTime: break; + case InvalidKind: break; + } +} + +const char* BaseValue::Format(Kind kind) { switch (kind) { case KD_Numeric: return "Numeric"; diff --git a/core/src/Model/Workflow/Workflow.hpp b/core/src/Model/Workflow/Workflow.hpp index 7bcd349..79c2b2f 100644 --- a/core/src/Model/Workflow/Workflow.hpp +++ b/core/src/Model/Workflow/Workflow.hpp @@ -17,20 +17,22 @@ class WorkflowConnection { public: - static constexpr auto kInvalidId = std::numeric_limits<size_t>::max(); + static constexpr auto kInvalidId = std::numeric_limits<uint32_t>::max(); - /// Used for `LinkId` when interfacing with imgui node editor. Runtime only (not saved to disk and generated when loading). - size_t UniqueId; - size_t SourceNode; - size_t DestinationNode; - int SourcePin; - int DestinationPin; + uint32_t Id; + uint32_t SourceNode; + uint32_t SourcePin; + uint32_t DestinationNode; + uint32_t DestinationPin; public: WorkflowConnection(); 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; + void DrawDebugInfo() const; void ReadFrom(std::istream& stream); void WriteTo(std::ostream& stream); @@ -39,7 +41,8 @@ public: class WorkflowNode { public: - static constexpr auto kInvalidId = std::numeric_limits<size_t>::max(); + static constexpr auto kInvalidId = std::numeric_limits<uint32_t>::max(); + static constexpr auto kInvalidPinId = std::numeric_limits<uint32_t>::max(); enum Type { @@ -64,16 +67,13 @@ public: KindCount = InvalidKind, }; -protected: struct InputPin { - /// Equivalent of `PinId` when interfacing with imgui node editor. Runtime only (not saved to disk and generated when loading). - size_t UniqueId; - size_t Connection = WorkflowConnection::kInvalidId; + uint32_t Connection = WorkflowConnection::kInvalidId; BaseValue::Kind MatchingType = BaseValue::InvalidKind; bool ConnectionToConst = false; - /// A constant connection connects from a user-specified constant value, feeding to a valid \c Destination and \c DestinationPin (i.e. input pins). + /// A constant connection connects from a user-specified constant value, feeding to a valid \c DestinationNode and \c DestinationPin (i.e. input pins). bool IsConstantConnection() const; bool IsConnected() const; BaseValue::Kind GetMatchingType() const; @@ -81,26 +81,22 @@ protected: struct OutputPin { - /// Equivalent of `PinId` when interfacing with imgui node editor. Runtime only (not saved to disk and generated when loading). - size_t UniqueId; - size_t Connection = WorkflowConnection::kInvalidId; + uint32_t Connection = WorkflowConnection::kInvalidId; BaseValue::Kind MatchingType = BaseValue::InvalidKind; - bool AllowsMultipleConnections = false; bool IsConnected() const; BaseValue::Kind GetMatchingType() const; }; +protected: friend class Workflow; friend class WorkflowEvaluationContext; Workflow* mWorkflow; - /// Equivalent of `NodeId` when interfacing with imgui node editor. Runtime only (not saved to disk and generated when loading). - size_t mUniqueId; - size_t mId; std::vector<InputPin> mInputs; std::vector<OutputPin> mOutputs; Vec2i mPosition; + uint32_t mId; Kind mKind; int mDepth; @@ -120,7 +116,9 @@ public: void SetPosition(const Vec2i& position); Vec2i GetPosition() const; - size_t GetId() const; + 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; Kind GetKind() const; int GetDepth() const; @@ -128,13 +126,15 @@ public: bool IsInputNode() const; bool IsOutputNode() const; - void ConnectInput(int nodeId, WorkflowNode& output, int outputNodeId); - void DisconnectInput(int nodeId); - bool IsInputConnected(int nodeId) const; + void ConnectInput(uint32_t pinId, WorkflowNode& output, uint32_t outputNodeId); + void DisconnectInput(uint32_t pinId); + const InputPin& GetInputPin(uint32_t pinId)const ; + size_t GetInputPinUniqueId(uint32_t pinId) const; - void ConnectOutput(int nodeId, WorkflowNode& input, int inputNodeId); - void DisconnectOutput(int nodeId); - bool IsOutputConnected(int nodeId) const; + void ConnectOutput(uint32_t pinId, WorkflowNode& input, uint32_t inputNodeId); + void DisconnectOutput(uint32_t pinId); + const OutputPin& GetOutputPin(uint32_t pinId)const ; + size_t GetOutputPinUniqueId(uint32_t pinId) const; virtual void Evaluate(WorkflowEvaluationContext& ctx) = 0; @@ -157,20 +157,20 @@ protected: /* For \c Workflow to invoke, override by implementations */ - void OnAttach(Workflow& workflow, size_t newId) {} - void OnDetach() {} + void OnAttach(Workflow& workflow, uint32_t newId); + void OnDetach(); }; class Workflow { private: + friend class WorkflowNode; friend class WorkflowEvaluationContext; - size_t mUniqueId = 0; std::vector<WorkflowConnection> mConnections; std::vector<std::unique_ptr<WorkflowNode>> mNodes; std::vector<std::unique_ptr<BaseValue>> mConstants; - std::vector<std::vector<size_t>> mDepthGroups; + std::vector<std::vector<uint32_t>> mDepthGroups; int mConnectionCount; int mNodeCount; int mConstantCount; @@ -186,23 +186,36 @@ public: const std::vector<std::unique_ptr<BaseValue>>& GetConstants() const; std::vector<std::unique_ptr<BaseValue>>& GetConstants(); - WorkflowConnection* GetConnectionById(size_t id); - WorkflowNode* GetStepById(size_t id); - BaseValue* GetConstantById(size_t id); + WorkflowConnection* GetConnectionById(uint32_t id); + WorkflowNode* GetNodeById(uint32_t id); + BaseValue* GetConstantById(uint32_t id); + + struct GlobalPinId + { + WorkflowNode* Node; + uint32_t PinId; + /// true => input pin + /// false => output pin + bool IsOutput; + }; + + /// `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; - const std::vector<std::vector<size_t>>& GetDepthGroups() const; + const std::vector<std::vector<uint32_t>>& GetDepthGroups() const; bool DoesDepthNeedsUpdate() const; /* Graph mutation */ - void AddStep(std::unique_ptr<WorkflowNode> step); - void RemoveStep(size_t id); + void AddNode(std::unique_ptr<WorkflowNode> step); + void RemoveNode(uint32_t id); - void RemoveConnection(size_t id); + void RemoveConnection(uint32_t id); - bool Connect(WorkflowNode& sourceNode, int sourcePin, WorkflowNode& destinationNode, int destinationPin); - bool DisconnectBySource(WorkflowNode& sourceNode, int sourcePin); - bool DisconnectByDestination(WorkflowNode& destinationNode, int destinationPin); + bool Connect(WorkflowNode& sourceNode, uint32_t sourcePin, WorkflowNode& destinationNode, uint32_t destinationPin); + bool DisconnectBySource(WorkflowNode& sourceNode, uint32_t sourcePin); + bool DisconnectByDestination(WorkflowNode& destinationNode, uint32_t destinationPin); /* Graph rebuild */ @@ -218,12 +231,12 @@ public: struct UnsatisfiedDependencies { - std::vector<size_t> UnsatisfiedNodes; + std::vector<uint32_t> UnsatisfiedNodes; }; struct UnreachableNodes { - std::vector<size_t> UnreachableNodes; + std::vector<uint32_t> UnreachableNodes; }; using T = std::variant< @@ -246,7 +259,6 @@ public: void WriteTo(std::ostream& stream); private: - size_t AllocUniqueId(); std::pair<WorkflowConnection&, size_t> AllocWorkflowConnection(); std::pair<std::unique_ptr<WorkflowNode>&, size_t> AllocWorkflowStep(); }; diff --git a/core/src/Model/Workflow/Workflow_Main.cpp b/core/src/Model/Workflow/Workflow_Main.cpp index cfa7cea..678b4b8 100644 --- a/core/src/Model/Workflow/Workflow_Main.cpp +++ b/core/src/Model/Workflow/Workflow_Main.cpp @@ -3,7 +3,6 @@ #include <imgui.h> #include <imgui_node_editor.h> #include <tsl/robin_set.h> -#include <algorithm> #include <cassert> #include <iostream> #include <queue> @@ -12,38 +11,46 @@ namespace ImNodes = ax::NodeEditor; WorkflowConnection::WorkflowConnection() - : SourceNode{ WorkflowNode::kInvalidId } + : Id{ 0 } + , SourceNode{ WorkflowNode::kInvalidId } , DestinationNode{ WorkflowNode::kInvalidId } - , SourcePin{ -1 } - , DestinationPin{ -1 } + , SourcePin{ WorkflowNode::kInvalidPinId } + , DestinationPin{ WorkflowNode::kInvalidPinId } { } bool WorkflowConnection::IsValid() const { - return SourceNode != WorkflowNode::kInvalidId; + return Id != 0; +} + +size_t 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 + return Id + 1; } void WorkflowConnection::DrawDebugInfo() const { ImGui::BeginTooltip(); ImGui::Text("Source (node with output pin):"); - ImGui::Text("{ Node = %llu, Pin = %d }", SourceNode, SourcePin); + ImGui::Text("{ Node = %u, Pin = %u }", SourceNode, SourcePin); ImGui::Text("Destination (node with input pin):"); - ImGui::Text("{ Node = %llu, Pin = %d }", DestinationNode, DestinationPin); + ImGui::Text("{ Node = %u, Pin = %u }", DestinationNode, DestinationPin); ImGui::EndTooltip(); } void WorkflowConnection::ReadFrom(std::istream& stream) { - stream >> SourceNode >> DestinationNode; - stream >> SourcePin >> DestinationPin; + stream >> SourceNode >> SourcePin; + stream >> DestinationNode >> DestinationPin; } void WorkflowConnection::WriteTo(std::ostream& stream) { - stream << SourceNode << DestinationNode; - stream << SourcePin << DestinationPin; + stream << SourceNode << SourcePin; + stream << DestinationNode << DestinationPin; } bool WorkflowNode::InputPin::IsConstantConnection() const @@ -87,11 +94,17 @@ void WorkflowNode::SetPosition(const Vec2i& position) mPosition = position; } -size_t WorkflowNode::GetId() const +uint32_t WorkflowNode::GetId() const { return mId; } +size_t WorkflowNode::GetNodeId() const +{ + // See WorkflowConnection::GetLinkId for the rationale + return mId + 1; +} + WorkflowNode::Kind WorkflowNode::GetKind() const { return mKind; @@ -123,47 +136,57 @@ bool WorkflowNode::IsOutputNode() const return mOutputs.size() == 0; } -void WorkflowNode::ConnectInput(int nodeId, WorkflowNode& output, int outputNodeId) +void WorkflowNode::ConnectInput(uint32_t pinId, WorkflowNode& output, uint32_t outputNodeId) +{ + mWorkflow->Connect(*this, pinId, output, outputNodeId); +} + +void WorkflowNode::DisconnectInput(uint32_t pinId) +{ + mWorkflow->DisconnectByDestination(*this, pinId); +} + +const WorkflowNode::InputPin& WorkflowNode::GetInputPin(uint32_t pinId) const { - mWorkflow->Connect(*this, nodeId, output, outputNodeId); + return mInputs[pinId]; } -void WorkflowNode::DisconnectInput(int nodeId) +size_t WorkflowNode::GetInputPinUniqueId(uint32_t pinId) const { - mWorkflow->DisconnectByDestination(*this, nodeId); + return mWorkflow->FabricateGlobalPinId(*this, pinId, false); } -bool WorkflowNode::IsInputConnected(int nodeId) const +void WorkflowNode::ConnectOutput(uint32_t pinId, WorkflowNode& input, uint32_t inputNodeId) { - return mInputs[nodeId].IsConnected(); + mWorkflow->Connect(input, inputNodeId, *this, pinId); } -void WorkflowNode::ConnectOutput(int nodeId, WorkflowNode& input, int inputNodeId) +void WorkflowNode::DisconnectOutput(uint32_t pinId) { - mWorkflow->Connect(input, inputNodeId, *this, nodeId); + mWorkflow->DisconnectBySource(*this, pinId); } -void WorkflowNode::DisconnectOutput(int nodeId) +const WorkflowNode::OutputPin& WorkflowNode::GetOutputPin(uint32_t pinId) const { - mWorkflow->DisconnectBySource(*this, nodeId); + return mOutputs[pinId]; } -bool WorkflowNode::IsOutputConnected(int nodeId) const +size_t WorkflowNode::GetOutputPinUniqueId(uint32_t pinId) const { - return mOutputs[nodeId].IsConnected(); + return mWorkflow->FabricateGlobalPinId(*this, pinId, true); } void WorkflowNode::Draw() { - for (size_t i = 0; i < mInputs.size(); ++i) { + for (uint32_t i = 0; i < mInputs.size(); ++i) { auto& pin = mInputs[i]; - ImNodes::BeginPin(i, ImNodes::PinKind::Input); + ImNodes::BeginPin(GetInputPinUniqueId(i), ImNodes::PinKind::Input); // TODO ImNodes::EndPin(); } - for (size_t i = 0; i < mOutputs.size(); ++i) { + for (uint32_t i = 0; i < mOutputs.size(); ++i) { auto& pin = mOutputs[i]; - ImNodes::BeginPin(i + mInputs.size(), ImNodes::PinKind::Output); + ImNodes::BeginPin(GetOutputPinUniqueId(i), ImNodes::PinKind::Output); // TODO ImNodes::EndPin(); } @@ -174,7 +197,7 @@ void WorkflowNode::DrawDebugInfo() const ImGui::BeginTooltip(); ImGui::Text("Node kind: %s", FormatKind(mKind)); ImGui::Text("Node type: %s", FormatType(GetType())); - ImGui::Text("Node ID: %llu", mId); + ImGui::Text("Node ID: %u", mId); ImGui::Text("Depth: %d", mDepth); DrawExtraDebugInfo(); ImGui::EndTooltip(); @@ -272,6 +295,14 @@ void WorkflowNode::SwapOutputPin(int a, int b) std::swap(pinA, pinB); } +void WorkflowNode::OnAttach(Workflow& workflow, uint32_t newId) +{ +} + +void WorkflowNode::OnDetach() +{ +} + const std::vector<WorkflowConnection>& Workflow::GetConnections() const { return mConnections; @@ -302,22 +333,57 @@ std::vector<std::unique_ptr<BaseValue>>& Workflow::GetConstants() return mConstants; } -WorkflowConnection* Workflow::GetConnectionById(size_t id) +WorkflowConnection* Workflow::GetConnectionById(uint32_t id) { return &mConnections[id]; } -WorkflowNode* Workflow::GetStepById(size_t id) +WorkflowNode* Workflow::GetNodeById(uint32_t id) { return mNodes[id].get(); } -BaseValue* Workflow::GetConstantById(size_t id) +BaseValue* Workflow::GetConstantById(uint32_t id) { return mConstants[id].get(); } -const std::vector<std::vector<size_t>>& Workflow::GetDepthGroups() const +Workflow::GlobalPinId Workflow::DisassembleGlobalPinId(size_t id) +{ + // 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 + // Therefore we encode one ourselves + + // Global pin id format + // nnnnnnnn nnnnnnnn nnnnnnnn nnnnnnnn Tppppppp pppppppp pppppppp pppppppp + // <------- (32 bits) node id -------> ^<------ (31 bits) pin id --------> + // | (1 bit) input (false) vs output (true) + + // 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) + + GlobalPinId result; + + result.Node = mNodes[id >> 32].get(); + result.PinId = (uint32_t)(id & 0x000000001FFFFFFF) - 1; + result.IsOutput = id >> 31; + + return result; +} + +size_t Workflow::FabricateGlobalPinId(const WorkflowNode& node, uint32_t pinId, bool isOutput) const +{ + // See this->DisassembleGlobalPinId for format details and rationale + + uint64_t id = 0; + id |= ((uint64_t)node.GetId() << 32); + id |= (isOutput << 31); + id |= ((pinId + 1) & 0x1FFFFFFF); + + return id; +} + +const std::vector<std::vector<uint32_t>>& Workflow::GetDepthGroups() const { return mDepthGroups; } @@ -327,7 +393,7 @@ bool Workflow::DoesDepthNeedsUpdate() const return mDepthsDirty; } -void Workflow::AddStep(std::unique_ptr<WorkflowNode> step) +void Workflow::AddNode(std::unique_ptr<WorkflowNode> step) { auto [storage, id] = AllocWorkflowStep(); storage = std::move(step); @@ -336,7 +402,7 @@ void Workflow::AddStep(std::unique_ptr<WorkflowNode> step) storage->mId = id; } -void Workflow::RemoveStep(size_t id) +void Workflow::RemoveNode(uint32_t id) { auto& step = mNodes[id]; if (step == nullptr) return; @@ -346,7 +412,7 @@ void Workflow::RemoveStep(size_t id) step->mId = WorkflowNode::kInvalidId; } -void Workflow::RemoveConnection(size_t id) +void Workflow::RemoveConnection(uint32_t id) { auto& conn = mConnections[id]; if (!conn.IsValid()) return; @@ -358,7 +424,7 @@ void Workflow::RemoveConnection(size_t id) mDepthsDirty = true; } -bool Workflow::Connect(WorkflowNode& sourceNode, int sourcePin, WorkflowNode& destinationNode, int destinationPin) +bool Workflow::Connect(WorkflowNode& sourceNode, uint32_t sourcePin, WorkflowNode& destinationNode, uint32_t destinationPin) { auto& src = sourceNode.mOutputs[sourcePin]; auto& dst = destinationNode.mInputs[destinationPin]; @@ -385,7 +451,7 @@ bool Workflow::Connect(WorkflowNode& sourceNode, int sourcePin, WorkflowNode& de return true; } -bool Workflow::DisconnectBySource(WorkflowNode& sourceNode, int sourcePin) +bool Workflow::DisconnectBySource(WorkflowNode& sourceNode, uint32_t sourcePin) { auto& sn = sourceNode.mOutputs[sourcePin]; if (!sn.IsConnected()) return false; @@ -401,7 +467,7 @@ bool Workflow::DisconnectBySource(WorkflowNode& sourceNode, int sourcePin) return true; } -bool Workflow::DisconnectByDestination(WorkflowNode& destinationNode, int destinationPin) +bool Workflow::DisconnectByDestination(WorkflowNode& destinationNode, uint32_t destinationPin) { auto& dn = destinationNode.mOutputs[destinationPin]; if (!dn.IsConnected()) return false; @@ -436,7 +502,7 @@ Workflow::GraphUpdateResult::T Workflow::UpdateGraph() }; std::vector<WorkingNode> workingNodes; - std::queue<size_t> q; + std::queue<uint32_t> q; // Check if all dependencies of this node is satisfied auto CheckNodeDependencies = [&](WorkflowNode& node) -> bool { @@ -450,8 +516,8 @@ Workflow::GraphUpdateResult::T Workflow::UpdateGraph() workingNodes.reserve(mNodes.size()); { - std::vector<size_t> unsatisfiedNodes; - for (size_t i = 0; i < mNodes.size(); ++i) { + std::vector<uint32_t> unsatisfiedNodes; + for (uint32_t i = 0; i < mNodes.size(); ++i) { auto& node = mNodes[i]; workingNodes.push_back(WorkingNode{}); @@ -506,8 +572,8 @@ Workflow::GraphUpdateResult::T Workflow::UpdateGraph() if (processedNodes < mNodes.size()) { // There is unreachable nodes, collect them and report to the caller - std::vector<size_t> unreachableNodes; - for (size_t i = 0; i < mNodes.size(); ++i) { + std::vector<uint32_t> unreachableNodes; + for (uint32_t i = 0; i < mNodes.size(); ++i) { auto& wn = workingNodes[i]; auto& n = *mNodes[i]; @@ -526,41 +592,32 @@ Workflow::GraphUpdateResult::T Workflow::UpdateGraph() Workflow::ReadResult Workflow::ReadFrom(std::istream& stream) { auto DeserializeV0 = [&]() { - size_t connectionsStorage, nodesStorage, constantsStorage; - stream >> connectionsStorage >> nodesStorage >> constantsStorage; - uint32_t connectionCount, nodeCount, constantCount; stream >> connectionCount >> nodeCount >> constantCount; + mConnections.reserve(connectionCount); for (uint32_t i = 0; i < connectionCount; ++i) { - size_t idx; - stream >> idx; - - mConnections[idx].ReadFrom(stream); + mConnections.emplace_back().ReadFrom(stream); } + mNodes.reserve(connectionCount); for (uint32_t i = 0; i < nodeCount; ++i) { - size_t idx; - stream >> idx; - uint32_t nKind; stream >> nKind; auto kind = (WorkflowNode::Kind)nKind; - mNodes[idx] = WorkflowNode::CreateByKind(kind); - mNodes[idx]->ReadFrom(stream); + mNodes.push_back(WorkflowNode::CreateByKind(kind)); + mNodes.back()->ReadFrom(stream); } + mConstants.reserve(connectionCount); for (uint32_t i = 0; i < constantCount; ++i) { - size_t idx; - stream >> idx; - uint32_t nKind; stream >> nKind; auto kind = (BaseValue::Kind)nKind; - mConstants[idx] = BaseValue::CreateByKind(kind); - mConstants[idx]->ReadFrom(stream); + mConstants.push_back(BaseValue::CreateByKind(kind)); + mConstants.back()->ReadFrom(stream); } }; @@ -579,6 +636,8 @@ void Workflow::WriteTo(std::ostream& stream) // Version stream << (uint64_t)0; + // TODO id compacting + stream << (size_t)mConnections.size(); stream << (size_t)mNodes.size(); stream << (size_t)mConstants.size(); @@ -621,7 +680,10 @@ std::pair<WorkflowConnection&, size_t> Workflow::AllocWorkflowConnection() } auto id = mConnections.size(); - return { mConnections.emplace_back(WorkflowConnection{}), id }; + auto& conn = mConnections.emplace_back(WorkflowConnection{}); + conn.Id = id; + + return { conn, id }; } std::pair<std::unique_ptr<WorkflowNode>&, size_t> Workflow::AllocWorkflowStep() @@ -634,10 +696,7 @@ std::pair<std::unique_ptr<WorkflowNode>&, size_t> Workflow::AllocWorkflowStep() } auto id = mNodes.size(); - return { mNodes.emplace_back(std::unique_ptr<WorkflowNode>()), id }; -} + auto& node = mNodes.emplace_back(std::unique_ptr<WorkflowNode>()); -size_t Workflow::AllocUniqueId() -{ - return mUniqueId++; + return { node, id }; } diff --git a/core/src/UI/UI.hpp b/core/src/UI/UI.hpp index 9e97118..cce0e00 100644 --- a/core/src/UI/UI.hpp +++ b/core/src/UI/UI.hpp @@ -18,6 +18,19 @@ void ErrorMessage(const char* fmt, ...); void WarningIcon(); void WarningMessage(const char* fmt, ...); +enum class IconType +{ + Flow, + Circle, + Square, + Grid, + RoundSquare, + Diamond, +}; + +void DrawIcon(ImDrawList* drawList, const ImVec2& a, const ImVec2& b, IconType type, bool filled, ImU32 color, ImU32 innerColor); +void Icon(const ImVec2& size, IconType type, bool filled, const ImVec4& color = ImVec4(1, 1, 1, 1), const ImVec4& innerColor = ImVec4(0, 0, 0, 0)); + } // namespace ImGui namespace UI { diff --git a/core/src/UI/UI_Utils.cpp b/core/src/UI/UI_Utils.cpp index 15f88cf..d662d2c 100644 --- a/core/src/UI/UI_Utils.cpp +++ b/core/src/UI/UI_Utils.cpp @@ -2,6 +2,8 @@ #include <IconsFontAwesome.h> #include <imgui.h> + +#define IMGUI_DEFINE_MATH_OPERATORS #include <imgui_internal.h> void ImGui::SetNextWindowSizeRelScreen(float xPercent, float yPercent, ImGuiCond cond) @@ -77,3 +79,220 @@ void ImGui::WarningMessage(const char* fmt, ...) TextV(fmt, args); va_end(args); } + +void ImGui::DrawIcon(ImDrawList* drawList, const ImVec2& a, const ImVec2& b, IconType type, bool filled, ImU32 color, ImU32 innerColor) +{ + // Taken from https://github.com/thedmd/imgui-node-editor/blob/master/examples/blueprints-example/utilities/drawing.cpp + // ax::NodeEditor::DrawIcon + + // Brace style was adapted but no names are changed + + auto rect = ImRect(a, b); + auto rect_x = rect.Min.x; + auto rect_y = rect.Min.y; + auto rect_w = rect.Max.x - rect.Min.x; + auto rect_h = rect.Max.y - rect.Min.y; + auto rect_center_x = (rect.Min.x + rect.Max.x) * 0.5f; + auto rect_center_y = (rect.Min.y + rect.Max.y) * 0.5f; + auto rect_center = ImVec2(rect_center_x, rect_center_y); + const auto outline_scale = rect_w / 24.0f; + const auto extra_segments = static_cast<int>(2 * outline_scale); // for full circle + + if (type == IconType::Flow) { + const auto origin_scale = rect_w / 24.0f; + + const auto offset_x = 1.0f * origin_scale; + const auto offset_y = 0.0f * origin_scale; + const auto margin = 2.0f * origin_scale; + const auto rounding = 0.1f * origin_scale; + const auto tip_round = 0.7f; // percentage of triangle edge (for tip) + //const auto edge_round = 0.7f; // percentage of triangle edge (for corner) + const auto canvas = ImRect( + rect.Min.x + margin + offset_x, + rect.Min.y + margin + offset_y, + rect.Max.x - margin + offset_x, + rect.Max.y - margin + offset_y); + const auto canvas_x = canvas.Min.x; + const auto canvas_y = canvas.Min.y; + const auto canvas_w = canvas.Max.x - canvas.Min.x; + const auto canvas_h = canvas.Max.y - canvas.Min.y; + + const auto left = canvas_x + canvas_w * 0.5f * 0.3f; + const auto right = canvas_x + canvas_w - canvas_w * 0.5f * 0.3f; + const auto top = canvas_y + canvas_h * 0.5f * 0.2f; + const auto bottom = canvas_y + canvas_h - canvas_h * 0.5f * 0.2f; + const auto center_y = (top + bottom) * 0.5f; + //const auto angle = AX_PI * 0.5f * 0.5f * 0.5f; + + const auto tip_top = ImVec2(canvas_x + canvas_w * 0.5f, top); + const auto tip_right = ImVec2(right, center_y); + const auto tip_bottom = ImVec2(canvas_x + canvas_w * 0.5f, bottom); + + drawList->PathLineTo(ImVec2(left, top) + ImVec2(0, rounding)); + drawList->PathBezierCurveTo( + ImVec2(left, top), + ImVec2(left, top), + ImVec2(left, top) + ImVec2(rounding, 0)); + drawList->PathLineTo(tip_top); + drawList->PathLineTo(tip_top + (tip_right - tip_top) * tip_round); + drawList->PathBezierCurveTo( + tip_right, + tip_right, + tip_bottom + (tip_right - tip_bottom) * tip_round); + drawList->PathLineTo(tip_bottom); + drawList->PathLineTo(ImVec2(left, bottom) + ImVec2(rounding, 0)); + drawList->PathBezierCurveTo( + ImVec2(left, bottom), + ImVec2(left, bottom), + ImVec2(left, bottom) - ImVec2(0, rounding)); + + if (!filled) { + if (innerColor & 0xFF000000) { + drawList->AddConvexPolyFilled(drawList->_Path.Data, drawList->_Path.Size, innerColor); + } + + drawList->PathStroke(color, true, 2.0f * outline_scale); + } else { + drawList->PathFillConvex(color); + } + } else { + auto triangleStart = rect_center_x + 0.32f * rect_w; + + auto rect_offset = -static_cast<int>(rect_w * 0.25f * 0.25f); + + rect.Min.x += rect_offset; + rect.Max.x += rect_offset; + rect_x += rect_offset; + rect_center_x += rect_offset * 0.5f; + rect_center.x += rect_offset * 0.5f; + + if (type == IconType::Circle) { + const auto c = rect_center; + + if (!filled) { + const auto r = 0.5f * rect_w / 2.0f - 0.5f; + + if (innerColor & 0xFF000000) + drawList->AddCircleFilled(c, r, innerColor, 12 + extra_segments); + drawList->AddCircle(c, r, color, 12 + extra_segments, 2.0f * outline_scale); + } else { + drawList->AddCircleFilled(c, 0.5f * rect_w / 2.0f, color, 12 + extra_segments); + } + } + + if (type == IconType::Square) { + if (filled) { + const auto r = 0.5f * rect_w / 2.0f; + const auto p0 = rect_center - ImVec2(r, r); + const auto p1 = rect_center + ImVec2(r, r); + + drawList->AddRectFilled(p0, p1, color, 0, 15 + extra_segments); + } else { + const auto r = 0.5f * rect_w / 2.0f - 0.5f; + const auto p0 = rect_center - ImVec2(r, r); + const auto p1 = rect_center + ImVec2(r, r); + + if (innerColor & 0xFF000000) + drawList->AddRectFilled(p0, p1, innerColor, 0, 15 + extra_segments); + + drawList->AddRect(p0, p1, color, 0, 15 + extra_segments, 2.0f * outline_scale); + } + } + + if (type == IconType::Grid) { + const auto r = 0.5f * rect_w / 2.0f; + const auto w = ceilf(r / 3.0f); + + const auto baseTl = ImVec2(floorf(rect_center_x - w * 2.5f), floorf(rect_center_y - w * 2.5f)); + const auto baseBr = ImVec2(floorf(baseTl.x + w), floorf(baseTl.y + w)); + + auto tl = baseTl; + auto br = baseBr; + for (int i = 0; i < 3; ++i) { + tl.x = baseTl.x; + br.x = baseBr.x; + drawList->AddRectFilled(tl, br, color); + tl.x += w * 2; + br.x += w * 2; + if (i != 1 || filled) + drawList->AddRectFilled(tl, br, color); + tl.x += w * 2; + br.x += w * 2; + drawList->AddRectFilled(tl, br, color); + + tl.y += w * 2; + br.y += w * 2; + } + + triangleStart = br.x + w + 1.0f / 24.0f * rect_w; + } + + if (type == IconType::RoundSquare) { + if (filled) { + const auto r = 0.5f * rect_w / 2.0f; + const auto cr = r * 0.5f; + const auto p0 = rect_center - ImVec2(r, r); + const auto p1 = rect_center + ImVec2(r, r); + + drawList->AddRectFilled(p0, p1, color, cr, 15); + } else { + const auto r = 0.5f * rect_w / 2.0f - 0.5f; + const auto cr = r * 0.5f; + const auto p0 = rect_center - ImVec2(r, r); + const auto p1 = rect_center + ImVec2(r, r); + + if (innerColor & 0xFF000000) + drawList->AddRectFilled(p0, p1, innerColor, cr, 15); + + drawList->AddRect(p0, p1, color, cr, 15, 2.0f * outline_scale); + } + } else if (type == IconType::Diamond) { + if (filled) { + const auto r = 0.607f * rect_w / 2.0f; + const auto c = rect_center; + + drawList->PathLineTo(c + ImVec2(0, -r)); + drawList->PathLineTo(c + ImVec2(r, 0)); + drawList->PathLineTo(c + ImVec2(0, r)); + drawList->PathLineTo(c + ImVec2(-r, 0)); + drawList->PathFillConvex(color); + } else { + const auto r = 0.607f * rect_w / 2.0f - 0.5f; + const auto c = rect_center; + + drawList->PathLineTo(c + ImVec2(0, -r)); + drawList->PathLineTo(c + ImVec2(r, 0)); + drawList->PathLineTo(c + ImVec2(0, r)); + drawList->PathLineTo(c + ImVec2(-r, 0)); + + if (innerColor & 0xFF000000) + drawList->AddConvexPolyFilled(drawList->_Path.Data, drawList->_Path.Size, innerColor); + + drawList->PathStroke(color, true, 2.0f * outline_scale); + } + } else { + const auto triangleTip = triangleStart + rect_w * (0.45f - 0.32f); + + drawList->AddTriangleFilled( + ImVec2(ceilf(triangleTip), rect_y + rect_h * 0.5f), + ImVec2(triangleStart, rect_center_y + 0.15f * rect_h), + ImVec2(triangleStart, rect_center_y - 0.15f * rect_h), + color); + } + } +} + +void ImGui::Icon(const ImVec2& size, IconType type, bool filled, const ImVec4& color, const ImVec4& innerColor) +{ + // Taken from https://github.com/thedmd/imgui-node-editor/blob/master/examples/blueprints-example/utilities/widgets.cpp + // ax::NodeEditor::Icon + + if (ImGui::IsRectVisible(size)) + { + auto cursorPos = ImGui::GetCursorScreenPos(); + auto drawList = ImGui::GetWindowDrawList(); + DrawIcon(drawList, cursorPos, cursorPos + size, type, filled, ImColor(color), ImColor(innerColor)); + } + + ImGui::Dummy(size); +} diff --git a/core/src/UI/UI_Workflows.cpp b/core/src/UI/UI_Workflows.cpp index f9857a1..7aefeab 100644 --- a/core/src/UI/UI_Workflows.cpp +++ b/core/src/UI/UI_Workflows.cpp @@ -17,9 +17,9 @@ namespace ImNodes = ax::NodeEditor; namespace { -class WorkflowCreationMenu +class WorkflowDatabase { -private: +public: using WorkflowNodeConstructor = std::unique_ptr<WorkflowNode> (*)(); enum Category @@ -39,6 +39,7 @@ private: Category Category; }; +private: std::vector<Candidate> mCandidates; #define SUB_RANGE_ACCESS(Type, AccessorName, storage, begin, nextBegin) \ @@ -53,6 +54,7 @@ private: int mSystemInputNodes; int mOutputOffset; +public: SUB_RANGE_ACCESS(Candidate, GetNumericNodes, mCandidates, 0, mTextOffset); SUB_RANGE_ACCESS(Candidate, GetTextNodes, mCandidates, mTextOffset, mDocumentOffset); SUB_RANGE_ACCESS(Candidate, GetDocumentNodes, mCandidates, mDocumentOffset, mUserInputOffset); @@ -63,12 +65,6 @@ private: #undef SUB_RANGE_ACCESS public: - WorkflowCreationMenu() - { - SetupCandidates(); - } - -private: void SetupCandidates() { // Numeric nodes offset start at 0 @@ -140,11 +136,13 @@ class WorkflowUI private: Workflow* mWorkflow; ImNodes::EditorContext* mContext; + WorkflowDatabase mWorkflowDatabase; public: WorkflowUI() { mContext = ImNodes::CreateEditor(); + mWorkflowDatabase.SetupCandidates(); } ~WorkflowUI() @@ -157,6 +155,8 @@ public: ImNodes::SetCurrentEditor(mContext); ImNodes::Begin(""); + const char* tooltipMessage = nullptr; + for (auto& node : mWorkflow->GetNodes()) { if (!node) continue; @@ -168,8 +168,112 @@ public: for (auto& conn : mWorkflow->GetConnections()) { if (!conn.IsValid()) continue; - // TODO create link + auto srcId = mWorkflow->GetNodes()[conn.SourceNode]->GetOutputPinUniqueId(conn.SourcePin); + auto dstId = mWorkflow->GetNodes()[conn.DestinationNode]->GetInputPinUniqueId(conn.DestinationPin); + ImNodes::Link(conn.GetLinkId(), srcId, dstId); + } + + if (ImNodes::BeginCreate()) { + ImNodes::PinId src = 0, dst = 0; + if (ImNodes::QueryNewLink(&src, &dst)) { + if (!src || !dst) { + goto createError; + } + + auto [srcNode, srcPinId, srcIsOutput] = mWorkflow->DisassembleGlobalPinId((size_t)src); + auto [dstNode, dstPinId, dstIsOutput] = mWorkflow->DisassembleGlobalPinId((size_t)dst); + + if (srcNode == dstNode) { + ImNodes::RejectNewItem(); + goto createError; + } + + if (srcIsOutput == dstIsOutput) { + ImNodes::RejectNewItem(); + goto createError; + } + + auto srcPin = srcNode->GetOutputPin(srcPinId); + auto dstPin = dstNode->GetOutputPin(dstPinId); + + if (srcPin.MatchingType != dstPin.MatchingType) { + ImNodes::RejectNewItem(); + goto createError; + } + + if (ImNodes::AcceptNewItem()) { + mWorkflow->Connect(*srcNode, srcPinId, *dstNode, dstPinId); + } + } + + ImNodes::PinId newNodePin = 0; + if (ImNodes::QueryNewNode(&newNodePin)) { + auto [node, pinId, isOutput] = mWorkflow->DisassembleGlobalPinId((size_t)newNodePin); + + if ((isOutput && node->GetOutputPin(pinId).IsConnected()) || + (!isOutput && node->GetInputPin(pinId).IsConnected())) + { + ImNodes::RejectNewItem(); + goto createError; + } + + if (ImNodes::AcceptNewItem()) { + ImNodes::Suspend(); + ImGui::BeginPopup("Create Node"); + ImNodes::Resume(); + } + } + } + createError: + ImNodes::EndCreate(); + + 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); + + // TODO + } + } + deleteError: + ImNodes::EndDelete(); + + // Popups + ImNodes::Suspend(); + if (ImGui::BeginPopup("Node Context Menu")) { + // TODO + ImGui::EndPopup(); + } + if (ImGui::BeginPopup("Link Context Menu")) { + // TODO + 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 + } + } + ImGui::TreePop(); + }; + + DisplayCandidatesCategory("Numeric nodes", mWorkflowDatabase.GetNumericNodes()); + DisplayCandidatesCategory("Text nodes", mWorkflowDatabase.GetTextNodes()); + DisplayCandidatesCategory("Document nodes", mWorkflowDatabase.GetDocumentNodes()); + DisplayCandidatesCategory("User input nodes", mWorkflowDatabase.GetUserInputNodes()); + DisplayCandidatesCategory("System input nodes", mWorkflowDatabase.GetSystemInputNodes()); + DisplayCandidatesCategory("Output nodes", mWorkflowDatabase.GetOutputNodes()); + ImGui::EndPopup(); + } + if (tooltipMessage) { + ImGui::BeginTooltip(); + ImGui::TextUnformatted(tooltipMessage); + ImGui::EndTooltip(); } + ImNodes::Resume(); ImNodes::End(); } diff --git a/core/src/UI/fwd.hpp b/core/src/UI/fwd.hpp index e412524..45facbe 100644 --- a/core/src/UI/fwd.hpp +++ b/core/src/UI/fwd.hpp @@ -5,3 +5,8 @@ class LocaleStrings; // States.hpp class UIState; + +// UI.hpp +namespace ImGui { +enum class IconType; +} diff --git a/core/src/Utils/Color.hpp b/core/src/Utils/Color.hpp new file mode 100644 index 0000000..26cfdd4 --- /dev/null +++ b/core/src/Utils/Color.hpp @@ -0,0 +1,191 @@ +#pragma once
+
+#include "Utils/Vector.hpp"
+#include "Utils/fwd.hpp"
+
+#include <imgui.h>
+#include <algorithm>
+#include <cstdint>
+#include <limits>
+
+class RgbaColor
+{
+public:
+ uint8_t r;
+ uint8_t g;
+ uint8_t b;
+ uint8_t a;
+
+public:
+ constexpr RgbaColor() noexcept
+ : r{ 255 }
+ , g{ 255 }
+ , b{ 255 }
+ , a{ 255 }
+ {
+ }
+
+ constexpr RgbaColor(float r, float g, float b, float a = 1.0f) noexcept
+ : r{ static_cast<uint8_t>(r * 255.0f) }
+ , g{ static_cast<uint8_t>(g * 255.0f) }
+ , b{ static_cast<uint8_t>(b * 255.0f) }
+ , a{ static_cast<uint8_t>(a * 255.0f) }
+ {
+ }
+
+ constexpr RgbaColor(int r, int g, int b, int a = 255) noexcept
+ : r{ static_cast<uint8_t>(r & 0xFF) }
+ , g{ static_cast<uint8_t>(g & 0xFF) }
+ , b{ static_cast<uint8_t>(b & 0xFF) }
+ , a{ static_cast<uint8_t>(a & 0xFF) }
+ {
+ }
+
+ constexpr RgbaColor(uint32_t rgba) noexcept
+ : r{ static_cast<uint8_t>((rgba >> 0) & 0xFF) }
+ , g{ static_cast<uint8_t>((rgba >> 8) & 0xFF) }
+ , b{ static_cast<uint8_t>((rgba >> 16) & 0xFF) }
+ , a{ static_cast<uint8_t>((rgba >> 24) & 0xFF) }
+ {
+ }
+
+ constexpr uint32_t GetScalar() const noexcept
+ {
+ uint32_t res = 0;
+ res |= r << 24;
+ res |= g << 16;
+ res |= b << 8;
+ res |= a;
+ return res;
+ }
+
+ constexpr void SetScalar(uint32_t scalar) noexcept
+ {
+ r = (scalar >> 0) & 0xFF;
+ g = (scalar >> 8) & 0xFF;
+ b = (scalar >> 16) & 0xFF;
+ a = (scalar >> 24) & 0xFF;
+ }
+
+ constexpr float GetNormalizedRed() const noexcept
+ {
+ return r / 255.0f;
+ }
+
+ constexpr float GetNormalizedGreen() const noexcept
+ {
+ return g / 255.0f;
+ }
+
+ constexpr float GetNormalizedBlue() const noexcept
+ {
+ return b / 255.0f;
+ }
+
+ constexpr float GetNormalizedAlpha() const noexcept
+ {
+ return a / 255.0f;
+ }
+
+ constexpr Vec4i AsVec4i() const noexcept
+ {
+ return Vec4i{ r, g, b, a };
+ }
+
+ constexpr Vec4f AsVec4f() const noexcept
+ {
+ return Vec4f{
+ GetNormalizedRed(),
+ GetNormalizedGreen(),
+ GetNormalizedBlue(),
+ GetNormalizedAlpha(),
+ };
+ }
+
+ ImVec4 AsImVec() const
+ {
+ auto v = AsVec4f();
+ return ImVec4{ v.x, v.y, v.z, v.w };
+ }
+
+ ImColor AsImColor() const
+ {
+ auto v = AsVec4f();
+ return ImColor{ v.x, v.y, v.z, v.w };
+ }
+
+ constexpr void SetVec(const Vec4f& vec) noexcept
+ {
+ r = (uint8_t)(vec.x * 255.0f);
+ g = (uint8_t)(vec.y * 255.0f);
+ b = (uint8_t)(vec.z * 255.0f);
+ a = (uint8_t)(vec.w * 255.0f);
+ }
+
+ // Forward declaring because cyclic reference between RgbaColor and HsvColor
+ constexpr HsvColor ToHsv() const noexcept;
+
+ friend constexpr bool operator==(const RgbaColor&, const RgbaColor&) noexcept = default;
+};
+
+class HsvColor
+{
+public:
+ float h;
+ float s;
+ float v;
+ float a;
+
+public:
+ constexpr HsvColor() noexcept
+ : h{ 0.0f }
+ , s{ 0.0f }
+ , v{ 1.0f }
+ , a{ 1.0f }
+ {
+ }
+
+ constexpr HsvColor(float h, float s, float v, float a) noexcept
+ : h{ h }
+ , s{ s }
+ , v{ v }
+ , a{ a }
+ {
+ }
+
+ // Forward declaring because cyclic reference between RgbaColor and HsvColor
+ constexpr RgbaColor ToRgba() const noexcept;
+};
+
+constexpr HsvColor RgbaColor::ToHsv() const noexcept
+{
+ float fr = GetNormalizedRed();
+ float fg = GetNormalizedBlue();
+ 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);
+ 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);
+
+ Vec3f hcv{ h, c, q.x };
+ float s = hcv.y / (hcv.z + std::numeric_limits<float>::epsilon());
+ return HsvColor(hcv.x, s, hcv.z, fa);
+}
+
+constexpr RgbaColor HsvColor::ToRgba() const noexcept
+{
+ float r = std::abs(h * 6 - 3) - 1;
+ float g = 2 - std::abs(h * 6 - 2);
+ float b = 2 - std::abs(h * 6 - 4);
+
+ auto rgb = Vec3f{
+ std::clamp(r, 0.0f, 1.0f),
+ std::clamp(g, 0.0f, 1.0f),
+ std::clamp(b, 0.0f, 1.0f),
+ };
+ auto vc = (rgb - Vec3f{ 0, 0, 0 }) * s + Vec3f{ 1, 1, 1 } * v;
+
+ return RgbaColor(vc.x, vc.y, vc.z, a);
+}
diff --git a/core/src/Utils/Enum.hpp b/core/src/Utils/Enum.hpp deleted file mode 100644 index 1fb9661..0000000 --- a/core/src/Utils/Enum.hpp +++ /dev/null @@ -1,140 +0,0 @@ -#pragma once - -#include <compare> - -template <class TSelf> -struct BasicEnum -{ - using Self = TSelf; - using ValueType = int; - - int value; - - BasicEnum() - : value{ 0 } {} - BasicEnum(int value) - : value{ value } {} - - /// Comparison between 2 values of enum. Useful for situations like `a == b` where both a and b are TSelf. - friend auto operator<=>(const TSelf& a, const TSelf& b) - { - return a.value <=> b.value; - } - - /// Comparison between a enum and a raw value. Useful for situations like `a == TSelf::Option` where a is a enum and TSelf::Option is a raw value. - friend auto operator<=>(const TSelf& self, int value) - { - return self.value <=> value; - } - - operator int() const - { - return value; - } - - operator bool() const - { - return value; - } -}; - -#define ENUM(Name) struct Name : public BasicEnum<Name> -#define ENUM_MEMBERS() \ - enum Enum : int; \ - operator Enum() const \ - { \ - return (Enum)value; \ - } \ - using BasicEnum<Self>::BasicEnum; \ - enum Enum : int - -template <class TSelf> -struct BasicFlag -{ - using Self = TSelf; - using ValueType = int; - - int value; - - BasicFlag() - : value{ 0 } {} - BasicFlag(int value) - : value{ value } {} - - bool IsSet(TSelf mask) const - { - return (value & mask.value) == mask.value; - } - bool IsSetExclusive(TSelf mask) const - { - return value == mask.value; - } - void Set(TSelf mask, bool state) - { - if (state) { - value = (int)(value | mask.value); - } else { - value = (int)(value & ~mask.value); - } - } - - /// Comparsion between 2 values of flag. Useful for situations like `a == b` where both a and b are TSelf. - friend bool operator==(const TSelf& a, const TSelf& b) - { - return a.value == b.value; - } - - friend auto operator<=>(const TSelf& a, const TSelf& b) - { - return a.value <=> b.value; - } - - /// Comparsion between a flag and a raw value. Useful for situations like `a == TSelf::Option` where a is a flag and TSelf::Option is a raw value. - friend bool operator==(const TSelf& self, int value) - { - return self.value == value; - } - - friend auto operator<=>(const TSelf& self, int value) - { - return self.value <=> value; - } - - friend TSelf operator&(const TSelf& a, const TSelf& b) - { - return TSelf(a.value & b.value); - } - - friend TSelf operator|(const TSelf& a, const TSelf& b) - { - return TSelf(a.value | b.value); - } - - friend TSelf operator^(const TSelf& a, const TSelf& b) - { - return TSelf(a.value ^ b.value); - } - - TSelf operator~() const - { - return TSelf(~value); - } - - TSelf& operator&=(int that) - { - value = value & that; - return *this; - } - - TSelf& operator|=(int that) - { - value = value | that; - return *this; - } - - TSelf& operator^=(int that) - { - value = value ^ that; - return *this; - } -}; diff --git a/core/src/Utils/Macros.hpp b/core/src/Utils/Macros.hpp index 658aebf..68b93fb 100644 --- a/core/src/Utils/Macros.hpp +++ b/core/src/Utils/Macros.hpp @@ -4,3 +4,5 @@ #define CONCAT(a, b) CONCAT_IMPL(a, b) #define UNIQUE_NAME(prefix) CONCAT(prefix, __COUNTER__) +#define UNIQUE_NAME_LINE(prefix) CONCAT(prefix, __LINE__) +#define DISCARD UNIQUE_NAME(_discard) diff --git a/core/src/Utils/Vector.hpp b/core/src/Utils/Vector.hpp index 61fa520..bf75fd1 100644 --- a/core/src/Utils/Vector.hpp +++ b/core/src/Utils/Vector.hpp @@ -1,7 +1,64 @@ #pragma once -struct Vec2i +template <class T> +struct Vec2 { - int x = 0; - int y = 0; + T x = 0; + T y = 0; + + friend constexpr Vec2 operator+(const Vec2& a, const Vec2& b) { return { a.x + b.x, a.y + b.y }; } + friend constexpr Vec2 operator-(const Vec2& a, const Vec2& b) { return { a.x - b.x, a.y - b.y }; } + friend constexpr Vec2 operator*(const Vec2& a, const Vec2& b) { return { a.x * b.x, a.y * b.y }; } + friend constexpr Vec2 operator/(const Vec2& a, const Vec2& b) { return { a.x / b.x, a.y / b.y }; } + + friend constexpr Vec2 operator+(const Vec2& a, T n) { return { a.x + n, a.y + n }; } + friend constexpr Vec2 operator-(const Vec2& a, T n) { return { a.x - n, a.y - n }; } + friend constexpr Vec2 operator*(const Vec2& a, T n) { return { a.x * n, a.y * n }; } + friend constexpr Vec2 operator/(const Vec2& a, T n) { return { a.x / n, a.y / n }; } +}; + +using Vec2i = Vec2<int>; +using Vec2f = Vec2<float>; + +template <class T> +struct Vec3 +{ + T x = 0; + T y = 0; + T z = 0; + + friend constexpr Vec3 operator+(const Vec3& a, const Vec3& b) { return { a.x + b.x, a.y + b.y, a.z + b.z }; } + friend constexpr Vec3 operator-(const Vec3& a, const Vec3& b) { return { a.x - b.x, a.y - b.y, a.z - b.z }; } + friend constexpr Vec3 operator*(const Vec3& a, const Vec3& b) { return { a.x * b.x, a.y * b.y, a.z * b.z }; } + friend constexpr Vec3 operator/(const Vec3& a, const Vec3& b) { return { a.x / b.x, a.y / b.y, a.z / b.z }; } + + friend constexpr Vec3 operator+(const Vec3& a, T n) { return { a.x + n, a.y + n, a.z + n }; } + friend constexpr Vec3 operator-(const Vec3& a, T n) { return { a.x - n, a.y - n, a.z - n }; } + friend constexpr Vec3 operator*(const Vec3& a, T n) { return { a.x * n, a.y * n, a.z * n }; } + friend constexpr Vec3 operator/(const Vec3& a, T n) { return { a.x / n, a.y / n, a.z / n }; } }; + +using Vec3i = Vec3<int>; +using Vec3f = Vec3<float>; + +template <class T> +struct Vec4 +{ + T x = 0; + T y = 0; + T z = 0; + T w = 0; + + friend constexpr Vec4 operator+(const Vec4& a, const Vec4& b) { return { a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w }; } + friend constexpr Vec4 operator-(const Vec4& a, const Vec4& b) { return { a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w }; } + friend constexpr Vec4 operator*(const Vec4& a, const Vec4& b) { return { a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w }; } + friend constexpr Vec4 operator/(const Vec4& a, const Vec4& b) { return { a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w }; } + + friend constexpr Vec4 operator+(const Vec4& a, T n) { return { a.x + n, a.y + n, a.z + n, a.w + n }; } + friend constexpr Vec4 operator-(const Vec4& a, T n) { return { a.x - n, a.y - n, a.z - n, a.w - n }; } + friend constexpr Vec4 operator*(const Vec4& a, T n) { return { a.x * n, a.y * n, a.z * n, a.w * n }; } + friend constexpr Vec4 operator/(const Vec4& a, T n) { return { a.x / n, a.y / n, a.z / n, a.w / n }; } +}; + +using Vec4i = Vec4<int>; +using Vec4f = Vec4<float>; diff --git a/core/src/Utils/fwd.hpp b/core/src/Utils/fwd.hpp index 58b2991..74e642d 100644 --- a/core/src/Utils/fwd.hpp +++ b/core/src/Utils/fwd.hpp @@ -1,10 +1,8 @@ #pragma once -// Sigslot.hpp -class SignalStub; -template <class... TArgs> -class Signal; -class SlotGuard; +// Color.hpp +class RgbaColor; +class HsvColor; // I18n.hpp class I18n; @@ -15,6 +13,12 @@ class BasicTranslation; class FormattedTranslation; class NumericTranslation; +// Sigslot.hpp +class SignalStub; +template <class... TArgs> +class Signal; +class SlotGuard; + // String.hpp class Utf8Iterator; class Utf8IterableString; |