summaryrefslogtreecommitdiff
path: root/core/src
diff options
context:
space:
mode:
authorrtk0c <[email protected]>2021-05-12 13:23:56 -0700
committerrtk0c <[email protected]>2021-05-12 13:34:43 -0700
commit765df313e065f8401319c68ba70cd41b0bc34c9d (patch)
tree44e0c781ed9f5ea0f98ac906e96c677d04befa27 /core/src
parent6ec8cc216282396ece535941ea6ca4a63d924e8f (diff)
Start to work on actually rendering the node graph
Diffstat (limited to 'core/src')
-rw-r--r--core/src/Model/Workflow/Value.hpp12
-rw-r--r--core/src/Model/Workflow/Value_RTTI.cpp29
-rw-r--r--core/src/Model/Workflow/Workflow.hpp100
-rw-r--r--core/src/Model/Workflow/Workflow_Main.cpp193
-rw-r--r--core/src/UI/UI.hpp13
-rw-r--r--core/src/UI/UI_Utils.cpp219
-rw-r--r--core/src/UI/UI_Workflows.cpp122
-rw-r--r--core/src/UI/fwd.hpp5
-rw-r--r--core/src/Utils/Color.hpp191
-rw-r--r--core/src/Utils/Enum.hpp140
-rw-r--r--core/src/Utils/Macros.hpp2
-rw-r--r--core/src/Utils/Vector.hpp63
-rw-r--r--core/src/Utils/fwd.hpp14
13 files changed, 832 insertions, 271 deletions
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;