summaryrefslogtreecommitdiff
path: root/core/src/UI
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/UI
parent6ec8cc216282396ece535941ea6ca4a63d924e8f (diff)
Start to work on actually rendering the node graph
Diffstat (limited to 'core/src/UI')
-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
4 files changed, 350 insertions, 9 deletions
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;
+}