yea
This commit is contained in:
@@ -10,15 +10,15 @@
|
||||
|
||||
int main()
|
||||
{
|
||||
CustomDrawData customDrawData{};
|
||||
ApplicationData applicationData{};
|
||||
|
||||
startImgui(&customDrawData, init, draw);
|
||||
startImgui(&applicationData, init, draw, "Node Test", 1280, 720);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup before first draw.
|
||||
/// </summary>
|
||||
void init()
|
||||
void init(DrawData& drawData, void* customData)
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.Fonts->AddFontFromFileTTF("Montserrat-Regular.ttf", 16.0f);
|
||||
@@ -27,118 +27,120 @@ void init()
|
||||
/// <summary>
|
||||
/// Draw main application content.
|
||||
/// </summary>
|
||||
void draw(DrawData& drawData, void* customDataVoid)
|
||||
void draw(DrawData& drawData, void* customData)
|
||||
{
|
||||
CustomDrawData* customData = static_cast<CustomDrawData*>(customDataVoid);
|
||||
|
||||
bool isDraggingLine = false;
|
||||
ImVec2 lineStartPos = ImVec2{};
|
||||
ApplicationData* applicationData = static_cast<ApplicationData*>(customData);
|
||||
|
||||
// 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));
|
||||
name.append(std::to_string(applicationData->nodes.size() + 1));
|
||||
applicationData->nodes.push_back(NodeWindow(name));
|
||||
}
|
||||
ImGui::EndMainMenuBar();
|
||||
|
||||
// Draw all node windows
|
||||
auto nodeIterator = customData->nodes.begin();
|
||||
while (nodeIterator != customData->nodes.end())
|
||||
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)
|
||||
{
|
||||
auto triggerIterator = node.triggers.begin();
|
||||
while (triggerIterator != node.triggers.end())
|
||||
if (!nodeOpen)
|
||||
{
|
||||
triggerIterator = cleanEraseElement(node.triggers, triggerIterator, customData);
|
||||
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())
|
||||
{
|
||||
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))
|
||||
if (ImGui::IsWindowHovered() || ImGui::IsWindowFocused())
|
||||
{
|
||||
alertIterator = cleanEraseElement(node.alerts, alertIterator, customData);
|
||||
continue;
|
||||
if (inlineButton("x", alertIterator._Ptr))
|
||||
{
|
||||
alertIterator = cleanEraseElement(node.alerts, alertIterator, applicationData);
|
||||
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"))
|
||||
else
|
||||
{
|
||||
ConnectionPayload* payloadData = static_cast<ConnectionPayload*>(payload->Data);
|
||||
|
||||
customData->connections.push_back(NodeConnection { payloadData->sourceID, triggerIterator->id });
|
||||
invisibleInlineButton("x", "x");
|
||||
}
|
||||
ImGui::EndDragDropTarget();
|
||||
}
|
||||
|
||||
ImGui::Text(triggerIterator->name.c_str());
|
||||
if (ImGui::IsWindowHovered() || ImGui::IsWindowFocused())
|
||||
{
|
||||
if (inlineButton("x", triggerIterator._Ptr))
|
||||
ImGui::SameLine();
|
||||
drawCircle(&alertIterator->position);
|
||||
|
||||
float lineHeight = ImGui::GetTextLineHeight();
|
||||
ImVec2 dragAreaSize = ImVec2{ lineHeight, lineHeight };
|
||||
invisibleDragArea(alertIterator._Ptr, dragAreaSize);
|
||||
if (ImGui::BeginDragDropSource())
|
||||
{
|
||||
triggerIterator = cleanEraseElement(node.triggers, triggerIterator, customData);
|
||||
continue;
|
||||
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++;
|
||||
}
|
||||
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"));
|
||||
}
|
||||
auto triggerIterator = node.triggers.begin();
|
||||
while (triggerIterator != node.triggers.end())
|
||||
{
|
||||
drawCircle(&triggerIterator->position);
|
||||
|
||||
ImGui::PopClipRect();
|
||||
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<ConnectionPayload*>(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++;
|
||||
}
|
||||
|
||||
@@ -151,11 +153,11 @@ void draw(DrawData& drawData, void* customDataVoid)
|
||||
|
||||
// Draw connected lines in background
|
||||
ImDrawList* bgDrawList = ImGui::GetBackgroundDrawList();
|
||||
for (NodeConnection& nodeConnection : customData->connections)
|
||||
for (NodeConnection& nodeConnection : applicationData->connections)
|
||||
{
|
||||
ImVec2 sourcePos = ImVec2{ 0, 0 };
|
||||
ImVec2 targetPos = ImVec2{ 0, 0 };
|
||||
for (auto node : customData->nodes)
|
||||
for (auto node : applicationData->nodes)
|
||||
{
|
||||
for (auto alert : node.alerts)
|
||||
{
|
||||
@@ -177,42 +179,35 @@ void draw(DrawData& drawData, void* customDataVoid)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a small circle in the same row.
|
||||
/// Creates an invisible button that does nothing, but can be used for drag/drop operations.
|
||||
/// </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)
|
||||
void invisibleDragArea(const void* id, ImVec2& size)
|
||||
{
|
||||
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::InvisibleButton("", size);
|
||||
ImGui::PopID();
|
||||
ImGui::SetCursorPos(originalPos);
|
||||
}
|
||||
|
||||
if (outCirclePos != nullptr)
|
||||
{
|
||||
*outCirclePos = circlePos;
|
||||
}
|
||||
/// <summary>
|
||||
/// Draws a small circle at the current cursor position.
|
||||
/// </summary>
|
||||
/// <param name="outCircleCenter">Returns the center position of the circle.</param>
|
||||
void drawCircle(ImVec2* outCircleCenter = nullptr)
|
||||
{
|
||||
float lineHeight = ImGui::GetTextLineHeight();
|
||||
float circleRadius = (lineHeight / 2.) * 0.75;
|
||||
float circleOffset = lineHeight / 2.;
|
||||
|
||||
if (dragType == CircleDragType::Source && ImGui::BeginDragDropSource())
|
||||
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)
|
||||
{
|
||||
return true;
|
||||
*outCircleCenter = circlePos;
|
||||
}
|
||||
if (dragType == CircleDragType::Target && ImGui::BeginDragDropTarget())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -222,23 +217,27 @@ 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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
std::vector<NodeElement>::iterator cleanEraseElement(std::vector<NodeElement>& elements, std::vector<NodeElement>::iterator toErase, ApplicationData* data)
|
||||
{
|
||||
auto connectionIterator = data->connections.begin();
|
||||
while (connectionIterator != data->connections.end())
|
||||
@@ -253,17 +252,46 @@ std::vector<NodeElement>::iterator cleanEraseElement(std::vector<NodeElement>& e
|
||||
return elements.erase(toErase);
|
||||
}
|
||||
|
||||
NodeElement::NodeElement(std::string name)
|
||||
void cleanEreaseNodeElements(NodeWindow& node, ApplicationData* data)
|
||||
{
|
||||
this->id = nodeIdCounter;
|
||||
nodeIdCounter++;
|
||||
this->name = name;
|
||||
this->position = ImVec2{ 0, 0 };
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
NodeWindow::NodeWindow(std::string title)
|
||||
NodeElement::NodeElement(std::string name) : name(name)
|
||||
{
|
||||
this->title = title;
|
||||
this->alerts = std::vector<NodeElement>();
|
||||
this->triggers = std::vector<NodeElement>();
|
||||
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 });
|
||||
}
|
||||
|
||||
@@ -2,39 +2,41 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
int nodeIdCounter = 0;
|
||||
int globalNodeIdCounter = 0;
|
||||
|
||||
class NodeElement {
|
||||
public:
|
||||
int id;
|
||||
std::string name;
|
||||
ImVec2 position;
|
||||
int id = -1;
|
||||
std::string name = {};
|
||||
ImVec2 position = {};
|
||||
|
||||
NodeElement(std::string name);
|
||||
};
|
||||
|
||||
class NodeWindow {
|
||||
public:
|
||||
std::string title;
|
||||
std::vector<NodeElement> alerts;
|
||||
std::vector<NodeElement> triggers;
|
||||
std::string title = {};
|
||||
std::vector<NodeElement> alerts = {};
|
||||
std::vector<NodeElement> triggers = {};
|
||||
|
||||
NodeWindow(std::string title);
|
||||
};
|
||||
|
||||
typedef struct NodeConnection {
|
||||
int sourceID;
|
||||
int targetID;
|
||||
struct NodeConnection {
|
||||
int sourceID = -1;
|
||||
int targetID = -1;
|
||||
};
|
||||
|
||||
typedef struct ConnectionPayload {
|
||||
int sourceID;
|
||||
struct ConnectionPayload {
|
||||
int sourceID = -1;
|
||||
};
|
||||
|
||||
class CustomDrawData {
|
||||
class ApplicationData {
|
||||
public:
|
||||
std::vector<NodeWindow> nodes;
|
||||
std::vector<NodeConnection> connections;
|
||||
std::vector<NodeWindow> nodes = {};
|
||||
std::vector<NodeConnection> connections = {};
|
||||
|
||||
void addConnection(int sourceID, int targetID);
|
||||
};
|
||||
|
||||
enum class CircleDragType
|
||||
@@ -44,8 +46,12 @@ enum class CircleDragType
|
||||
Target
|
||||
};
|
||||
|
||||
void init();
|
||||
void init(DrawData& drawData, void* customData);
|
||||
void draw(DrawData& drawData, void* customDataVoid);
|
||||
bool drawCircle(float xOffset, CircleDragType dragType, const void* id, ImVec2* outCirclePos = nullptr);
|
||||
|
||||
void invisibleDragArea(const void* id, ImVec2& size);
|
||||
void drawCircle(ImVec2* outCircleCenter);
|
||||
bool inlineButton(const char* title, const void* id);
|
||||
std::vector<NodeElement>::iterator cleanEraseElement(std::vector<NodeElement>& vector, std::vector<NodeElement>::iterator toErase, CustomDrawData* data);
|
||||
bool invisibleInlineButton(const char* title, const char* id);
|
||||
std::vector<NodeElement>::iterator cleanEraseElement(std::vector<NodeElement>& vector, std::vector<NodeElement>::iterator toErase, ApplicationData* data);
|
||||
void cleanEreaseNodeElements(NodeWindow& node, ApplicationData* data);
|
||||
|
||||
Reference in New Issue
Block a user