//Disables console window #if !_DEBUG #pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup") #endif #include #include #include "ImguiBase.h" #include "ImguiNodes.h" int main() { CustomDrawData customDrawData{}; startImgui(&customDrawData, init, draw); } /// /// Setup before first draw. /// void init() { ImGuiIO& io = ImGui::GetIO(); io.Fonts->AddFontFromFileTTF("Montserrat-Regular.ttf", 16.0f); } /// /// Draw main application content. /// void draw(DrawData& drawData, void* customDataVoid) { CustomDrawData* customData = static_cast(customDataVoid); bool isDraggingLine = false; ImVec2 lineStartPos = ImVec2{}; // Top menu bar ImGui::BeginMainMenuBar(); if (ImGui::Button("Add Node")) { std::string name{ "Node " }; name.append(std::to_string(customData->nodes.size() + 1)); customData->nodes.push_back(NodeWindow(name)); } ImGui::EndMainMenuBar(); // Draw all node windows auto nodeIterator = customData->nodes.begin(); while (nodeIterator != customData->nodes.end()) { NodeWindow& node = *nodeIterator; bool nodeOpen; ImGui::Begin(node.title.c_str(), &nodeOpen, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize); if (!nodeOpen) { auto triggerIterator = node.triggers.begin(); while (triggerIterator != node.triggers.end()) { triggerIterator = cleanEraseElement(node.triggers, triggerIterator, customData); } auto alertIterator = node.alerts.begin(); while (alertIterator != node.alerts.end()) { alertIterator = cleanEraseElement(node.alerts, alertIterator, customData); } nodeIterator = customData->nodes.erase(nodeIterator); ImGui::End(); break; // TODO: contine will create problems with closing too many windows? } // Expand clip rect to allow circles on edges ImVec2 contentMin = ImGui::GetWindowPos(); ImVec2 contentMax = ImVec2{ contentMin.x + ImGui::GetWindowWidth(), contentMin.y + ImGui::GetWindowHeight() }; ImGui::PushClipRect(ImVec2{ contentMin.x - 15, contentMin.y }, ImVec2{ contentMax.x + 15, contentMax.y }, false); auto alertIterator = node.alerts.begin(); while (alertIterator != node.alerts.end()) { if (drawCircle(0, CircleDragType::Source, alertIterator._Ptr, &alertIterator->position)) { 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(); } ImGui::Text(alertIterator->name.c_str()); if (ImGui::IsWindowHovered() || ImGui::IsWindowFocused()) { if (inlineButton("x", alertIterator._Ptr)) { alertIterator = cleanEraseElement(node.alerts, alertIterator, customData); continue; } } alertIterator++; } auto triggerIterator = node.triggers.begin(); while (triggerIterator != node.triggers.end()) { if (drawCircle(-ImGui::GetStyle().WindowPadding.x, CircleDragType::Target, triggerIterator._Ptr, &triggerIterator->position)) { if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("NODE_CONNECTION")) { ConnectionPayload* payloadData = static_cast(payload->Data); customData->connections.push_back(NodeConnection { payloadData->sourceID, triggerIterator->id }); } ImGui::EndDragDropTarget(); } ImGui::Text(triggerIterator->name.c_str()); if (ImGui::IsWindowHovered() || ImGui::IsWindowFocused()) { if (inlineButton("x", triggerIterator._Ptr)) { triggerIterator = cleanEraseElement(node.triggers, triggerIterator, customData); 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::PopClipRect(); 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 : customData->connections) { ImVec2 sourcePos = ImVec2{ 0, 0 }; ImVec2 targetPos = ImVec2{ 0, 0 }; for (auto node : customData->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); } } /// /// Draws a small circle in the same row. /// /// Returns the position of the circle. /// True if an event matching the dragType has occurred. bool drawCircle(float xOffset, CircleDragType dragType, const void* id, ImVec2* outCirclePos) { auto cursor = ImGui::GetCursorScreenPos(); ImDrawList* drawList = ImGui::GetWindowDrawList(); float circleRadius = 5; float yCircleOffset = ImGui::GetTextLineHeight() / 2.; ImVec2 circlePos = ImVec2{ cursor.x + xOffset, cursor.y + yCircleOffset }; drawList->AddCircleFilled(circlePos, 5, IM_COL32_WHITE); ImVec2 originalPos = ImGui::GetCursorPos(); ImGui::SetCursorPosX(xOffset); ImGui::PushID(id); ImGui::InvisibleButton("", ImVec2{ 15, 15 }); ImGui::PopID(); ImGui::SetCursorPos(originalPos); if (outCirclePos != nullptr) { *outCirclePos = circlePos; } if (dragType == CircleDragType::Source && ImGui::BeginDragDropSource()) { return true; } if (dragType == CircleDragType::Target && ImGui::BeginDragDropTarget()) { return true; } return false; } /// /// Draws a small button in the same row. /// bool inlineButton(const char* title, const void* id) { ImGui::SameLine(); ImGui::PushID(id); if (ImGui::SmallButton(title)) { ImGui::PopID(); return true; } else { ImGui::PopID(); return false; } } /// /// Removes any attached connections before erasing this NodeElement from a vector. /// /// Continued iterator std::vector::iterator cleanEraseElement(std::vector& elements, std::vector::iterator toErase, CustomDrawData* 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); } NodeElement::NodeElement(std::string name) { this->id = nodeIdCounter; nodeIdCounter++; this->name = name; this->position = ImVec2{ 0, 0 }; } NodeWindow::NodeWindow(std::string title) { this->title = title; this->alerts = std::vector(); this->triggers = std::vector(); }