Skip to content

Instantly share code, notes, and snippets.

@ChemistAion
Last active January 12, 2024 20:37
Show Gist options
  • Star 20 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save ChemistAion/0cd64b71711d81661344af040c142c1c to your computer and use it in GitHub Desktop.
Save ChemistAion/0cd64b71711d81661344af040c142c1c to your computer and use it in GitHub Desktop.
Second prototype of standalone node graph editor for ImGui
#include "nodes.h"
namespace ImGui
{
void ImGuiNodes::UpdateCanvasGeometry(ImDrawList* draw_list)
{
const ImGuiIO& io = ImGui::GetIO();
mouse_ = ImGui::GetMousePos();
{
ImVec2 min = ImGui::GetWindowContentRegionMin();
ImVec2 max = ImGui::GetWindowContentRegionMax();
pos_ = ImGui::GetWindowPos() + min;
size_ = max - min;
}
ImRect canvas(pos_, pos_ + size_);
////////////////////////////////////////////////////////////////////////////////
if (ImGui::IsKeyPressed(io.KeyMap[ImGuiKey_Home]))
{
scroll_ = {};
scale_ = 1.0f;
}
////////////////////////////////////////////////////////////////////////////////
if (false == 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) && element_node_ == NULL)
if (io.MouseDragMaxDistanceSqr[1] < (io.MouseDragThreshold * io.MouseDragThreshold))
{
bool selected = false;
for (int node_idx = 0; node_idx < nodes_.size(); ++node_idx)
{
if (nodes_[node_idx]->state_ & ImGuiNodesNodeStateFlag_Selected)
{
selected = true;
break;
}
}
if (false == selected)
ImGui::OpenPopup("NodesContextMenu");
}
}
////////////////////////////////////////////////////////////////////////////////
const float grid = 64.0f * scale_;
int mark_x = (int)(scroll_.x / grid);
for (float x = std::fmodf(scroll_.x, grid); x < size_.x; x += grid, --mark_x)
{
ImColor color = mark_x % 5 ? ImColor(0.5f, 0.5f, 0.5f, 0.1f) : ImColor(1.0f, 1.0f, 1.0f, 0.1f);
draw_list->AddLine(ImVec2(x, 0.0f) + pos_, ImVec2(x, size_.y) + pos_, color, 0.1f);
}
int mark_y = (int)(scroll_.y / grid);
for (float y = std::fmodf(scroll_.y, grid); y < size_.y; y += grid, --mark_y)
{
ImColor color = mark_y % 5 ? ImColor(0.5f, 0.5f, 0.5f, 0.1f) : ImColor(1.0f, 1.0f, 1.0f, 0.1f);
draw_list->AddLine(ImVec2(0.0f, y) + pos_, ImVec2(size_.x, y) + pos_, color, 0.1f);
}
}
ImGuiNodesNode* ImGuiNodes::UpdateNodesFromCanvas()
{
if (nodes_.empty())
return NULL;
const ImGuiIO& io = ImGui::GetIO();
ImVec2 offset = pos_ + scroll_;
ImRect canvas(pos_, pos_ + size_);
ImGuiNodesNode* hovered_node = NULL;
for (int node_idx = nodes_.size(); node_idx != 0;)
{
ImGuiNodesNode* node = nodes_[--node_idx];
IM_ASSERT(node);
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 (NULL == hovered_node && node_rect.Contains(mouse_))
hovered_node = node;
////////////////////////////////////////////////////////////////////////////////
if (state_ == ImGuiNodesState_Selecting)
{
if (io.KeyCtrl && area_.Overlaps(node_rect))
{
node->state_ |= ImGuiNodesNodeStateFlag_Marked;
continue;
}
if (false == io.KeyCtrl && area_.Contains(node_rect))
{
node->state_ |= ImGuiNodesNodeStateFlag_Marked;
continue;
}
node->state_ &= ~ImGuiNodesNodeStateFlag_Marked;
}
////////////////////////////////////////////////////////////////////////////////
for (int input_idx = 0; input_idx < node->inputs_.size(); ++input_idx)
{
ImGuiNodesInput& input = node->inputs_[input_idx];
if (input.type_ == ImGuiNodesConnectorType_None)
continue;
input.state_ &= ~(ImGuiNodesConnectorStateFlag_Hovered | ImGuiNodesConnectorStateFlag_Consider | ImGuiNodesConnectorStateFlag_Draging);
if (state_ == ImGuiNodesState_DragingInput)
{
if (&input == element_input_)
input.state_ |= ImGuiNodesConnectorStateFlag_Draging;
continue;
}
if (state_ == ImGuiNodesState_DragingOutput)
{
if (element_node_ == node)
continue;
if (ConnectionMatrix(node, element_node_, &input, element_output_))
input.state_ |= ImGuiNodesConnectorStateFlag_Consider;
}
if (!hovered_node || hovered_node != node)
continue;
if (state_ == ImGuiNodesState_Selecting)
continue;
if (state_ != ImGuiNodesState_DragingOutput && node->state_ & ImGuiNodesNodeStateFlag_Selected)
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_DragingOutput)
{
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_None)
continue;
output.state_ &= ~(ImGuiNodesConnectorStateFlag_Hovered | ImGuiNodesConnectorStateFlag_Consider | ImGuiNodesConnectorStateFlag_Draging);
if (state_ == ImGuiNodesState_DragingOutput)
{
if (&output == element_output_)
output.state_ |= ImGuiNodesConnectorStateFlag_Draging;
continue;
}
if (state_ == ImGuiNodesState_DragingInput)
{
if (element_node_ == node)
continue;
if (ConnectionMatrix(element_node_, node, element_input_, &output))
output.state_ |= ImGuiNodesConnectorStateFlag_Consider;
}
if (!hovered_node || hovered_node != node)
continue;
if (state_ == ImGuiNodesState_Selecting)
continue;
if (state_ != ImGuiNodesState_DragingInput && node->state_ & ImGuiNodesNodeStateFlag_Selected)
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_DragingInput)
{
output.state_ |= ImGuiNodesConnectorStateFlag_Hovered;
continue;
}
if (output.state_ & ImGuiNodesConnectorStateFlag_Consider)
output.state_ |= ImGuiNodesConnectorStateFlag_Hovered;
}
}
}
if (hovered_node)
hovered_node->state_ |= ImGuiNodesNodeStateFlag_Hovered;
return hovered_node;
}
ImGuiNodesNode* ImGuiNodes::CreateNodeFromDesc(ImGuiNodesNodeDesc* desc, ImVec2 pos)
{
IM_ASSERT(desc);
ImGuiNodesNode* node = new ImGuiNodesNode(desc->name_, desc->type_, desc->color_);
ImVec2 inputs;
ImVec2 outputs;
////////////////////////////////////////////////////////////////////////////////
for (int input_idx = 0; input_idx < desc->inputs_.size(); ++input_idx)
{
ImGuiNodesInput input(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 < desc->outputs_.size(); ++output_idx)
{
ImGuiNodesOutput output(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 | ImGuiNodesNodeStateFlag_Processing;
////////////////////////////////////////////////////////////////////////////////
if (processing_node_)
processing_node_->state_ &= ~(ImGuiNodesNodeStateFlag_Processing);
return processing_node_ = node;
}
bool ImGuiNodes::SortSelectedNodesOrder()
{
bool selected = false;
ImVector<ImGuiNodesNode*> nodes_unselected;
nodes_unselected.reserve(nodes_.size());
ImVector<ImGuiNodesNode*> nodes_selected;
nodes_selected.reserve(nodes_.size());
for (ImGuiNodesNode** iterator = nodes_.begin(); iterator != nodes_.end(); ++iterator)
{
ImGuiNodesNode* node = ((ImGuiNodesNode*)*iterator);
if (node->state_ & ImGuiNodesNodeStateFlag_Marked || node->state_ & ImGuiNodesNodeStateFlag_Selected)
{
selected = true;
node->state_ &= ~ImGuiNodesNodeStateFlag_Marked;
node->state_ |= ImGuiNodesNodeStateFlag_Selected;
nodes_selected.push_back(node);
}
else
nodes_unselected.push_back(node);
}
int node_idx = 0;
for (int unselected_idx = 0; unselected_idx < nodes_unselected.size(); ++unselected_idx)
nodes_[node_idx++] = nodes_unselected[unselected_idx];
for (int selected_idx = 0; selected_idx < nodes_selected.size(); ++selected_idx)
nodes_[node_idx++] = nodes_selected[selected_idx];
return selected;
}
void ImGuiNodes::Update()
{
const ImGuiIO& io = ImGui::GetIO();
UpdateCanvasGeometry(ImGui::GetWindowDrawList());
////////////////////////////////////////////////////////////////////////////////
ImGuiNodesNode* hovered_node = UpdateNodesFromCanvas();
bool consider_hover = state_ == ImGuiNodesState_Default;
consider_hover |= state_ == ImGuiNodesState_HoveringNode;
consider_hover |= state_ == ImGuiNodesState_HoveringInput;
consider_hover |= state_ == ImGuiNodesState_HoveringOutput;
////////////////////////////////////////////////////////////////////////////////
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_DragingInput)
{
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_DragingOutput)
{
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;
if (!hovered_node)
state_ = ImGuiNodesState_Default;
}
////////////////////////////////////////////////////////////////////////////////
if (ImGui::IsMouseDoubleClicked(0))
{
switch (state_)
{
case ImGuiNodesState_Default:
{
bool selected = false;
for (int node_idx = 0; node_idx < nodes_.size(); ++node_idx)
{
ImGuiNodesState& state = nodes_[node_idx]->state_;
if (state & ImGuiNodesNodeStateFlag_Selected)
selected = true;
state &= ~(ImGuiNodesNodeStateFlag_Selected | ImGuiNodesNodeStateFlag_Marked | ImGuiNodesNodeStateFlag_Hovered);
}
if (processing_node_ && false == selected)
{
processing_node_->state_ &= ~(ImGuiNodesNodeStateFlag_Processing);
processing_node_ = NULL;
}
return;
};
case ImGuiNodesState_HoveringInput:
{
if (element_input_->target_)
{
element_input_->output_->connections_--;
element_input_->output_ = NULL;
element_input_->target_ = NULL;
state_ = ImGuiNodesState_DragingInput;
}
return;
}
case ImGuiNodesState_HoveringNode:
{
IM_ASSERT(element_node_);
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_;
//const ImVec2 click = (mouse_ - scroll_ - pos_) / scale_;
//const ImVec2 position = click - element_node_->area_node_.GetCenter();
element_node_->TranslateNode(ImVec2(0.0f, element_node_->body_height_ * 0.5f));
}
state_ = ImGuiNodesState_Draging;
return;
}
}
}
////////////////////////////////////////////////////////////////////////////////
if (ImGui::IsMouseDoubleClicked(1))
{
switch (state_)
{
case ImGuiNodesState_HoveringNode:
{
IM_ASSERT(hovered_node);
if (hovered_node->state_ & ImGuiNodesNodeStateFlag_Disabled)
hovered_node->state_ &= ~(ImGuiNodesNodeStateFlag_Disabled);
else
hovered_node->state_ |= (ImGuiNodesNodeStateFlag_Disabled);
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;
bool selected = element_node_->state_ & ImGuiNodesNodeStateFlag_Selected;
if (false == selected)
{
if (processing_node_)
processing_node_->state_ &= ~(ImGuiNodesNodeStateFlag_Processing);
element_node_->state_ |= ImGuiNodesNodeStateFlag_Processing;
processing_node_ = element_node_;
IM_ASSERT(false == nodes_.empty());
if (nodes_.back() != element_node_)
{
ImGuiNodesNode** iterator = nodes_.find(element_node_);
nodes_.erase(iterator);
nodes_.push_back(element_node_);
}
}
else
SortSelectedNodesOrder();
state_ = ImGuiNodesState_Draging;
return;
}
case ImGuiNodesState_HoveringInput:
{
if (!element_input_->target_)
state_ = ImGuiNodesState_DragingInput;
else
state_ = ImGuiNodesState_Draging;
return;
}
case ImGuiNodesState_HoveringOutput:
{
state_ = ImGuiNodesState_DragingOutput;
return;
}
}
return;
}
////////////////////////////////////////////////////////////////////////////////
if (ImGui::IsMouseDragging(0))
{
switch (state_)
{
case ImGuiNodesState_Default:
{
ImRect canvas(pos_, pos_ + size_);
if (false == canvas.Contains(mouse_))
return;
if (false == 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:
{
const ImVec2 pos = mouse_ - ImGui::GetMouseDragDelta(0);
area_.Min = ImMin(pos, mouse_);
area_.Max = ImMax(pos, mouse_);
return;
}
case ImGuiNodesState_Draging:
{
if (element_input_ && element_input_->output_ && element_input_->output_->connections_ > 0)
return;
if (false == (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_DragingInput:
{
ImVec2 offset = pos_ + scroll_;
ImVec2 p1 = offset + (element_input_->pos_ * scale_);
ImVec2 p4 = element_output_ ? (offset + (element_output_->pos_ * scale_)) : mouse_;
connection_ = ImVec4(p1.x, p1.y, p4.x, p4.y);
return;
}
case ImGuiNodesState_DragingOutput:
{
ImVec2 offset = pos_ + scroll_;
ImVec2 p1 = offset + (element_output_->pos_ * scale_);
ImVec2 p4 = element_input_ ? (offset + (element_input_->pos_ * scale_)) : mouse_;
connection_ = ImVec4(p4.x, p4.y, p1.x, p1.y);
return;
}
}
return;
}
////////////////////////////////////////////////////////////////////////////////
if (ImGui::IsMouseReleased(0))
{
switch (state_)
{
case ImGuiNodesState_Selecting:
{
element_node_ = NULL;
element_input_ = NULL;
element_output_ = NULL;
area_ = {};
////////////////////////////////////////////////////////////////////////////////
SortSelectedNodesOrder();
state_ = ImGuiNodesState_Default;
return;
}
case ImGuiNodesState_Draging:
{
state_ = ImGuiNodesState_HoveringNode;
return;
}
case ImGuiNodesState_DragingInput:
case ImGuiNodesState_DragingOutput:
{
if (element_input_ && element_output_)
{
IM_ASSERT(hovered_node);
IM_ASSERT(element_node_);
element_input_->target_ = state_ == ImGuiNodesState_DragingInput ? hovered_node : element_node_;
if (element_input_->output_)
element_input_->output_->connections_--;
element_input_->output_ = element_output_;
element_output_->connections_++;
}
connection_ = ImVec4();
state_ = ImGuiNodesState_Default;
return;
}
}
return;
}
////////////////////////////////////////////////////////////////////////////////
if (ImGui::IsKeyPressed(io.KeyMap[ImGuiKey_Delete]))
{
ImVector<ImGuiNodesNode*> nodes;
nodes.reserve(nodes_.size());
for (int node_idx = 0; node_idx < nodes_.size(); ++node_idx)
{
ImGuiNodesNode* node = nodes_[node_idx];
IM_ASSERT(node);
if (node->state_ & ImGuiNodesNodeStateFlag_Selected)
{
element_node_ = NULL;
element_input_ = NULL;
element_output_ = NULL;
state_ = ImGuiNodesState_Default;
for (int sweep_idx = 0; sweep_idx < nodes_.size(); ++sweep_idx)
{
ImGuiNodesNode* sweep = nodes_[sweep_idx];
IM_ASSERT(sweep);
for (int input_idx = 0; input_idx < sweep->inputs_.size(); ++input_idx)
{
ImGuiNodesInput& input = sweep->inputs_[input_idx];
if (node == input.target_)
{
if (input.output_)
input.output_->connections_--;
input.target_ = NULL;
input.output_ = NULL;
}
}
}
for (int input_idx = 0; input_idx < node->inputs_.size(); ++input_idx)
{
ImGuiNodesInput& input = node->inputs_[input_idx];
if (input.output_)
input.output_->connections_--;
input.type_ = ImGuiNodesNodeType_None;
input.name_ = NULL;
input.target_ = NULL;
input.output_ = NULL;
}
for (int output_idx = 0; output_idx < node->outputs_.size(); ++output_idx)
{
ImGuiNodesOutput& output = node->outputs_[output_idx];
IM_ASSERT(output.connections_ == 0);
}
if (node == processing_node_)
processing_node_ = NULL;
delete node;
}
else
{
nodes.push_back(node);
}
}
nodes_ = nodes;
return;
}
}
void ImGuiNodes::ProcessNodes()
{
ImDrawList* draw_list = ImGui::GetWindowDrawList();
const ImVec2 offset = pos_ + scroll_;
////////////////////////////////////////////////////////////////////////////////
ImGui::SetWindowFontScale(scale_);
for (int node_idx = 0; node_idx < nodes_.size(); ++node_idx)
{
const ImGuiNodesNode* node = nodes_[node_idx];
IM_ASSERT(node);
for (int input_idx = 0; input_idx < node->inputs_.size(); ++input_idx)
{
const ImGuiNodesInput& input = node->inputs_[input_idx];
if (const ImGuiNodesNode* target = input.target_)
{
IM_ASSERT(target);
ImVec2 p1 = offset;
ImVec2 p4 = offset;
if (node->state_ & ImGuiNodesNodeStateFlag_Collapsed)
{
ImVec2 collapsed_input = { 0, (node->area_node_.Max.y - node->area_node_.Min.y) * 0.5f };
p1 += ((node->area_node_.Min + collapsed_input) * scale_);
}
else
{
p1 += (input.pos_ * scale_);
}
if (target->state_ & ImGuiNodesNodeStateFlag_Collapsed)
{
ImVec2 collapsed_output = { 0, (target->area_node_.Max.y - target->area_node_.Min.y) * 0.5f };
p4 += ((target->area_node_.Max - collapsed_output) * scale_);
}
else
{
p4 += (input.output_->pos_ * scale_);
}
DrawConnection(p1, p4, ImColor(1.0f, 1.0f, 1.0f, 1.0f));
}
}
}
for (int node_idx = 0; node_idx < nodes_.size(); ++node_idx)
{
const ImGuiNodesNode* node = nodes_[node_idx];
IM_ASSERT(node);
node->DrawNode(draw_list, offset, scale_, state_);
}
if (connection_.x != connection_.z && connection_.y != connection_.w)
DrawConnection(ImVec2(connection_.x, connection_.y), ImVec2(connection_.z, connection_.w), ImColor(0.0f, 1.0f, 0.0f, 1.0f));
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_Draging: ImGui::Text("ImGuiNodesState_Draging"); break;
case ImGuiNodesState_DragingInput: ImGui::Text("ImGuiNodesState_DragingInput"); break;
case ImGuiNodesState_DragingOutput: ImGui::Text("ImGuiNodesState_DragingOutput"); break;
case ImGuiNodesState_Selecting: ImGui::Text("ImGuiNodesState_Selecting"); 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_))
{
ImVec2 position = (mouse_ - scroll_ - pos_) / scale_;
ImGuiNodesNode* node = CreateNodeFromDesc(&nodes_desc_[node_idx], position);
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_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_Draging = 1 << 3
};
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,
ImGuiNodesNodeStateFlag_Processing = 1 << 6
};
enum ImGuiNodesState_
{
ImGuiNodesState_Default = 0,
ImGuiNodesState_HoveringNode,
ImGuiNodesState_HoveringInput,
ImGuiNodesState_HoveringOutput,
ImGuiNodesState_Draging,
ImGuiNodesState_DragingInput,
ImGuiNodesState_DragingOutput,
ImGuiNodesState_Selecting
};
////////////////////////////////////////////////////////////////////////////////
typedef unsigned int ImGuiNodesConnectorType;
typedef unsigned int ImGuiNodesNodeType;
typedef unsigned int ImGuiNodesConnectorState;
typedef unsigned int ImGuiNodesNodeState;
typedef unsigned int ImGuiNodesState;
////////////////////////////////////////////////////////////////////////////////
// connector text name heights factors
constexpr float ImGuiNodesConnectorDotDiameter = 0.7f; // connectors dot diameter
constexpr float ImGuiNodesConnectorDotPadding = 0.35f; // connectors dot left/right sides padding
constexpr float ImGuiNodesConnectorDistance = 1.5f; // vertical distance between connectors centers
// title text name heights factors
constexpr float ImGuiNodesHSeparation = 1.7f; // extra horizontal separation inbetween IOs
constexpr float ImGuiNodesVSeparation = 1.5f; // total IOs area separation from title and node bottom edge
constexpr float ImGuiNodesTitleHight = 2.0f;
struct ImGuiNodesNode;
struct ImGuiNodesInput;
struct ImGuiNodesOutput;
////////////////////////////////////////////////////////////////////////////////
struct ImGuiNodesInput
{
ImVec2 pos_;
ImRect area_input_;
ImRect area_name_;
ImGuiNodesConnectorType type_;
ImGuiNodesConnectorState state_;
const char* name_;
ImGuiNodesNode* target_;
ImGuiNodesOutput* output_;
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, ImGuiNodesState state) const
{
if (type_ == ImGuiNodesConnectorType_None)
return;
if (state != ImGuiNodesState_Draging && state_ & ImGuiNodesConnectorStateFlag_Hovered && false == (state_ & ImGuiNodesConnectorStateFlag_Consider))
{
const ImColor color = target_ == NULL ? ImColor(0.0f, 0.0f, 1.0f, 0.5f) : ImColor(1.0f, 0.5f, 0.0f, 0.5f);
draw_list->AddRectFilled((area_input_.Min * scale) + offset, (area_input_.Max * scale) + offset, color);
}
if (state_ & (ImGuiNodesConnectorStateFlag_Consider | ImGuiNodesConnectorStateFlag_Draging))
draw_list->AddRectFilled((area_input_.Min * scale) + offset, (area_input_.Max * scale) + offset, ImColor(0.0f, 1.0f, 0.0f, 0.5f));
bool consider_fill = false;
consider_fill |= bool(state_ & ImGuiNodesConnectorStateFlag_Draging);
consider_fill |= bool(state_ & ImGuiNodesConnectorStateFlag_Hovered && state_ & ImGuiNodesConnectorStateFlag_Consider);
ImColor color = consider_fill ? ImColor(0.0f, 1.0f, 0.0f, 1.0f) : ImColor(1.0f, 1.0f, 1.0f, 1.0f);
consider_fill |= bool(target_);
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;
target_ = NULL;
output_ = NULL;
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();
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);
}
};
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, ImGuiNodesState state) const
{
if (type_ == ImGuiNodesConnectorType_None)
return;
if (state != ImGuiNodesState_Draging && state_ & ImGuiNodesConnectorStateFlag_Hovered && false == (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_Draging))
draw_list->AddRectFilled((area_output_.Min * scale) + offset, (area_output_.Max * scale) + offset, ImColor(0.0f, 1.0f, 0.0f, 0.5f));
bool consider_fill = false;
consider_fill |= bool(state_ & ImGuiNodesConnectorStateFlag_Draging);
consider_fill |= bool(state_ & ImGuiNodesConnectorStateFlag_Hovered && state_ & ImGuiNodesConnectorStateFlag_Consider);
ImColor color = consider_fill ? ImColor(0.0f, 1.0f, 0.0f, 1.0f) : ImColor(1.0f, 1.0f, 1.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();
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_;
ImColor color_;
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, ImGuiNodesState state) const
{
if (false == (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;
ImColor head_color = color_, body_color = color_;
head_color.Value.x *= 0.5;
head_color.Value.y *= 0.5;
head_color.Value.z *= 0.5;
head_color.Value.w = 1.00f;
body_color.Value.w = 0.75f;
const ImVec2 outline(4.0f * scale, 4.0f * scale);
const ImDrawFlags rounding_corners_flags = ImDrawCornerFlags_All;
if (state_ & ImGuiNodesNodeStateFlag_Disabled)
{
body_color.Value.w = 0.25f;
if (state_ & ImGuiNodesNodeStateFlag_Collapsed)
head_color.Value.w = 0.25f;
}
if (state_ & ImGuiNodesNodeStateFlag_Processing)
draw_list->AddRectFilled(node_rect.Min - outline, node_rect.Max + outline, body_color, rounding, rounding_corners_flags);
else
draw_list->AddRectFilled(node_rect.Min, node_rect.Max, body_color, rounding, rounding_corners_flags);
const ImVec2 head = node_rect.GetTR() + ImVec2(0.0f, title_height_ * scale);
if (false == (state_ & ImGuiNodesNodeStateFlag_Collapsed))
draw_list->AddLine(ImVec2(node_rect.Min.x, head.y), ImVec2(head.x - 1.0f, head.y), ImColor(0.0f, 0.0f, 0.0f, 0.5f), 2.0f);
const ImDrawFlags head_corners_flags = state_ & ImGuiNodesNodeStateFlag_Collapsed ? rounding_corners_flags : ImDrawCornerFlags_Top;
draw_list->AddRectFilled(node_rect.Min, head, head_color, rounding, head_corners_flags);
////////////////////////////////////////////////////////////////////////////////
if (state_ & ImGuiNodesNodeStateFlag_Disabled)
{
IM_ASSERT(false == node_rect.IsInverted());
const float separation = 15.0f * scale;
for (float line = separation; true; line += separation)
{
ImVec2 start = node_rect.Min + ImVec2(0.0f, line);
ImVec2 stop = node_rect.Min + ImVec2(line, 0.0f);
if (start.y > node_rect.Max.y)
start = ImVec2(start.x + (start.y - node_rect.Max.y), node_rect.Max.y);
if (stop.x > node_rect.Max.x)
stop = ImVec2(node_rect.Max.x, stop.y + (stop.x - node_rect.Max.x));
if (start.x > node_rect.Max.x)
break;
if (stop.y > node_rect.Max.y)
break;
draw_list->AddLine(start, stop, body_color, 3.0f * scale);
}
}
////////////////////////////////////////////////////////////////////////////////
if (false == (state_ & ImGuiNodesNodeStateFlag_Collapsed))
{
for (int input_idx = 0; input_idx < inputs_.size(); ++input_idx)
inputs_[input_idx].DrawInput(draw_list, offset, scale, state);
for (int output_idx = 0; output_idx < outputs_.size(); ++output_idx)
outputs_[output_idx].DrawOutput(draw_list, offset, scale, state);
}
////////////////////////////////////////////////////////////////////////////////
ImGui::SetCursorScreenPos(((area_name_.Min + ImVec2(2, 2)) * scale) + offset);
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255));
ImGui::Text(name_);
ImGui::PopStyleColor();
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);
if (state_ & ImGuiNodesNodeStateFlag_Processing)
{
ImColor processing_color = color_;
processing_color.Value.x *= 1.5;
processing_color.Value.y *= 1.5;
processing_color.Value.z *= 1.5;
processing_color.Value.w = 1.0f;
draw_list->AddRect(node_rect.Min - outline, node_rect.Max + outline, processing_color, rounding, rounding_corners_flags, 2.0f * scale);
}
else
{
draw_list->AddRect
(
node_rect.Min - outline * 0.5f,
node_rect.Max + outline * 0.5f,
ImColor(0.0f, 0.0f, 0.0f, 0.5f),
rounding,
rounding_corners_flags,
3.0f * scale
);
}
}
ImGuiNodesNode(const char* name, ImGuiNodesNodeType type, ImColor color)
{
name_ = name;
type_ = type;
state_ = ImGuiNodesNodeStateFlag_Default;
color_ = color;
area_name_.Min = ImVec2(0.0f, 0.0f);
area_name_.Max = ImGui::CalcTextSize(name);
title_height_ = ImGuiNodesTitleHight * area_name_.GetHeight();
}
};
////////////////////////////////////////////////////////////////////////////////
//ImGuiNodesConnectionDesc size round up to 32 bytes to be cache boundaries friendly
constexpr int ImGuiNodesNamesMaxLen = 32 - sizeof(ImGuiNodesConnectorType);
struct ImGuiNodesConnectionDesc
{
char name_[ImGuiNodesNamesMaxLen];
ImGuiNodesConnectorType type_;
};
//TODO: ImVector me
struct ImGuiNodesNodeDesc
{
char name_[ImGuiNodesNamesMaxLen];
ImGuiNodesNodeType type_;
ImColor color_;
ImVector<ImGuiNodesConnectionDesc> inputs_;
ImVector<ImGuiNodesConnectionDesc> outputs_;
};
////////////////////////////////////////////////////////////////////////////////
struct ImGuiNodes
{
private:
ImVec2 mouse_;
ImVec2 pos_;
ImVec2 size_;
ImVec2 scroll_;
ImVec4 connection_;
float scale_;
////////////////////////////////////////////////////////////////////////////////
ImGuiNodesState state_;
ImRect area_;
ImGuiNodesNode* element_node_ = NULL;
ImGuiNodesInput* element_input_ = NULL;
ImGuiNodesOutput* element_output_ = NULL;
ImGuiNodesNode* processing_node_ = NULL;
////////////////////////////////////////////////////////////////////////////////
ImVector<ImGuiNodesNode*> nodes_;
ImVector<ImGuiNodesNodeDesc> nodes_desc_;
////////////////////////////////////////////////////////////////////////////////
private:
void UpdateCanvasGeometry(ImDrawList* draw_list);
ImGuiNodesNode* UpdateNodesFromCanvas();
ImGuiNodesNode* CreateNodeFromDesc(ImGuiNodesNodeDesc* desc, ImVec2 pos);
inline void DrawConnection(ImVec2 p1, ImVec2 p4, ImColor color)
{
ImDrawList* draw_list = ImGui::GetWindowDrawList();
float line = 25.0f;
ImVec2 p2 = p1;
ImVec2 p3 = p4;
p2 += (ImVec2(-line, 0.0f) * scale_);
p3 += (ImVec2(+line, 0.0f) * scale_);
draw_list->AddBezierCurve(p1, p2, p3, p4, color, 1.5f * scale_);
}
inline bool ConnectionMatrix(ImGuiNodesNode* input_node, ImGuiNodesNode* output_node, ImGuiNodesInput* input, ImGuiNodesOutput* output)
{
if (input->target_ && input->target_ == output_node)
return false;
if (input->type_ == output->type_)
return true;
if (input->type_ == ImGuiNodesConnectorType_Generic)
return true;
if (output->type_ == ImGuiNodesConnectorType_Generic)
return true;
return false;
}
inline bool SortSelectedNodesOrder();
public:
void Update();
void ProcessNodes();
void ProcessContextMenu();
ImGuiNodes()
{
scale_ = 1.0f;
state_ = ImGuiNodesState_Default;
element_node_ = NULL;
element_input_ = NULL;
element_output_ = NULL;
////////////////////////////////////////////////////////////////////////////////
{
ImGuiNodesNodeDesc desc("Test", ImGuiNodesNodeType_Generic, ImColor(0.2, 0.3, 0.6, 0.0f));
nodes_desc_.push_back(desc);
desc.inputs_.push_back({ "Float", ImGuiNodesConnectorType_Float });
desc.inputs_.push_back({ "Int", ImGuiNodesConnectorType_Int });
desc.inputs_.push_back({ "TextStream", ImGuiNodesConnectorType_Text });
desc.outputs_.push_back({ "Float", ImGuiNodesConnectorType_Float });
auto& back = nodes_desc_.back();
back.inputs_ = desc.inputs_;
back.outputs_ = desc.outputs_;
}
{
ImGuiNodesNodeDesc desc("InputBox", ImGuiNodesNodeType_Generic, ImColor(0.3, 0.5, 0.5, 0.0f));
nodes_desc_.push_back(desc);
desc.inputs_.push_back({ "Float1", ImGuiNodesConnectorType_Float });
desc.inputs_.push_back({ "Float2", ImGuiNodesConnectorType_Float });
desc.inputs_.push_back({ "Int1", ImGuiNodesConnectorType_Int });
desc.inputs_.push_back({ "Int2", ImGuiNodesConnectorType_Int });
desc.inputs_.push_back({ "", ImGuiNodesConnectorType_None });
desc.inputs_.push_back({ "GenericSink", ImGuiNodesConnectorType_Generic });
desc.inputs_.push_back({ "", ImGuiNodesConnectorType_None });
desc.inputs_.push_back({ "Vector", ImGuiNodesConnectorType_Vector });
desc.inputs_.push_back({ "Image", ImGuiNodesConnectorType_Image });
desc.inputs_.push_back({ "Text", ImGuiNodesConnectorType_Text });
desc.outputs_.push_back({ "TextStream", ImGuiNodesConnectorType_Text });
desc.outputs_.push_back({ "", ImGuiNodesConnectorType_None });
desc.outputs_.push_back({ "Float", ImGuiNodesConnectorType_Float });
desc.outputs_.push_back({ "", ImGuiNodesConnectorType_None });
desc.outputs_.push_back({ "Int", ImGuiNodesConnectorType_Int });
auto& back = nodes_desc_.back();
back.inputs_.swap(desc.inputs_);
back.outputs_.swap(desc.outputs_);
}
{
ImGuiNodesNodeDesc desc("OutputBox", ImGuiNodesNodeType_Generic, ImColor(0.4, 0.3, 0.5, 0.0f));
nodes_desc_.push_back(desc);
desc.inputs_.push_back({ "GenericSink1", ImGuiNodesConnectorType_Generic });
desc.inputs_.push_back({ "GenericSink2", ImGuiNodesConnectorType_Generic });
desc.inputs_.push_back({ "", ImGuiNodesConnectorType_None });
desc.inputs_.push_back({ "Float", ImGuiNodesConnectorType_Float });
desc.inputs_.push_back({ "Int", ImGuiNodesConnectorType_Int });
desc.inputs_.push_back({ "Text", ImGuiNodesConnectorType_Text });
desc.outputs_.push_back({ "Vector", ImGuiNodesConnectorType_Vector });
desc.outputs_.push_back({ "Image", ImGuiNodesConnectorType_Image });
desc.outputs_.push_back({ "Text", ImGuiNodesConnectorType_Text });
desc.outputs_.push_back({ "", ImGuiNodesConnectorType_None });
desc.outputs_.push_back({ "Float", ImGuiNodesConnectorType_Float });
desc.outputs_.push_back({ "Int", ImGuiNodesConnectorType_Int });
desc.outputs_.push_back({ "", ImGuiNodesConnectorType_None });
desc.outputs_.push_back({ "", ImGuiNodesConnectorType_None });
desc.outputs_.push_back({ "", ImGuiNodesConnectorType_None });
desc.outputs_.push_back({ "Generic", ImGuiNodesConnectorType_Generic });
auto& back = nodes_desc_.back();
back.inputs_.swap(desc.inputs_);
back.outputs_.swap(desc.outputs_);
}
////////////////////////////////////////////////////////////////////////////////
return;
}
~ImGuiNodes()
{
for (int desc_idx = 0; desc_idx < nodes_desc_.size(); ++desc_idx)
{
ImGuiNodesNodeDesc& node = nodes_desc_[desc_idx];
node.inputs_.~ImVector();
node.outputs_.~ImVector();
}
for (int node_idx = 0; node_idx < nodes_.size(); ++node_idx)
delete nodes_[node_idx];
}
};
////////////////////////////////////////////////////////////////////////////////
}
@ChemistAion
Copy link
Author

