//Disables console window #if !_DEBUG #pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup") #endif #include #include #include "ImguiBase.h" #include "ImguiNodes.h" int main() { ApplicationData applicationData{}; ImGuiCallbacks callbacks{}; callbacks.initFunc = std::bind(init, std::placeholders::_1, applicationData); callbacks.drawFunc = std::bind(draw, std::placeholders::_1, applicationData); startImgui(callbacks, "Node Test", 1280, 720); } /// /// Setup before first draw. /// void init(DrawData& drawData, ApplicationData& customData) { ImGuiIO& io = ImGui::GetIO(); io.Fonts->AddFontFromFileTTF("Montserrat-Regular.ttf", 16.0f); } /// /// Draw main application content. /// void draw(DrawData& drawData, ApplicationData& applicationData) { // Top menu bar ImGui::BeginMainMenuBar(); if (ImGui::Button("Add Node")) { std::string name{ "Node " }; name.append(std::to_string(applicationData.nodes.size() + 1)); applicationData.nodes.push_back(NodeWindow(name)); } ImGui::EndMainMenuBar(); // Draw all node windows bool isDraggingLine = false; ImVec2 lineStartPos = ImVec2{}; auto nodeIterator = applicationData.nodes.begin(); while (nodeIterator != applicationData.nodes.end()) { NodeWindow& node = *nodeIterator; bool nodeOpen; ImGui::Begin(node.title.c_str(), &nodeOpen, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize); { if (!nodeOpen) { cleanEreaseNodeElements(*nodeIterator, applicationData); nodeIterator = applicationData.nodes.erase(nodeIterator); ImGui::End(); break; // TODO: contine creates bug with closing too many windows??? } auto alertIterator = node.alerts.begin(); while (alertIterator != node.alerts.end()) { ImGui::Text(alertIterator->name.c_str()); if (ImGui::IsWindowHovered() || ImGui::IsWindowFocused()) { if (inlineButton("x", alertIterator._Ptr)) { alertIterator = cleanEraseElement(node.alerts, alertIterator, applicationData); continue; } } else { invisibleInlineButton("x", "x"); } ImGui::SameLine(); drawCircle(&alertIterator->position); float lineHeight = ImGui::GetTextLineHeight(); ImVec2 dragAreaSize = ImVec2{ lineHeight, lineHeight }; invisibleDragArea(alertIterator._Ptr, dragAreaSize); if (ImGui::BeginDragDropSource()) { isDraggingLine = true; lineStartPos = alertIterator->position; ImGui::Text(alertIterator->name.c_str()); ConnectionPayload payload{ alertIterator->id }; ImGui::SetDragDropPayload("NODE_CONNECTION", (void*)&payload, sizeof(ConnectionPayload)); ImGui::EndDragDropSource(); } alertIterator++; } auto triggerIterator = node.triggers.begin(); while (triggerIterator != node.triggers.end()) { drawCircle(&triggerIterator->position); float lineHeight = ImGui::GetTextLineHeight(); ImVec2 dragAreaSize = ImVec2{ lineHeight, lineHeight }; invisibleDragArea(triggerIterator._Ptr, dragAreaSize); if (ImGui::BeginDragDropTarget()) { if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("NODE_CONNECTION")) { ConnectionPayload* payloadData = static_cast(payload->Data); applicationData.addConnection(payloadData->sourceID, triggerIterator->id); } ImGui::EndDragDropTarget(); } ImGui::SameLine(); ImGui::Text(triggerIterator->name.c_str()); if (ImGui::IsWindowHovered() || ImGui::IsWindowFocused()) { if (inlineButton("x", triggerIterator._Ptr)) { triggerIterator = cleanEraseElement(node.triggers, triggerIterator, applicationData); continue; } } triggerIterator++; } if (ImGui::Button("New Alert")) { node.alerts.push_back(NodeElement("Today, 18:00")); } if (ImGui::Button("New Trigger")) { node.triggers.push_back(NodeElement("Doot")); } } ImGui::End(); nodeIterator++; } // Draw drag line in foreground if (isDraggingLine) { ImDrawList* drawList = ImGui::GetForegroundDrawList(); drawList->AddLine(lineStartPos, ImGui::GetMousePos(), IM_COL32(200, 200, 200, 255), 3.0f); } // Draw connected lines in background ImDrawList* bgDrawList = ImGui::GetBackgroundDrawList(); for (NodeConnection& nodeConnection : applicationData.connections) { ImVec2 sourcePos = ImVec2{ 0, 0 }; ImVec2 targetPos = ImVec2{ 0, 0 }; for (auto node : applicationData.nodes) { for (auto alert : node.alerts) { if (alert.id == nodeConnection.sourceID) { targetPos = alert.position; } } for (auto trigger : node.triggers) { if (trigger.id == nodeConnection.targetID) { sourcePos = trigger.position; } } } bgDrawList->AddLine(sourcePos, targetPos, IM_COL32_WHITE, 2.0f); } } /// /// Creates an invisible button that does nothing, but can be used for drag/drop operations. /// void invisibleDragArea(const void* id, ImVec2& size) { ImGui::PushID(id); ImGui::InvisibleButton("", size); ImGui::PopID(); } /// /// Draws a small circle at the current cursor position. /// /// Returns the center position of the circle. void drawCircle(ImVec2* outCircleCenter = nullptr) { float lineHeight = ImGui::GetTextLineHeight(); float circleRadius = (lineHeight / 2.) * 0.75; float circleOffset = lineHeight / 2.; ImVec2 cursor = ImGui::GetCursorScreenPos(); ImVec2 circlePos = ImVec2{ cursor.x + circleOffset, cursor.y + circleOffset }; ImDrawList* drawList = ImGui::GetWindowDrawList(); drawList->AddCircleFilled(circlePos, circleRadius, IM_COL32_WHITE); if (outCircleCenter != nullptr) { *outCircleCenter = circlePos; } } /// /// Draws a small button in the same row. /// bool inlineButton(const char* title, const void* id) { ImGui::SameLine(); ImGui::PushID(id); bool result = ImGui::SmallButton(title); ImGui::PopID(); return result; } bool invisibleInlineButton(const char* title, const char* id) { ImGui::SameLine(); ImVec2 padding = ImGui::GetStyle().FramePadding; ImVec2 size = ImGui::CalcTextSize(title); size.x += padding.x * 2.; return ImGui::InvisibleButton(id, size); } /// /// Removes any attached connections before erasing this NodeElement from a vector. /// /// Continued iterator std::vector::iterator cleanEraseElement(std::vector& elements, std::vector::iterator toErase, ApplicationData& data) { auto connectionIterator = data.connections.begin(); while (connectionIterator != data.connections.end()) { if (connectionIterator->sourceID == toErase->id || connectionIterator->targetID == toErase->id) { connectionIterator = data.connections.erase(connectionIterator); continue; } connectionIterator++; } return elements.erase(toErase); } void cleanEreaseNodeElements(NodeWindow& node, ApplicationData& data) { auto triggerIterator = node.triggers.begin(); while (triggerIterator != node.triggers.end()) { triggerIterator = cleanEraseElement(node.triggers, triggerIterator, data); } auto alertIterator = node.alerts.begin(); while (alertIterator != node.alerts.end()) { alertIterator = cleanEraseElement(node.alerts, alertIterator, data); } } NodeElement::NodeElement(std::string name) : name(name) { this->id = globalNodeIdCounter; globalNodeIdCounter++; } NodeWindow::NodeWindow(std::string title) : title(title) { } void ApplicationData::addConnection(int sourceID, int targetID) { // Remove duplicate connections auto connectionIterator = connections.begin(); while (connectionIterator != connections.end()) { // Currently only remove on exact match, maybe add some other constraints later if (connectionIterator->sourceID == sourceID && connectionIterator->targetID == targetID) { connectionIterator = connections.erase(connectionIterator); continue; } connectionIterator++; } // Add new connection connections.push_back(NodeConnection{ sourceID, targetID }); }