270 lines
7.1 KiB
C++
270 lines
7.1 KiB
C++
//Disables console window
|
|
#if !_DEBUG
|
|
#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")
|
|
#endif
|
|
|
|
#include <iostream>
|
|
#include <algorithm>
|
|
#include "ImguiBase.h"
|
|
#include "ImguiNodes.h"
|
|
|
|
int main()
|
|
{
|
|
CustomDrawData customDrawData{};
|
|
|
|
startImgui(&customDrawData, init, draw);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Setup before first draw.
|
|
/// </summary>
|
|
void init()
|
|
{
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
io.Fonts->AddFontFromFileTTF("Montserrat-Regular.ttf", 16.0f);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draw main application content.
|
|
/// </summary>
|
|
void draw(DrawData& drawData, void* customDataVoid)
|
|
{
|
|
CustomDrawData* customData = static_cast<CustomDrawData*>(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<ConnectionPayload*>(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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draws a small circle in the same row.
|
|
/// </summary>
|
|
/// <param name="outCirclePos">Returns the position of the circle.</param>
|
|
/// <returns>True if an event matching the dragType has occurred.</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draws a small button in the same row.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes any attached connections before erasing this NodeElement from a vector.
|
|
/// </summary>
|
|
/// <returns>Continued iterator</returns>
|
|
std::vector<NodeElement>::iterator cleanEraseElement(std::vector<NodeElement>& elements, std::vector<NodeElement>::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<NodeElement>();
|
|
this->triggers = std::vector<NodeElement>();
|
|
}
|