ChemistAion commented Jan 29, 2022

Node collapsing and node deleting are implemented
Many asserts are added
There is also a nicer grid with marks every 5 units and bold canvas origin

@ChemistAion
Copy link
Author

Bug-fix: nodes.cpp on line339 to: nodes_[node_idx++] = nodes_unselected[unselected_idx];

@metaleap
Copy link

metaleap commented Dec 11, 2023

A heads-up for all comers with ImGui v1.90.1 or newer =)

On nodes.h...

  • L313, L334: ImDrawCornerFlags is no longer there
    • likely fix: I guess all ImDrawCornerFlags_Foo change to ImDrawFlags_RoundCornersFoo
  • L478: ImDrawList.AddBezierCurve is no longer there
    • fix: was renamed to AddBezierCubic in 2020 =)
  • L515, L530, L556: looks like ImGuiNodesNodeDesc had breaking changes in constructor signature(s)
    • likely fix: change from desc(myName, myTy, myCol) to desc{myName, myTy, myCol}

On nodes.cpp...

  • L23, L705: current ImGui::IsKeyPressed has no overload matching the call
    • likely fix: ImGui::IsKeyPressed(ImGuiKey_Foo, false)
  • probably due to my local setup with clangd in VSCode, but getting No member named 'fmodf' in namespace 'std'; did you mean simply 'fmodf'? for std::fmodf
    • easy fix tho at my end, remove std:: prefix

@ChemistAion
Copy link
Author

ChemistAion commented Dec 11, 2023

@metaleap
Indeed, this code snippet utilizes some 'older' ImGui API, let me follow your list one by one (in reversed order):

  • std::fmodf == fmodf [OK]
  • ImGui::IsKeyPressed(io.KeyMap[foo] --> ImGui::IsKeyPressed(foo)
  • ImGuiNodesNodeDesc ctor as you proposed
  • ImDrawList.AddBezierCurve: here, I am no longer using Bezier curves due to computational overhead (eg. hover detection) - they are just multiple lines. In my final assessment I am using strict lines, which I am recommended to use for nodes-editor low latency
  • as you've noticed: ImDrawCornerFlags_Foo --> ImDrawFlags_RoundCornersFoo

Please note the purpose of this snippet: ocornut/imgui#306 (comment)
There is much to be done beyond its state-machine (not even touched in this snippet), such as: finding self-cycles, (perhaps) inheritance, serialization, "execution" order, undo/redo, etc.
Here is a glimpse of what I am doing based on that (with history): https://youtu.be/qkikedz6Wyo

@metaleap
Copy link

Here is a glimpse of what I am doing based on that (with history): https://youtu.be/qkikedz6Wyo

Lookin' real neat! Have actually briefly deferred my need for nodes-editing but thanks for the pointers, will see how it goes when the time comes.

@paccerdk
Copy link

Is there anything to be aware of in regards to permission of use / licensing? I'm working on a single binary / static build project and I'm currently considering forking this and as a base for my own node editor, I don't have any commercial intentions, and will probably end up releasing the project on GitHub eventually

@ChemistAion
Copy link
Author

@paccerdk it's free/public domain, enjoy ☺️
ocornut/imgui#306 (comment)

@paccerdk
Copy link

That's amazing - Thank you for this!

@ChemistAion
Copy link
Author

@paccerdk: you are welcome, here is an interface legend-list: ocornut/imgui#306 (comment)
I encourage you to share your attack on #306 whenever it's ready.

@GeTechG
Copy link

GeTechG commented Jan 12, 2024

2024-01-12.20-16-00-353.mp4

I kind of did what you wrote

if (ImGui::Begin("Node Editor")) {
	this->imguiNodes.Update();
	this->imguiNodes.ProcessNodes();
	this->imguiNodes.ProcessContextMenu();
}
ImGui::End();

This helped, but I'm not sure it's a good solution.

ImGui::GetIO().ConfigWindowsMoveFromTitleBarOnly = true;

@ChemistAion
Copy link
Author

@GeTechG: yes, that's the way

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