diff options
Diffstat (limited to 'core/src/UI')
-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 |
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; +} |