Skip to content

Instantly share code, notes, and snippets.

@sphaero
Forked from ChemistAion/nodes.cpp
Created December 12, 2018 15:07
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sphaero/5042d4ef83b451455eb93afbca0fe080 to your computer and use it in GitHub Desktop.
Save sphaero/5042d4ef83b451455eb93afbca0fe080 to your computer and use it in GitHub Desktop.
Second prototype of standalone node graph editor for ImGui
#include "Nodes.h"
namespace ImGui
{
template<int n>
struct BezierWeights
{
constexpr BezierWeights() : x_(), y_(), z_(), w_()
{
for (int i = 1; i <= n; ++i)
{
float t = (float)i / (float)(n + 1);
float u = 1.0f - t;
x_[i - 1] = u * u * u;
y_[i - 1] = 3 * u * u * t;
z_[i - 1] = 3 * u * t * t;
w_[i - 1] = t * t * t;
}
}
float x_[n];
float y_[n];
float z_[n];
float w_[n];
};
static constexpr auto bezier_weights_ = BezierWeights<16>();
float ImVec2Dot(const ImVec2& S1, const ImVec2& S2)
{
return (S1.x * S2.x + S1.y * S2.y);
}
float GetSquaredDistancePointSegment(const ImVec2& P, const ImVec2& S1, const ImVec2& S2)
{
const float l2 = (S1.x - S2.x) * (S1.x - S2.x) + (S1.y - S2.y) * (S1.y - S2.y);
if (l2 < 1.0f)
{
return (P.x - S2.x) * (P.x - S2.x) + (P.y - S2.y) * (P.y - S2.y);
}
ImVec2 PS1(P.x - S1.x, P.y - S1.y);
ImVec2 T(S2.x - S1.x, S2.y - S2.y);
const float tf = ImVec2Dot(PS1, T) / l2;
const float minTf = 1.0f < tf ? 1.0f : tf;
const float t = 0.0f > minTf ? 0.0f : minTf;
T.x = S1.x + T.x * t;
T.y = S1.y + T.y * t;
return (P.x - T.x) * (P.x - T.x) + (P.y - T.y) * (P.y - T.y);
}
float GetSquaredDistanceToBezierCurve(const ImVec2& point, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4)
{
float minSquaredDistance = FLT_MAX;
float tmp;
ImVec2 L = p1;
ImVec2 temp;
for (int i = 1; i < 16 - 1; ++i)
{
const ImVec4& W = ImVec4(bezier_weights_.x_[i], bezier_weights_.y_[i], bezier_weights_.z_[i], bezier_weights_.w_[i]);
temp.x = W.x * p1.x + W.y * p2.x + W.z * p3.x + W.w * p4.x;
temp.y = W.x * p1.y + W.y * p2.y + W.z * p3.y + W.w * p4.y;
tmp = GetSquaredDistancePointSegment(point, L, temp);
if (minSquaredDistance > tmp)
{
minSquaredDistance = tmp;
}
L = temp;
}
tmp = GetSquaredDistancePointSegment(point, L, p4);
if (minSquaredDistance > tmp)
{
minSquaredDistance = tmp;
}
return minSquaredDistance;
}
bool ImGuiNodes::UpdateCanvasGeometry(ImDrawList * draw_list)
{
ImGuiIO& io = ImGui::GetIO();
pos_ = ImGui::GetWindowPos();
size_ = ImGui::GetWindowSize();
mouse_ = ImGui::GetMousePos();
ImRect canvas(pos_, pos_ + size_);
if (!ImGui::IsMouseDown(0) && canvas.Contains(mouse_))
{
if (ImGui::IsMouseDragging(1))
scroll_ += io.MouseDelta;
if (io.KeyShift && !io.KeyCtrl)
scroll_.x += io.MouseWheel * 16.0f;
if (!io.KeyShift && !io.KeyCtrl)
scroll_.y += io.MouseWheel * 16.0f;
if (!io.KeyShift && io.KeyCtrl)
{
ImVec2 focus = (mouse_ - scroll_ - pos_) / scale_;
if (io.MouseWheel < 0.0f)
for (float zoom = io.MouseWheel; zoom < 0.0f; zoom += 1.0f)
scale_ = ImMax(0.3f, scale_ / 1.05f);
if (io.MouseWheel > 0.0f)
for (float zoom = io.MouseWheel; zoom > 0.0f; zoom -= 1.0f)
scale_ = ImMin(3.0f, scale_ * 1.05f);
ImVec2 shift = scroll_ + (focus * scale_);
scroll_ += mouse_ - shift - pos_;
}
if (ImGui::IsMouseReleased(1))
if (io.MouseDragMaxDistanceSqr[1] < (io.MouseDragThreshold * io.MouseDragThreshold))
ImGui::OpenPopup("NodesContextMenu");
}
////////////////////////////////////////////////////////////////////////////////
const float grid = 64.0f * scale_;
for (float x = fmodf(scroll_.x, grid); x < size_.x; x += grid)
draw_list->AddLine(ImVec2(x, 0.0f) + pos_, ImVec2(x, size_.y) + pos_, ImColor(0.5f, 0.5f, 0.5f, 0.1f));
for (float y = fmodf(scroll_.y, grid); y < size_.y; y += grid)
draw_list->AddLine(ImVec2(0.0f, y) + pos_, ImVec2(size_.x, y) + pos_, ImColor(0.5f, 0.5f, 0.5f, 0.1f));
return true;
}
ImGuiNodesNode* ImGuiNodes::UpdateNodesFromCanvas()
{
ImGuiIO& io = ImGui::GetIO();
ImVec2 offset = pos_ + scroll_;
ImRect canvas(pos_, pos_ + size_);
ImGuiNodesNode* hovered_node = NULL;
for (int node_idx = 0; node_idx < nodes_.size(); ++node_idx)
{
ImGuiNodesNode* node = nodes_[node_idx];
ImRect node_rect = node->area_node_;
node_rect.Min *= scale_;
node_rect.Max *= scale_;
node_rect.Translate(offset);
node_rect.ClipWith(canvas);
if (canvas.Overlaps(node_rect))
{
node->state_ |= ImGuiNodesNodeStateFlag_Visible;
node->state_ &= ~ImGuiNodesNodeStateFlag_Hovered;
}
else
{
node->state_ &= ~(ImGuiNodesNodeStateFlag_Visible | ImGuiNodesNodeStateFlag_Hovered | ImGuiNodesNodeStateFlag_Marked);
continue;
}
////////////////////////////////////////////////////////////////////////////////
if (!hovered_node && node_rect.Contains(mouse_))
hovered_node = node;
////////////////////////////////////////////////////////////////////////////////
for (int input_idx = 0; input_idx < node->inputs_.size(); ++input_idx)
{
ImGuiNodesInput* input = node->inputs_[input_idx];
if (input->type_ == ImGuiNodesConnectorType_Invisible)
continue;
input->state_ &= ~(ImGuiNodesConnectorStateFlag_Hovered | ImGuiNodesConnectorStateFlag_Consider | ImGuiNodesConnectorStateFlag_Dragging);
if (state_ == ImGuiNodesState_DraggingInput)
{
if (input == element_input_)
input->state_ |= ImGuiNodesConnectorStateFlag_Dragging;
continue;
}
if (state_ == ImGuiNodesState_DraggingOutput)
{
if (element_node_ == node)
continue;
if (ConnectionMatrix(node, element_node_, input, element_output_))
input->state_ |= ImGuiNodesConnectorStateFlag_Consider;
}
if (!hovered_node || hovered_node != node)
continue;
ImRect input_rect = input->area_input_;
input_rect.Min *= scale_;
input_rect.Max *= scale_;
input_rect.Translate(offset);
if (input_rect.Contains(mouse_))
{
if (state_ != ImGuiNodesState_DraggingOutput)
{
input->state_ |= ImGuiNodesConnectorStateFlag_Hovered;
continue;
}
if (input->state_ & ImGuiNodesConnectorStateFlag_Consider)
input->state_ |= ImGuiNodesConnectorStateFlag_Hovered;
}
}
////////////////////////////////////////////////////////////////////////////////
for (int output_idx = 0; output_idx < node->outputs_.size(); ++output_idx)
{
ImGuiNodesOutput* output = node->outputs_[output_idx];
if (output->type_ == ImGuiNodesConnectorType_Invisible)
continue;
output->state_ &= ~(ImGuiNodesConnectorStateFlag_Hovered | ImGuiNodesConnectorStateFlag_Consider | ImGuiNodesConnectorStateFlag_Dragging);
if (state_ == ImGuiNodesState_DraggingOutput)
{
if (output == element_output_)
output->state_ |= ImGuiNodesConnectorStateFlag_Dragging;
continue;
}
if (state_ == ImGuiNodesState_DraggingInput)
{
if (element_node_ == node)
continue;
if (ConnectionMatrix(element_node_, node, element_input_, output))
output->state_ |= ImGuiNodesConnectorStateFlag_Consider;
}
if (!hovered_node || hovered_node != node)
continue;
ImRect output_rect = output->area_output_;
output_rect.Min *= scale_;
output_rect.Max *= scale_;
output_rect.Translate(offset);
if (output_rect.Contains(mouse_))
{
if (state_ != ImGuiNodesState_DraggingInput)
{
output->state_ |= ImGuiNodesConnectorStateFlag_Hovered;
continue;
}
if (output->state_ & ImGuiNodesConnectorStateFlag_Consider)
output->state_ |= ImGuiNodesConnectorStateFlag_Hovered;
}
}
////////////////////////////////////////////////////////////////////////////////
if (state_ == ImGuiNodesState_Selecting)
{
if (io.KeyCtrl && area_.Overlaps(node_rect))
{
node->state_ |= ImGuiNodesNodeStateFlag_Marked;
continue;
}
if (!io.KeyCtrl && area_.Contains(node_rect))
{
node->state_ |= ImGuiNodesNodeStateFlag_Marked;
continue;
}
node->state_ &= ~ImGuiNodesNodeStateFlag_Marked;
}
}
////////////////////////////////////////////////////////////////////////////////
if (hovered_node)
hovered_node->state_ |= ImGuiNodesNodeStateFlag_Hovered;
return hovered_node;
}
ImGuiNodesConnectionPair* ImGuiNodes::UpdateConnectionsFromCanvas()
{
ImGuiNodesConnectionPair* ret = NULL;
ImVector<ImGuiNodesConnectionPair>::iterator it = nodes_connections_.begin();
while ( it != nodes_connections_.end() )
{
// TODO: optimise this by first checking within canvas and checking within bezier bbox
if ( ret == NULL )
{
ImVec2 p1, p2, p3, p4;
p1 = (it->output->pos_ + pos_) * scale_;
p2 = p1 + (ImVec2(+50.0f, 0.0f) * scale_);
p3 = (it->input->pos_ + pos_) * scale_;
p4 = p3 + (ImVec2(-50.0f, 0.0f) * scale_);
const float distance_squared = GetSquaredDistanceToBezierCurve(mouse_, p1, p2, p3, p4);
if (distance_squared < (10.0f * 10.0f) )
{
it->state = ImGuiNodesConnectionStateFlag_Hovered;
ret = &*it;
}
else
{
it->state = ImGuiNodesConnectionStateFlag_Default;
}
}
else
{
it->state = ImGuiNodesConnectionStateFlag_Default;
}
++it;
}
return ret;
}
ImGuiNodesNode* ImGuiNodes::CreateNodeFromDesc(ImGuiNodesNodeDesc* desc, ImVec2 pos)
{
ImGuiNodesNode* node = new ImGuiNodesNode(desc->name_, desc->type_);
ImVec2 inputs;
ImVec2 outputs;
////////////////////////////////////////////////////////////////////////////////
for (int input_idx = 0; input_idx < ImGuiNodesConnectionsMaxNumber; ++input_idx)
{
if (desc->inputs_[input_idx].type_ == ImGuiNodesConnectorType_None)
break;
ImGuiNodesInput* input = new ImGuiNodesInput(desc->inputs_[input_idx].name_, desc->inputs_[input_idx].type_);
inputs.x = ImMax(inputs.x, input->area_input_.GetWidth());
inputs.y += input->area_input_.GetHeight();
node->inputs_.push_back(input);
}
for (int output_idx = 0; output_idx < ImGuiNodesConnectionsMaxNumber; ++output_idx)
{
if (desc->outputs_[output_idx].type_ == ImGuiNodesConnectorType_None)
break;
ImGuiNodesOutput* output = new ImGuiNodesOutput(desc->outputs_[output_idx].name_, desc->outputs_[output_idx].type_);
outputs.x = ImMax(outputs.x, output->area_output_.GetWidth());
outputs.y += output->area_output_.GetHeight();
node->outputs_.push_back(output);
}
////////////////////////////////////////////////////////////////////////////////
node->BuildNodeGeometry(inputs, outputs);
node->TranslateNode(pos - node->area_node_.GetCenter());
node->state_ |= ImGuiNodesNodeStateFlag_Visible | ImGuiNodesNodeStateFlag_Hovered;
return node;
}
void ImGuiNodes::Update()
{
ImGuiIO& io = ImGui::GetIO();
////////////////////////////////////////////////////////////////////////////////
if (UpdateCanvasGeometry(ImGui::GetWindowDrawList()))
{
ImGuiNodesNode* hovered_node = UpdateNodesFromCanvas();
ImGuiNodesConnectionPair* hovered_conn = NULL;
bool consider_hover = state_ == ImGuiNodesState_Default;
consider_hover |= state_ == ImGuiNodesState_HoveringNode;
consider_hover |= state_ == ImGuiNodesState_HoveringInput;
consider_hover |= state_ == ImGuiNodesState_HoveringOutput;
consider_hover |= state_ == ImGuiNodesState_HoveringConnection;
////////////////////////////////////////////////////////////////////////////////
if (hovered_node && consider_hover)
{
element_input_ = NULL;
element_output_ = NULL;
for (int input_idx = 0; input_idx < hovered_node->inputs_.size(); ++input_idx)
{
if (hovered_node->inputs_[input_idx]->state_ & ImGuiNodesConnectorStateFlag_Hovered)
{
element_input_ = hovered_node->inputs_[input_idx];
state_ = ImGuiNodesState_HoveringInput;
break;
}
}
for (int output_idx = 0; output_idx < hovered_node->outputs_.size(); ++output_idx)
{
if (hovered_node->outputs_[output_idx]->state_ & ImGuiNodesConnectorStateFlag_Hovered)
{
element_output_ = hovered_node->outputs_[output_idx];
state_ = ImGuiNodesState_HoveringOutput;
break;
}
}
if (!element_input_ && !element_output_)
state_ = ImGuiNodesState_HoveringNode;
}
////////////////////////////////////////////////////////////////////////////////
if (state_ == ImGuiNodesState_DraggingInput)
{
element_output_ = NULL;
if (hovered_node)
for (int output_idx = 0; output_idx < hovered_node->outputs_.size(); ++output_idx)
{
ImGuiNodesConnectorState state = hovered_node->outputs_[output_idx]->state_;
if (state & ImGuiNodesConnectorStateFlag_Hovered && state & ImGuiNodesConnectorStateFlag_Consider)
element_output_ = hovered_node->outputs_[output_idx];
}
}
if (state_ == ImGuiNodesState_DraggingOutput)
{
element_input_ = NULL;
if (hovered_node)
for (int input_idx = 0; input_idx < hovered_node->inputs_.size(); ++input_idx)
{
ImGuiNodesConnectorState state = hovered_node->inputs_[input_idx]->state_;
if (state & ImGuiNodesConnectorStateFlag_Hovered && state & ImGuiNodesConnectorStateFlag_Consider)
element_input_ = hovered_node->inputs_[input_idx];
}
}
////////////////////////////////////////////////////////////////////////////////
if (consider_hover)
{
element_node_ = hovered_node;
// we can only hover a connection if we're in ImGuiNodesState_Default
hovered_conn = UpdateConnectionsFromCanvas();
if (!hovered_node)
{
if ( hovered_conn )
{
state_ = ImGuiNodesState_HoveringConnection;
element_conn_ = hovered_conn;
}
else
state_ = ImGuiNodesState_Default;
}
}
}
////////////////////////////////////////////////////////////////////////////////
if (ImGui::IsMouseDoubleClicked(0))
{
switch (state_)
{
case ImGuiNodesState_Default:
{
for (int node_idx = 0; node_idx < nodes_.size(); ++node_idx)
nodes_[node_idx]->state_ &= ~(ImGuiNodesNodeStateFlag_Selected | ImGuiNodesNodeStateFlag_Marked | ImGuiNodesNodeStateFlag_Hovered);
} break;
case ImGuiNodesState_HoveringInput:
case ImGuiNodesState_HoveringOutput:
case ImGuiNodesState_HoveringNode:
{
if (element_node_->state_ & ImGuiNodesNodeStateFlag_Collapsed)
{
element_node_->state_ &= ~ImGuiNodesNodeStateFlag_Collapsed;
element_node_->area_node_.Max.y += element_node_->body_height_;
element_node_->TranslateNode(ImVec2(0.0f, element_node_->body_height_ * -0.5f));
}
else
{
element_node_->state_ |= ImGuiNodesNodeStateFlag_Collapsed;
element_node_->area_node_.Max.y -= element_node_->body_height_;
element_node_->TranslateNode(ImVec2(0.0f, element_node_->body_height_ * 0.5f));
}
state_ = ImGuiNodesState_Dragging;
} break;
case ImGuiNodesState_HoveringConnection:
{
// Delete connection
element_conn_->input->connections_--;
element_conn_->output->connections_--;
nodes_connections_.erase(element_conn_);
element_conn_ = NULL;
} break;
}
return;
}
if (ImGui::IsMouseClicked(0))
{
switch (state_)
{
case ImGuiNodesState_HoveringNode:
{
if (io.KeyCtrl)
element_node_->state_ ^= ImGuiNodesNodeStateFlag_Selected;
if (io.KeyShift)
element_node_->state_ |= ImGuiNodesNodeStateFlag_Selected;
state_ = ImGuiNodesState_Dragging;
return;
}
case ImGuiNodesState_HoveringInput:
{
state_ = ImGuiNodesState_DraggingInput;
return;
}
case ImGuiNodesState_HoveringOutput:
{
state_ = ImGuiNodesState_DraggingOutput;
return;
}
}
return;
}
if (ImGui::IsMouseDragging(0))
{
switch (state_)
{
case ImGuiNodesState_Default:
{
ImRect canvas(pos_, pos_ + size_);
if (!canvas.Contains(mouse_))
return;
ImVec2 pos = mouse_ - ImGui::GetMouseDragDelta(0);
area_ = ImRect(pos, pos);
if (!io.KeyShift)
for (int node_idx = 0; node_idx < nodes_.size(); ++node_idx)
nodes_[node_idx]->state_ &= ~(ImGuiNodesNodeStateFlag_Selected | ImGuiNodesNodeStateFlag_Marked);
state_ = ImGuiNodesState_Selecting;
return;
}
case ImGuiNodesState_Selecting:
{
ImVec2 pos = mouse_ - ImGui::GetMouseDragDelta(0);
area_.Min = ImMin(pos, mouse_);
area_.Max = ImMax(pos, mouse_);
return;
}
case ImGuiNodesState_Dragging:
{
if (!(element_node_->state_ & ImGuiNodesNodeStateFlag_Selected))
element_node_->TranslateNode(io.MouseDelta / scale_, false);
else
for (int node_idx = 0; node_idx < nodes_.size(); ++node_idx)
nodes_[node_idx]->TranslateNode(io.MouseDelta / scale_, true);
return;
}
case ImGuiNodesState_DraggingInput:
{
ImVec2 offset = pos_ + scroll_;
ImVec2 p1 = offset + (element_input_->pos_ * scale_);
ImVec2 p2 = p1 + (ImVec2(-50.0f, 0.0f) * scale_);
ImVec2 p4 = element_output_ ? (offset + (element_output_->pos_ * scale_)) : mouse_;
ImVec2 p3 = p4 + (ImVec2(+50.0f, 0.0f) * scale_);
ImGui::GetWindowDrawList()->AddBezierCurve(p1, p2, p3, p4, ImColor(0.0f, 1.0f, 0.0f, 1.0f), 2.0f * scale_);
return;
}
case ImGuiNodesState_DraggingOutput:
{
ImVec2 offset = pos_ + scroll_;
ImVec2 p1 = offset + (element_output_->pos_ * scale_);
ImVec2 p2 = p1 + (ImVec2(+50.0f, 0.0f) * scale_);
ImVec2 p4 = element_input_ ? (offset + (element_input_->pos_ * scale_)) : mouse_;
ImVec2 p3 = p4 + (ImVec2(-50.0f, 0.0f) * scale_);
ImGui::GetWindowDrawList()->AddBezierCurve(p1, p2, p3, p4, ImColor(0.0f, 1.0f, 0.0f, 1.0f), 2.0f * scale_);
return;
}
}
return;
}
if (ImGui::IsMouseReleased(0))
{
switch (state_)
{
case ImGuiNodesState_Selecting:
{
area_ = ImRect();
for (int node_idx = 0; node_idx < nodes_.size(); ++node_idx)
if (nodes_[node_idx]->state_ & ImGuiNodesNodeStateFlag_Marked)
{
nodes_[node_idx]->state_ |= ImGuiNodesNodeStateFlag_Selected;
nodes_[node_idx]->state_ &= ~ImGuiNodesNodeStateFlag_Marked;
}
state_ = ImGuiNodesState_Default;
return;
}
case ImGuiNodesState_Dragging:
{
state_ = ImGuiNodesState_HoveringNode;
return;
}
case ImGuiNodesState_DraggingInput:
case ImGuiNodesState_DraggingOutput:
{
if (element_input_ && element_output_)
{
// prevent duplicate
if ( findConnection(element_input_, element_output_) == NULL )
{
ImGuiNodesConnectionPair conn; // = ImGuiNodesConnectionPair();
conn.input = element_input_;
conn.output = element_output_;
conn.input->connections_++;
conn.output->connections_++;
conn.state = ImGuiNodesConnectionStateFlag_Default;
nodes_connections_.push_back(conn);
}
// Trigger new connection!!!
}
state_ = ImGuiNodesState_Default;
return;
}
}
return;
}
}
void ImGuiNodes::ProcessNodes()
{
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImVec2 offset = pos_ + scroll_;
////////////////////////////////////////////////////////////////////////////////
ImGui::SetWindowFontScale(scale_);
for ( int conn_idx = 0; conn_idx < nodes_connections_.size(); ++conn_idx )
{
ImGuiNodesConnectionPair conn = nodes_connections_[conn_idx];
ImVec2 p1 = offset + (conn.input->pos_ * scale_);
ImVec2 p2 = p1 + (ImVec2(-50.0f, 0.0f) * scale_);
ImVec2 p4 = offset + (conn.output->pos_ * scale_);
ImVec2 p3 = p4 + (ImVec2(+50.0f, 0.0f) * scale_);
if ( conn.state == ImGuiNodesConnectionStateFlag_Hovered )
draw_list->AddBezierCurve(p1, p2, p3, p4, ImColor(1.0f, 0.7f, 0.0f, 1.0f), 2.0f * scale_);
else
draw_list->AddBezierCurve(p1, p2, p3, p4, ImColor(1.0f, 1.0f, 1.0f, 1.0f), 2.0f * scale_);
}
for (int node_idx = 0; node_idx < nodes_.size(); ++node_idx)
nodes_[node_idx]->DrawNode(draw_list, offset, scale_);
ImGui::SetWindowFontScale(1.0f);
////////////////////////////////////////////////////////////////////////////////
if (state_ == ImGuiNodesState_Selecting)
{
draw_list->AddRectFilled(area_.Min, area_.Max, ImColor(1.0f, 1.0f, 0.0f, 0.1f));
draw_list->AddRect(area_.Min, area_.Max, ImColor(1.0f, 1.0f, 0.0f, 0.5f));
}
////////////////////////////////////////////////////////////////////////////////
ImGui::SetCursorPos(ImVec2(0.0f, 0.0f));
ImGui::NewLine();
switch (state_)
{
case ImGuiNodesState_Default: ImGui::Text("ImGuiNodesState_Default"); break;
case ImGuiNodesState_HoveringNode: ImGui::Text("ImGuiNodesState_HoveringNode"); break;
case ImGuiNodesState_HoveringInput: ImGui::Text("ImGuiNodesState_HoveringInput"); break;
case ImGuiNodesState_HoveringOutput: ImGui::Text("ImGuiNodesState_HoveringOutput"); break;
case ImGuiNodesState_Dragging: ImGui::Text("ImGuiNodesState_Dragging"); break;
case ImGuiNodesState_DraggingInput: ImGui::Text("ImGuiNodesState_DraggingInput"); break;
case ImGuiNodesState_DraggingOutput: ImGui::Text("ImGuiNodesState_DraggingOutput"); break;
case ImGuiNodesState_Selecting: ImGui::Text("ImGuiNodesState_Selecting"); break;
case ImGuiNodesState_HoveringConnection: ImGui::Text("ImGuiNodesState_HoveringConnection"); break;
default: ImGui::Text("UNKNOWN"); break;
}
ImGui::NewLine();
ImGui::Text("Position: %.2f, %.2f", pos_.x, pos_.y);
ImGui::Text("Size: %.2f, %.2f", size_.x, size_.y);
ImGui::Text("Mouse: %.2f, %.2f", mouse_.x, mouse_.y);
ImGui::Text("Scroll: %.2f, %.2f", scroll_.x, scroll_.y);
ImGui::Text("Scale: %.2f", scale_);
ImGui::NewLine();
if (element_node_)
ImGui::Text("Element_node: %s", element_node_->name_);
if (element_input_)
ImGui::Text("Element_input: %s", element_input_->name_);
if (element_output_)
ImGui::Text("Element_output: %s", element_output_->name_);
////////////////////////////////////////////////////////////////////////////////
}
void ImGuiNodes::ProcessContextMenu()
{
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 8));
if (ImGui::BeginPopup("NodesContextMenu"))
{
for (int node_idx = 0; node_idx < nodes_desc_.size(); ++node_idx)
if (ImGui::MenuItem(nodes_desc_[node_idx].name_))
{
ImGuiNodesNode* node = CreateNodeFromDesc(&nodes_desc_[node_idx], (mouse_ - scroll_ - pos_) / scale_);
nodes_.push_back(node);
}
ImGui::EndPopup();
}
ImGui::PopStyleVar();
}
}
#pragma once
#define IMGUI_DEFINE_MATH_OPERATORS
#include "imgui.h"
#include "imgui_internal.h"
namespace ImGui
{
////////////////////////////////////////////////////////////////////////////////
enum ImGuiNodesConnectorType_
{
ImGuiNodesConnectorType_None = 0,
ImGuiNodesConnectorType_Invisible,
ImGuiNodesConnectorType_Generic,
ImGuiNodesConnectorType_Int,
ImGuiNodesConnectorType_Float,
ImGuiNodesConnectorType_Vector,
ImGuiNodesConnectorType_Image,
ImGuiNodesConnectorType_Text
};
enum ImGuiNodesNodeType_
{
ImGuiNodesNodeType_None = 0,
ImGuiNodesNodeType_Generic,
ImGuiNodesNodeType_Generator,
ImGuiNodesNodeType_Test
};
enum ImGuiNodesConnectorStateFlag_
{
ImGuiNodesConnectorStateFlag_Default = 0,
ImGuiNodesConnectorStateFlag_Visible = 1 << 0,
ImGuiNodesConnectorStateFlag_Hovered = 1 << 1,
ImGuiNodesConnectorStateFlag_Consider = 1 << 2,
ImGuiNodesConnectorStateFlag_Dragging = 1 << 3
};
enum ImGuiNodesConnectionStateFlag_
{
ImGuiNodesConnectionStateFlag_Default = 0,
ImGuiNodesConnectionStateFlag_Hovered = 1 << 0,
ImGuiNodesConnectionStateFlag_Selected = 1 << 1,
ImGuiNodesConnectionStateFlag_Consider = 1 << 2
};
enum ImGuiNodesNodeStateFlag_
{
ImGuiNodesNodeStateFlag_Default = 0,
ImGuiNodesNodeStateFlag_Visible = 1 << 0,
ImGuiNodesNodeStateFlag_Hovered = 1 << 1,
ImGuiNodesNodeStateFlag_Marked = 1 << 2,
ImGuiNodesNodeStateFlag_Selected = 1 << 3,
ImGuiNodesNodeStateFlag_Collapsed = 1 << 4,
ImGuiNodesNodeStateFlag_Disabled = 1 << 5
};
enum ImGuiNodesState_
{
ImGuiNodesState_Default = 0,
ImGuiNodesState_HoveringNode,
ImGuiNodesState_HoveringInput,
ImGuiNodesState_HoveringOutput,
ImGuiNodesState_Dragging,
ImGuiNodesState_DraggingInput,
ImGuiNodesState_DraggingOutput,
ImGuiNodesState_Selecting,
ImGuiNodesState_HoveringConnection
};
////////////////////////////////////////////////////////////////////////////////
typedef unsigned int ImGuiNodesConnectorType;
typedef unsigned int ImGuiNodesNodeType;
typedef unsigned int ImGuiNodesConnectorState;
typedef unsigned int ImGuiNodesConnectionState;
typedef unsigned int ImGuiNodesNodeState;
typedef unsigned int ImGuiNodesState;
////////////////////////////////////////////////////////////////////////////////
// connector text name heights factors
static const float ImGuiNodesConnectorDotDiameter = 0.7f; // connectors dot diameter
static const float ImGuiNodesConnectorDotPadding = 0.35f; // connectors dot left/right sides padding
static const float ImGuiNodesConnectorDistance = 1.5f; // vertical distance between connectors centers
// title text name heights factors
static const float ImGuiNodesHSeparation = 1.7f; // extra horizontal separation inbetween IOs
static const float ImGuiNodesVSeparation = 1.5f; // total IOs area separation from title and node bottom edge
static const float ImGuiNodesTitleHight = 2.0f;
struct ImGuiNodesNode;
struct ImGuiNodesInput;
struct ImGuiNodesOutput;
struct ImGuiNodesConnectionPair;
////////////////////////////////////////////////////////////////////////////////
struct ImGuiNodesInput
{
ImVec2 pos_;
ImRect area_input_;
ImRect area_name_;
ImGuiNodesConnectorType type_;
ImGuiNodesConnectorState state_;
ImGuiNodesNode* owner;
u_int8_t connections_;
const char* name_;
inline void TranslateInput(ImVec2 delta)
{
pos_ += delta;
area_input_.Translate(delta);
area_name_.Translate(delta);
}
inline void DrawInput(ImDrawList* draw_list, ImVec2 offset, float scale) const
{
if (type_ == ImGuiNodesConnectorType_Invisible)
return;
if (state_ & ImGuiNodesConnectorStateFlag_Hovered && !(state_ & ImGuiNodesConnectorStateFlag_Consider))
draw_list->AddRectFilled((area_input_.Min * scale) + offset, (area_input_.Max * scale) + offset, ImColor(0.0f, 0.0f, 1.0f, 0.5f));
if (state_ & (ImGuiNodesConnectorStateFlag_Consider | ImGuiNodesConnectorStateFlag_Dragging))
draw_list->AddRectFilled((area_input_.Min * scale) + offset, (area_input_.Max * scale) + offset, ImColor(0.0f, 1.0f, 0.0f, 0.5f));
ImColor color = ImColor(1.0f, 1.0f, 1.0f, 1.0f);
bool consider_fill = false;
consider_fill |= bool(state_ & ImGuiNodesConnectorStateFlag_Dragging);
consider_fill |= bool(state_ & ImGuiNodesConnectorStateFlag_Hovered && state_ & ImGuiNodesConnectorStateFlag_Consider);
if (consider_fill)
color = ImColor(0.0f, 1.0f, 0.0f, 1.0f);
consider_fill |= bool(connections_);
if (consider_fill)
draw_list->AddCircleFilled((pos_ * scale) + offset, (ImGuiNodesConnectorDotDiameter * 0.5f) * area_name_.GetHeight() * scale, color);
else
draw_list->AddCircle((pos_ * scale) + offset, (ImGuiNodesConnectorDotDiameter * 0.5f) * area_name_.GetHeight() * scale, color);
ImGui::SetCursorScreenPos((area_name_.Min * scale) + offset);
ImGui::Text(name_);
}
ImGuiNodesInput(const char* name, ImGuiNodesConnectorType type)
{
type_ = type;
state_ = ImGuiNodesConnectorStateFlag_Default;
name_ = name;
area_name_.Min = ImVec2(0.0f, 0.0f);
area_name_.Max = ImGui::CalcTextSize(name);
area_input_.Min = ImVec2(0.0f, 0.0f);
area_input_.Max.x = ImGuiNodesConnectorDotPadding + ImGuiNodesConnectorDotDiameter + ImGuiNodesConnectorDotPadding;
area_input_.Max.y = ImGuiNodesConnectorDistance;
area_input_.Max *= area_name_.GetHeight();
ImVec2 offset = ImVec2(0.0f, 0.0f) - area_input_.GetCenter();
offset.x = -2.0f; //put connectors slightly over the border
area_name_.Translate(ImVec2(area_input_.GetWidth(), (area_input_.GetHeight() - area_name_.GetHeight()) * 0.5f));
area_input_.Max.x += area_name_.GetWidth();
area_input_.Max.x += ImGuiNodesConnectorDotPadding * area_name_.GetHeight();
area_input_.Translate(offset);
area_name_.Translate(offset);
connections_ = 0;
}
};
struct ImGuiNodesOutput
{
ImVec2 pos_;
ImRect area_output_;
ImRect area_name_;
ImGuiNodesConnectorType type_;
ImGuiNodesConnectorState state_;
const char* name_;
unsigned int connections_;
inline void TranslateOutput(ImVec2 delta)
{
pos_ += delta;
area_output_.Translate(delta);
area_name_.Translate(delta);
}
inline void DrawOutput(ImDrawList* draw_list, ImVec2 offset, float scale) const
{
if (type_ == ImGuiNodesConnectorType_Invisible)
return;
if (state_ & ImGuiNodesConnectorStateFlag_Hovered && !(state_ & ImGuiNodesConnectorStateFlag_Consider))
draw_list->AddRectFilled((area_output_.Min * scale) + offset, (area_output_.Max * scale) + offset, ImColor(0.0f, 0.0f, 1.0f, 0.5f));
if (state_ & (ImGuiNodesConnectorStateFlag_Consider | ImGuiNodesConnectorStateFlag_Dragging))
draw_list->AddRectFilled((area_output_.Min * scale) + offset, (area_output_.Max * scale) + offset, ImColor(0.0f, 1.0f, 0.0f, 0.5f));
ImColor color = ImColor(1.0f, 1.0f, 1.0f, 1.0f);
bool consider_fill = false;
consider_fill |= bool(state_ & ImGuiNodesConnectorStateFlag_Dragging);
consider_fill |= bool(state_ & ImGuiNodesConnectorStateFlag_Hovered && state_ & ImGuiNodesConnectorStateFlag_Consider);
if (consider_fill)
color = ImColor(0.0f, 1.0f, 0.0f, 1.0f);
consider_fill |= bool(connections_ > 0);
if (consider_fill)
draw_list->AddCircleFilled((pos_ * scale) + offset, (ImGuiNodesConnectorDotDiameter * 0.5f) * area_name_.GetHeight() * scale, color);
else
draw_list->AddCircle((pos_ * scale) + offset, (ImGuiNodesConnectorDotDiameter * 0.5f) * area_name_.GetHeight() * scale, color);
ImGui::SetCursorScreenPos((area_name_.Min * scale) + offset);
ImGui::Text(name_);
}
ImGuiNodesOutput(const char* name, ImGuiNodesConnectorType type)
{
type_ = type;
state_ = ImGuiNodesConnectorStateFlag_Default;
connections_ = 0;
name_ = name;
area_name_.Min = ImVec2(0.0f, 0.0f) - ImGui::CalcTextSize(name);
area_name_.Max = ImVec2(0.0f, 0.0f);
area_output_.Min.x = ImGuiNodesConnectorDotPadding + ImGuiNodesConnectorDotDiameter + ImGuiNodesConnectorDotPadding;
area_output_.Min.y = ImGuiNodesConnectorDistance;
area_output_.Min *= -area_name_.GetHeight();
area_output_.Max = ImVec2(0.0f, 0.0f);
ImVec2 offset = ImVec2(0.0f, 0.0f) - area_output_.GetCenter();
offset.x = 2.0f; //put connectors slightly over the border
area_name_.Translate(ImVec2(area_output_.Min.x, (area_output_.GetHeight() - area_name_.GetHeight()) * -0.5f));
area_output_.Min.x -= area_name_.GetWidth();
area_output_.Min.x -= ImGuiNodesConnectorDotPadding * area_name_.GetHeight();
area_output_.Translate(offset);
area_name_.Translate(offset);
}
};
struct ImGuiNodesNode
{
ImRect area_node_;
ImRect area_name_;
float title_height_;
float body_height_;
ImGuiNodesNodeState state_;
ImGuiNodesNodeType type_;
const char* name_;
ImVector<ImGuiNodesInput*> inputs_;
ImVector<ImGuiNodesOutput*> outputs_;
inline void TranslateNode(ImVec2 delta, bool selected_only = false)
{
if (selected_only && !(state_ & ImGuiNodesNodeStateFlag_Selected))
return;
area_node_.Translate(delta);
area_name_.Translate(delta);
for (int input_idx = 0; input_idx < inputs_.size(); ++input_idx)
inputs_[input_idx]->TranslateInput(delta);
for (int output_idx = 0; output_idx < outputs_.size(); ++output_idx)
outputs_[output_idx]->TranslateOutput(delta);
}
inline void BuildNodeGeometry(ImVec2 inputs_size, ImVec2 outputs_size)
{
body_height_ = ImMax(inputs_size.y, outputs_size.y) + (ImGuiNodesVSeparation * area_name_.GetHeight());
area_node_.Min = ImVec2(0.0f, 0.0f);
area_node_.Max = ImVec2(0.0f, 0.0f);
area_node_.Max.x += inputs_size.x + outputs_size.x;
area_node_.Max.x += ImGuiNodesHSeparation * area_name_.GetHeight();
area_node_.Max.y += title_height_ + body_height_;
area_name_.Translate(ImVec2((area_node_.GetWidth() - area_name_.GetWidth()) * 0.5f, ((title_height_ - area_name_.GetHeight()) * 0.5f)));
ImVec2 inputs = area_node_.GetTL();
inputs.y += title_height_ + (ImGuiNodesVSeparation * area_name_.GetHeight() * 0.5f);
for (int input_idx = 0; input_idx < inputs_.size(); ++input_idx)
{
inputs_[input_idx]->TranslateInput(inputs - inputs_[input_idx]->area_input_.GetTL());
inputs.y += inputs_[input_idx]->area_input_.GetHeight();
}
ImVec2 outputs = area_node_.GetTR();
outputs.y += title_height_ + (ImGuiNodesVSeparation * area_name_.GetHeight() * 0.5f);
for (int output_idx = 0; output_idx < outputs_.size(); ++output_idx)
{
outputs_[output_idx]->TranslateOutput(outputs - outputs_[output_idx]->area_output_.GetTR());
outputs.y += outputs_[output_idx]->area_output_.GetHeight();
}
}
inline void DrawNode(ImDrawList* draw_list, ImVec2 offset, float scale) const
{
if (!(state_ & ImGuiNodesNodeStateFlag_Visible))
return;
ImRect node_rect = area_node_;
node_rect.Min *= scale;
node_rect.Max *= scale;
node_rect.Translate(offset);
float rounding = title_height_ * scale * 0.3f;
int rounding_corners_flags = state_ & ImGuiNodesNodeStateFlag_Collapsed ? ImDrawCornerFlags_All : ImDrawCornerFlags_Top;
draw_list->AddRectFilled(node_rect.Min, node_rect.Max, ImColor(0.5f, 0.5f, 0.5f, 0.5f), rounding, rounding_corners_flags);
draw_list->AddRectFilled(node_rect.Min, node_rect.GetTR() + ImVec2(0.0f, title_height_ * scale), ImColor(0.5f, 0.5f, 0.5f, 0.5f), rounding, rounding_corners_flags);
if (!(state_ & ImGuiNodesNodeStateFlag_Collapsed))
{
for (int input_idx = 0; input_idx < inputs_.size(); ++input_idx)
inputs_[input_idx]->DrawInput(draw_list, offset, scale);
for (int output_idx = 0; output_idx < outputs_.size(); ++output_idx)
outputs_[output_idx]->DrawOutput(draw_list, offset, scale);
}
ImGui::SetCursorScreenPos((area_name_.Min * scale) + offset);
ImGui::Text(name_);
if (state_ & (ImGuiNodesNodeStateFlag_Marked | ImGuiNodesNodeStateFlag_Selected))
draw_list->AddRectFilled(node_rect.Min, node_rect.Max, ImColor(1.0f, 1.0f, 1.0f, 0.25f), rounding, rounding_corners_flags);
else
if (state_ & ImGuiNodesNodeStateFlag_Hovered)
draw_list->AddRect(node_rect.Min, node_rect.Max, ImColor(1.0f, 1.0f, 1.0f, 0.5f), rounding, rounding_corners_flags);
}
ImGuiNodesNode(const char* name, ImGuiNodesNodeType type)
{
name_ = name;
type_ = type;
state_ = ImGuiNodesNodeStateFlag_Default;
area_name_.Min = ImVec2(0.0f, 0.0f);
area_name_.Max = ImGui::CalcTextSize(name);
title_height_ = ImGuiNodesTitleHight * area_name_.GetHeight();
}
};
struct ImGuiNodesConnectionPair {
ImGuiNodesNode* input_node;
ImGuiNodesInput* input;
ImGuiNodesNode* output_node;
ImGuiNodesOutput* output;
ImGuiNodesConnectionState state;
};
////////////////////////////////////////////////////////////////////////////////
static const int ImGuiNodesNamesMaxLen = 32;
static const int ImGuiNodesConnectionsMaxNumber = 16;
struct ImGuiNodesConnectionDesc
{
char name_[ImGuiNodesNamesMaxLen];
ImGuiNodesConnectorType type_;
};
struct ImGuiNodesNodeDesc
{
char name_[ImGuiNodesNamesMaxLen];
ImGuiNodesNodeType type_;
ImGuiNodesConnectionDesc inputs_[ImGuiNodesConnectionsMaxNumber];
ImGuiNodesConnectionDesc outputs_[ImGuiNodesConnectionsMaxNumber];
};
////////////////////////////////////////////////////////////////////////////////
struct ImGuiNodes
{
private:
ImVec2 mouse_;
ImVec2 pos_;
ImVec2 size_;
ImVec2 scroll_;
float scale_;
////////////////////////////////////////////////////////////////////////////////
ImGuiNodesState state_;
ImRect area_;
ImGuiNodesNode* element_node_;
ImGuiNodesInput* element_input_;
ImGuiNodesOutput* element_output_;
ImGuiNodesConnectionPair* element_conn_;
////////////////////////////////////////////////////////////////////////////////
ImVector<ImGuiNodesNode*> nodes_;
ImVector<ImGuiNodesNodeDesc> nodes_desc_;
ImVector<ImGuiNodesConnectionPair> nodes_connections_;
////////////////////////////////////////////////////////////////////////////////
private:
bool UpdateCanvasGeometry(ImDrawList* draw_list);
ImGuiNodesNode* UpdateNodesFromCanvas();
ImGuiNodesConnectionPair* UpdateConnectionsFromCanvas();
ImGuiNodesNode* CreateNodeFromDesc(ImGuiNodesNodeDesc* desc, ImVec2 pos);
inline bool ConnectionMatrix(ImGuiNodesNode* input_node, ImGuiNodesNode* output_node, ImGuiNodesInput* input, ImGuiNodesOutput* output)
{
//UNREFERENCED_PARAMETER(input_node);
//UNREFERENCED_PARAMETER(output_node);
// if connection already exists?
if ( findConnection(input, output) != NULL )
return false;
// if connection is valid
if (input->type_ == output->type_)
return true;
if (input->type_ == ImGuiNodesConnectorType_Generic)
return true;
if (output->type_ == ImGuiNodesConnectorType_Generic)
return true;
return false;
}
inline ImGuiNodesConnectionPair* findConnection(ImGuiNodesInput* input, ImGuiNodesOutput* output)
{
ImVector<ImGuiNodesConnectionPair>::iterator it = nodes_connections_.begin();
while (it != nodes_connections_.end() )
{
if (it->input == input && it->output == output) return &*it;
++it;
}
return NULL;
}
public:
void Update();
void ProcessNodes();
void ProcessContextMenu();
ImGuiNodes()
{
scale_ = 1.0f;
state_ = ImGuiNodesState_Default;
element_node_ = NULL;
element_input_ = NULL;
element_output_ = NULL;
////////////////////////////////////////////////////////////////////////////////
ImGuiNodesNodeDesc nd = {
"TestTest", ImGuiNodesNodeType_Generic,
{ // inputs
{ "Float", ImGuiNodesConnectorType_Float },
{ "Int", ImGuiNodesConnectorType_Int },
{ "TextStream", ImGuiNodesConnectorType_Text }
},
{ // outputs
{ "Float", ImGuiNodesConnectorType_Float }
}
};
nodes_desc_.push_back( nd );
ImGuiNodesNodeDesc nd2 = {
"InputBox", ImGuiNodesNodeType_Generic,
{ // inputs
{ "Float1", ImGuiNodesConnectorType_Float },
{ "Float2", ImGuiNodesConnectorType_Float },
{ "Int1", ImGuiNodesConnectorType_Int },
{ "Int2", ImGuiNodesConnectorType_Int },
{ "", ImGuiNodesConnectorType_Invisible },
{ "GenericSink", ImGuiNodesConnectorType_Generic },
{ "", ImGuiNodesConnectorType_Invisible },
{ "Vector", ImGuiNodesConnectorType_Vector },
{ "Image", ImGuiNodesConnectorType_Image },
{ "Text", ImGuiNodesConnectorType_Text }
},
{ // outputs
{ "TextStream", ImGuiNodesConnectorType_Text },
{ "", ImGuiNodesConnectorType_Invisible },
{ "Float", ImGuiNodesConnectorType_Float },
{ "", ImGuiNodesConnectorType_Invisible },
{ "Int", ImGuiNodesConnectorType_Int }
}
};
nodes_desc_.push_back( nd2 );
ImGuiNodesNodeDesc nd3 = {
"OutputBox", ImGuiNodesNodeType_Generic,
{ // inputs
{ "GenericSink1", ImGuiNodesConnectorType_Generic },
{ "GenericSink2", ImGuiNodesConnectorType_Generic },
{ "", ImGuiNodesConnectorType_Invisible },
{ "Float", ImGuiNodesConnectorType_Float },
{ "Int", ImGuiNodesConnectorType_Int },
{ "Text", ImGuiNodesConnectorType_Text },
},
{ // outputs
{ "Vector", ImGuiNodesConnectorType_Vector },
{ "Image", ImGuiNodesConnectorType_Image },
{ "Text", ImGuiNodesConnectorType_Text },
{ "", ImGuiNodesConnectorType_Invisible },
{ "Float", ImGuiNodesConnectorType_Float },
{ "Int", ImGuiNodesConnectorType_Int },
{ "", ImGuiNodesConnectorType_Invisible },
{ "", ImGuiNodesConnectorType_Invisible },
{ "", ImGuiNodesConnectorType_Invisible },
{ "Generic", ImGuiNodesConnectorType_Generic }
}
};
nodes_desc_.push_back( nd3 );
////////////////////////////////////////////////////////////////////////////////
}
~ImGuiNodes()
{
for (int node_idx = 0; node_idx < nodes_.size(); ++node_idx)
{
ImGuiNodesNode* node = nodes_[node_idx];
for (int input_idx = 0; input_idx < node->inputs_.size(); ++input_idx)
delete node->inputs_[input_idx];
for (int output_idx = 0; output_idx < node->outputs_.size(); ++output_idx)
delete node->outputs_[output_idx];
delete node;
}
}
};
////////////////////////////////////////////////////////////////////////////////
}
@atorralb
Copy link

Hi, do you have sample code to try it out?

@sphaero
Copy link
Author

sphaero commented Jul 28, 2020

Better have a look at: https://github.com/rokups/ImNodes

@atorralb
Copy link

Better have a look at: https://github.com/rokups/ImNodes

ok, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment