Initial graph editor and graph instantiation from file.
This commit is contained in:
+710
@@ -0,0 +1,710 @@
|
||||
#include "node_editor.h"
|
||||
#include "graph.h"
|
||||
|
||||
#include <imnodes.h>
|
||||
#include <imgui.h>
|
||||
|
||||
#include <SDL_keycode.h>
|
||||
#include <SDL_timer.h>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
namespace example
|
||||
{
|
||||
namespace
|
||||
{
|
||||
enum class NodeType
|
||||
{
|
||||
add,
|
||||
multiply,
|
||||
output,
|
||||
sine,
|
||||
time,
|
||||
value
|
||||
};
|
||||
|
||||
struct Node
|
||||
{
|
||||
NodeType type;
|
||||
float value;
|
||||
|
||||
explicit Node(const NodeType t) : type(t), value(0.f) {}
|
||||
|
||||
Node(const NodeType t, const float v) : type(t), value(v) {}
|
||||
};
|
||||
|
||||
template<class T>
|
||||
T clamp(T x, T a, T b)
|
||||
{
|
||||
return std::min(b, std::max(x, a));
|
||||
}
|
||||
|
||||
static float current_time_seconds = 0.f;
|
||||
static bool emulate_three_button_mouse = false;
|
||||
|
||||
ImU32 evaluate(const Graph<Node>& graph, const int root_node)
|
||||
{
|
||||
std::stack<int> postorder;
|
||||
dfs_traverse(
|
||||
graph, root_node, [&postorder](const int node_id) -> void { postorder.push(node_id); });
|
||||
|
||||
std::stack<float> value_stack;
|
||||
while (!postorder.empty())
|
||||
{
|
||||
const int id = postorder.top();
|
||||
postorder.pop();
|
||||
const Node node = graph.node(id);
|
||||
|
||||
switch (node.type)
|
||||
{
|
||||
case NodeType::add:
|
||||
{
|
||||
const float rhs = value_stack.top();
|
||||
value_stack.pop();
|
||||
const float lhs = value_stack.top();
|
||||
value_stack.pop();
|
||||
value_stack.push(lhs + rhs);
|
||||
}
|
||||
break;
|
||||
case NodeType::multiply:
|
||||
{
|
||||
const float rhs = value_stack.top();
|
||||
value_stack.pop();
|
||||
const float lhs = value_stack.top();
|
||||
value_stack.pop();
|
||||
value_stack.push(rhs * lhs);
|
||||
}
|
||||
break;
|
||||
case NodeType::sine:
|
||||
{
|
||||
const float x = value_stack.top();
|
||||
value_stack.pop();
|
||||
const float res = std::abs(std::sin(x));
|
||||
value_stack.push(res);
|
||||
}
|
||||
break;
|
||||
case NodeType::time:
|
||||
{
|
||||
value_stack.push(current_time_seconds);
|
||||
}
|
||||
break;
|
||||
case NodeType::value:
|
||||
{
|
||||
// If the edge does not have an edge connecting to another node, then just use the value
|
||||
// at this node. It means the node's input pin has not been connected to anything and
|
||||
// the value comes from the node's UI.
|
||||
if (graph.num_edges_from_node(id) == 0ull)
|
||||
{
|
||||
value_stack.push(node.value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// The final output node isn't evaluated in the loop -- instead we just pop
|
||||
// the three values which should be in the stack.
|
||||
assert(value_stack.size() == 3ull);
|
||||
const int b = static_cast<int>(255.f * clamp(value_stack.top(), 0.f, 1.f) + 0.5f);
|
||||
value_stack.pop();
|
||||
const int g = static_cast<int>(255.f * clamp(value_stack.top(), 0.f, 1.f) + 0.5f);
|
||||
value_stack.pop();
|
||||
const int r = static_cast<int>(255.f * clamp(value_stack.top(), 0.f, 1.f) + 0.5f);
|
||||
value_stack.pop();
|
||||
|
||||
return IM_COL32(r, g, b, 255);
|
||||
}
|
||||
|
||||
class ColorNodeEditor
|
||||
{
|
||||
public:
|
||||
ColorNodeEditor() : graph_(), nodes_(), root_node_id_(-1),
|
||||
minimap_location_(ImNodesMiniMapLocation_BottomRight) {}
|
||||
|
||||
void show()
|
||||
{
|
||||
// Update timer context
|
||||
current_time_seconds = 0.001f * SDL_GetTicks();
|
||||
|
||||
auto flags = ImGuiWindowFlags_MenuBar;
|
||||
|
||||
// The node editor window
|
||||
ImGui::Begin("color node editor", NULL, flags);
|
||||
|
||||
if (ImGui::BeginMenuBar())
|
||||
{
|
||||
if (ImGui::BeginMenu("Mini-map"))
|
||||
{
|
||||
const char* names[] = {
|
||||
"Top Left",
|
||||
"Top Right",
|
||||
"Bottom Left",
|
||||
"Bottom Right",
|
||||
};
|
||||
int locations[] = {
|
||||
ImNodesMiniMapLocation_TopLeft,
|
||||
ImNodesMiniMapLocation_TopRight,
|
||||
ImNodesMiniMapLocation_BottomLeft,
|
||||
ImNodesMiniMapLocation_BottomRight,
|
||||
};
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
bool selected = minimap_location_ == locations[i];
|
||||
if (ImGui::MenuItem(names[i], NULL, &selected))
|
||||
minimap_location_ = locations[i];
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("Style"))
|
||||
{
|
||||
if (ImGui::MenuItem("Classic"))
|
||||
{
|
||||
ImGui::StyleColorsClassic();
|
||||
ImNodes::StyleColorsClassic();
|
||||
}
|
||||
if (ImGui::MenuItem("Dark"))
|
||||
{
|
||||
ImGui::StyleColorsDark();
|
||||
ImNodes::StyleColorsDark();
|
||||
}
|
||||
if (ImGui::MenuItem("Light"))
|
||||
{
|
||||
ImGui::StyleColorsLight();
|
||||
ImNodes::StyleColorsLight();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
ImGui::EndMenuBar();
|
||||
}
|
||||
|
||||
ImGui::TextUnformatted("Edit the color of the output color window using nodes.");
|
||||
ImGui::Columns(2);
|
||||
ImGui::TextUnformatted("A -- add node");
|
||||
ImGui::TextUnformatted("X -- delete selected node or link");
|
||||
ImGui::NextColumn();
|
||||
if (ImGui::Checkbox("emulate_three_button_mouse", &emulate_three_button_mouse))
|
||||
{
|
||||
ImNodes::GetIO().EmulateThreeButtonMouse.Modifier =
|
||||
emulate_three_button_mouse ? &ImGui::GetIO().KeyAlt : NULL;
|
||||
}
|
||||
ImGui::Columns(1);
|
||||
|
||||
ImNodes::BeginNodeEditor();
|
||||
|
||||
// Handle new nodes
|
||||
// These are driven by the user, so we place this code before rendering the nodes
|
||||
{
|
||||
const bool open_popup = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
|
||||
ImNodes::IsEditorHovered() &&
|
||||
ImGui::IsKeyReleased(SDL_SCANCODE_A);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.f, 8.f));
|
||||
if (!ImGui::IsAnyItemHovered() && open_popup)
|
||||
{
|
||||
ImGui::OpenPopup("add node");
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopup("add node"))
|
||||
{
|
||||
const ImVec2 click_pos = ImGui::GetMousePosOnOpeningCurrentPopup();
|
||||
|
||||
if (ImGui::MenuItem("add"))
|
||||
{
|
||||
const Node value(NodeType::value, 0.f);
|
||||
const Node op(NodeType::add);
|
||||
|
||||
UiNode ui_node;
|
||||
ui_node.type = UiNodeType::add;
|
||||
ui_node.add.lhs = graph_.insert_node(value);
|
||||
ui_node.add.rhs = graph_.insert_node(value);
|
||||
ui_node.id = graph_.insert_node(op);
|
||||
|
||||
graph_.insert_edge(ui_node.id, ui_node.add.lhs);
|
||||
graph_.insert_edge(ui_node.id, ui_node.add.rhs);
|
||||
|
||||
nodes_.push_back(ui_node);
|
||||
ImNodes::SetNodeScreenSpacePos(ui_node.id, click_pos);
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("multiply"))
|
||||
{
|
||||
const Node value(NodeType::value, 0.f);
|
||||
const Node op(NodeType::multiply);
|
||||
|
||||
UiNode ui_node;
|
||||
ui_node.type = UiNodeType::multiply;
|
||||
ui_node.multiply.lhs = graph_.insert_node(value);
|
||||
ui_node.multiply.rhs = graph_.insert_node(value);
|
||||
ui_node.id = graph_.insert_node(op);
|
||||
|
||||
graph_.insert_edge(ui_node.id, ui_node.multiply.lhs);
|
||||
graph_.insert_edge(ui_node.id, ui_node.multiply.rhs);
|
||||
|
||||
nodes_.push_back(ui_node);
|
||||
ImNodes::SetNodeScreenSpacePos(ui_node.id, click_pos);
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("output") && root_node_id_ == -1)
|
||||
{
|
||||
const Node value(NodeType::value, 0.f);
|
||||
const Node out(NodeType::output);
|
||||
|
||||
UiNode ui_node;
|
||||
ui_node.type = UiNodeType::output;
|
||||
ui_node.output.r = graph_.insert_node(value);
|
||||
ui_node.output.g = graph_.insert_node(value);
|
||||
ui_node.output.b = graph_.insert_node(value);
|
||||
ui_node.id = graph_.insert_node(out);
|
||||
|
||||
graph_.insert_edge(ui_node.id, ui_node.output.r);
|
||||
graph_.insert_edge(ui_node.id, ui_node.output.g);
|
||||
graph_.insert_edge(ui_node.id, ui_node.output.b);
|
||||
|
||||
nodes_.push_back(ui_node);
|
||||
ImNodes::SetNodeScreenSpacePos(ui_node.id, click_pos);
|
||||
root_node_id_ = ui_node.id;
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("sine"))
|
||||
{
|
||||
const Node value(NodeType::value, 0.f);
|
||||
const Node op(NodeType::sine);
|
||||
|
||||
UiNode ui_node;
|
||||
ui_node.type = UiNodeType::sine;
|
||||
ui_node.sine.input = graph_.insert_node(value);
|
||||
ui_node.id = graph_.insert_node(op);
|
||||
|
||||
graph_.insert_edge(ui_node.id, ui_node.sine.input);
|
||||
|
||||
nodes_.push_back(ui_node);
|
||||
ImNodes::SetNodeScreenSpacePos(ui_node.id, click_pos);
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("time"))
|
||||
{
|
||||
UiNode ui_node;
|
||||
ui_node.type = UiNodeType::time;
|
||||
ui_node.id = graph_.insert_node(Node(NodeType::time));
|
||||
|
||||
nodes_.push_back(ui_node);
|
||||
ImNodes::SetNodeScreenSpacePos(ui_node.id, click_pos);
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
for (const UiNode& node : nodes_)
|
||||
{
|
||||
switch (node.type)
|
||||
{
|
||||
case UiNodeType::add:
|
||||
{
|
||||
const float node_width = 100.f;
|
||||
ImNodes::BeginNode(node.id);
|
||||
|
||||
ImNodes::BeginNodeTitleBar();
|
||||
ImGui::TextUnformatted("add");
|
||||
ImNodes::EndNodeTitleBar();
|
||||
{
|
||||
ImNodes::BeginInputAttribute(node.add.lhs);
|
||||
const float label_width = ImGui::CalcTextSize("left").x;
|
||||
ImGui::TextUnformatted("left");
|
||||
if (graph_.num_edges_from_node(node.add.lhs) == 0ull)
|
||||
{
|
||||
ImGui::SameLine();
|
||||
ImGui::PushItemWidth(node_width - label_width);
|
||||
ImGui::DragFloat("##hidelabel", &graph_.node(node.add.lhs).value, 0.01f);
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
ImNodes::EndInputAttribute();
|
||||
}
|
||||
|
||||
{
|
||||
ImNodes::BeginInputAttribute(node.add.rhs);
|
||||
const float label_width = ImGui::CalcTextSize("right").x;
|
||||
ImGui::TextUnformatted("right");
|
||||
if (graph_.num_edges_from_node(node.add.rhs) == 0ull)
|
||||
{
|
||||
ImGui::SameLine();
|
||||
ImGui::PushItemWidth(node_width - label_width);
|
||||
ImGui::DragFloat("##hidelabel", &graph_.node(node.add.rhs).value, 0.01f);
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
ImNodes::EndInputAttribute();
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
{
|
||||
ImNodes::BeginOutputAttribute(node.id);
|
||||
const float label_width = ImGui::CalcTextSize("result").x;
|
||||
ImGui::Indent(node_width - label_width);
|
||||
ImGui::TextUnformatted("result");
|
||||
ImNodes::EndOutputAttribute();
|
||||
}
|
||||
|
||||
ImNodes::EndNode();
|
||||
}
|
||||
break;
|
||||
case UiNodeType::multiply:
|
||||
{
|
||||
const float node_width = 100.0f;
|
||||
ImNodes::BeginNode(node.id);
|
||||
|
||||
ImNodes::BeginNodeTitleBar();
|
||||
ImGui::TextUnformatted("multiply");
|
||||
ImNodes::EndNodeTitleBar();
|
||||
|
||||
{
|
||||
ImNodes::BeginInputAttribute(node.multiply.lhs);
|
||||
const float label_width = ImGui::CalcTextSize("left").x;
|
||||
ImGui::TextUnformatted("left");
|
||||
if (graph_.num_edges_from_node(node.multiply.lhs) == 0ull)
|
||||
{
|
||||
ImGui::SameLine();
|
||||
ImGui::PushItemWidth(node_width - label_width);
|
||||
ImGui::DragFloat(
|
||||
"##hidelabel", &graph_.node(node.multiply.lhs).value, 0.01f);
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
ImNodes::EndInputAttribute();
|
||||
}
|
||||
|
||||
{
|
||||
ImNodes::BeginInputAttribute(node.multiply.rhs);
|
||||
const float label_width = ImGui::CalcTextSize("right").x;
|
||||
ImGui::TextUnformatted("right");
|
||||
if (graph_.num_edges_from_node(node.multiply.rhs) == 0ull)
|
||||
{
|
||||
ImGui::SameLine();
|
||||
ImGui::PushItemWidth(node_width - label_width);
|
||||
ImGui::DragFloat(
|
||||
"##hidelabel", &graph_.node(node.multiply.rhs).value, 0.01f);
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
ImNodes::EndInputAttribute();
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
{
|
||||
ImNodes::BeginOutputAttribute(node.id);
|
||||
const float label_width = ImGui::CalcTextSize("result").x;
|
||||
ImGui::Indent(node_width - label_width);
|
||||
ImGui::TextUnformatted("result");
|
||||
ImNodes::EndOutputAttribute();
|
||||
}
|
||||
|
||||
ImNodes::EndNode();
|
||||
}
|
||||
break;
|
||||
case UiNodeType::output:
|
||||
{
|
||||
const float node_width = 100.0f;
|
||||
ImNodes::PushColorStyle(ImNodesCol_TitleBar, IM_COL32(11, 109, 191, 255));
|
||||
ImNodes::PushColorStyle(ImNodesCol_TitleBarHovered, IM_COL32(45, 126, 194, 255));
|
||||
ImNodes::PushColorStyle(ImNodesCol_TitleBarSelected, IM_COL32(81, 148, 204, 255));
|
||||
ImNodes::BeginNode(node.id);
|
||||
|
||||
ImNodes::BeginNodeTitleBar();
|
||||
ImGui::TextUnformatted("output");
|
||||
ImNodes::EndNodeTitleBar();
|
||||
|
||||
ImGui::Dummy(ImVec2(node_width, 0.f));
|
||||
{
|
||||
ImNodes::BeginInputAttribute(node.output.r);
|
||||
const float label_width = ImGui::CalcTextSize("r").x;
|
||||
ImGui::TextUnformatted("r");
|
||||
if (graph_.num_edges_from_node(node.output.r) == 0ull)
|
||||
{
|
||||
ImGui::SameLine();
|
||||
ImGui::PushItemWidth(node_width - label_width);
|
||||
ImGui::DragFloat(
|
||||
"##hidelabel", &graph_.node(node.output.r).value, 0.01f, 0.f, 1.0f);
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
ImNodes::EndInputAttribute();
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
{
|
||||
ImNodes::BeginInputAttribute(node.output.g);
|
||||
const float label_width = ImGui::CalcTextSize("g").x;
|
||||
ImGui::TextUnformatted("g");
|
||||
if (graph_.num_edges_from_node(node.output.g) == 0ull)
|
||||
{
|
||||
ImGui::SameLine();
|
||||
ImGui::PushItemWidth(node_width - label_width);
|
||||
ImGui::DragFloat(
|
||||
"##hidelabel", &graph_.node(node.output.g).value, 0.01f, 0.f, 1.f);
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
ImNodes::EndInputAttribute();
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
{
|
||||
ImNodes::BeginInputAttribute(node.output.b);
|
||||
const float label_width = ImGui::CalcTextSize("b").x;
|
||||
ImGui::TextUnformatted("b");
|
||||
if (graph_.num_edges_from_node(node.output.b) == 0ull)
|
||||
{
|
||||
ImGui::SameLine();
|
||||
ImGui::PushItemWidth(node_width - label_width);
|
||||
ImGui::DragFloat(
|
||||
"##hidelabel", &graph_.node(node.output.b).value, 0.01f, 0.f, 1.0f);
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
ImNodes::EndInputAttribute();
|
||||
}
|
||||
ImNodes::EndNode();
|
||||
ImNodes::PopColorStyle();
|
||||
ImNodes::PopColorStyle();
|
||||
ImNodes::PopColorStyle();
|
||||
}
|
||||
break;
|
||||
case UiNodeType::sine:
|
||||
{
|
||||
const float node_width = 100.0f;
|
||||
ImNodes::BeginNode(node.id);
|
||||
|
||||
ImNodes::BeginNodeTitleBar();
|
||||
ImGui::TextUnformatted("sine");
|
||||
ImNodes::EndNodeTitleBar();
|
||||
|
||||
{
|
||||
ImNodes::BeginInputAttribute(node.sine.input);
|
||||
const float label_width = ImGui::CalcTextSize("number").x;
|
||||
ImGui::TextUnformatted("number");
|
||||
if (graph_.num_edges_from_node(node.sine.input) == 0ull)
|
||||
{
|
||||
ImGui::SameLine();
|
||||
ImGui::PushItemWidth(node_width - label_width);
|
||||
ImGui::DragFloat(
|
||||
"##hidelabel", &graph_.node(node.sine.input).value, 0.01f, 0.f, 1.0f);
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
ImNodes::EndInputAttribute();
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
{
|
||||
ImNodes::BeginOutputAttribute(node.id);
|
||||
const float label_width = ImGui::CalcTextSize("output").x;
|
||||
ImGui::Indent(node_width - label_width);
|
||||
ImGui::TextUnformatted("output");
|
||||
ImNodes::EndInputAttribute();
|
||||
}
|
||||
|
||||
ImNodes::EndNode();
|
||||
}
|
||||
break;
|
||||
case UiNodeType::time:
|
||||
{
|
||||
ImNodes::BeginNode(node.id);
|
||||
|
||||
ImNodes::BeginNodeTitleBar();
|
||||
ImGui::TextUnformatted("time");
|
||||
ImNodes::EndNodeTitleBar();
|
||||
|
||||
ImNodes::BeginOutputAttribute(node.id);
|
||||
ImGui::Text("output");
|
||||
ImNodes::EndOutputAttribute();
|
||||
|
||||
ImNodes::EndNode();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& edge : graph_.edges())
|
||||
{
|
||||
// If edge doesn't start at value, then it's an internal edge, i.e.
|
||||
// an edge which links a node's operation to its input. We don't
|
||||
// want to render node internals with visible links.
|
||||
if (graph_.node(edge.from).type != NodeType::value)
|
||||
continue;
|
||||
|
||||
ImNodes::Link(edge.id, edge.from, edge.to);
|
||||
}
|
||||
|
||||
ImNodes::MiniMap(0.2f, minimap_location_);
|
||||
ImNodes::EndNodeEditor();
|
||||
|
||||
// Handle new links
|
||||
// These are driven by Imnodes, so we place the code after EndNodeEditor().
|
||||
|
||||
{
|
||||
int start_attr, end_attr;
|
||||
if (ImNodes::IsLinkCreated(&start_attr, &end_attr))
|
||||
{
|
||||
const NodeType start_type = graph_.node(start_attr).type;
|
||||
const NodeType end_type = graph_.node(end_attr).type;
|
||||
|
||||
const bool valid_link = start_type != end_type;
|
||||
if (valid_link)
|
||||
{
|
||||
// Ensure the edge is always directed from the value to
|
||||
// whatever produces the value
|
||||
if (start_type != NodeType::value)
|
||||
{
|
||||
std::swap(start_attr, end_attr);
|
||||
}
|
||||
graph_.insert_edge(start_attr, end_attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle deleted links
|
||||
|
||||
{
|
||||
int link_id;
|
||||
if (ImNodes::IsLinkDestroyed(&link_id))
|
||||
{
|
||||
graph_.erase_edge(link_id);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const int num_selected = ImNodes::NumSelectedLinks();
|
||||
if (num_selected > 0 && ImGui::IsKeyReleased(SDL_SCANCODE_X))
|
||||
{
|
||||
static std::vector<int> selected_links;
|
||||
selected_links.resize(static_cast<size_t>(num_selected));
|
||||
ImNodes::GetSelectedLinks(selected_links.data());
|
||||
for (const int edge_id : selected_links)
|
||||
{
|
||||
graph_.erase_edge(edge_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const int num_selected = ImNodes::NumSelectedNodes();
|
||||
if (num_selected > 0 && ImGui::IsKeyReleased(SDL_SCANCODE_X))
|
||||
{
|
||||
static std::vector<int> selected_nodes;
|
||||
selected_nodes.resize(static_cast<size_t>(num_selected));
|
||||
ImNodes::GetSelectedNodes(selected_nodes.data());
|
||||
for (const int node_id : selected_nodes)
|
||||
{
|
||||
graph_.erase_node(node_id);
|
||||
auto iter = std::find_if(
|
||||
nodes_.begin(), nodes_.end(), [node_id](const UiNode& node) -> bool {
|
||||
return node.id == node_id;
|
||||
});
|
||||
// Erase any additional internal nodes
|
||||
switch (iter->type)
|
||||
{
|
||||
case UiNodeType::add:
|
||||
graph_.erase_node(iter->add.lhs);
|
||||
graph_.erase_node(iter->add.rhs);
|
||||
break;
|
||||
case UiNodeType::multiply:
|
||||
graph_.erase_node(iter->multiply.lhs);
|
||||
graph_.erase_node(iter->multiply.rhs);
|
||||
break;
|
||||
case UiNodeType::output:
|
||||
graph_.erase_node(iter->output.r);
|
||||
graph_.erase_node(iter->output.g);
|
||||
graph_.erase_node(iter->output.b);
|
||||
root_node_id_ = -1;
|
||||
break;
|
||||
case UiNodeType::sine:
|
||||
graph_.erase_node(iter->sine.input);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
nodes_.erase(iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
|
||||
// The color output window
|
||||
|
||||
const ImU32 color =
|
||||
root_node_id_ != -1 ? evaluate(graph_, root_node_id_) : IM_COL32(255, 20, 147, 255);
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, color);
|
||||
ImGui::Begin("output color");
|
||||
ImGui::End();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
private:
|
||||
enum class UiNodeType
|
||||
{
|
||||
add,
|
||||
multiply,
|
||||
output,
|
||||
sine,
|
||||
time,
|
||||
};
|
||||
|
||||
struct UiNode
|
||||
{
|
||||
UiNodeType type;
|
||||
// The identifying id of the ui node. For add, multiply, sine, and time
|
||||
// this is the "operation" node id. The additional input nodes are
|
||||
// stored in the structs.
|
||||
int id;
|
||||
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
int lhs, rhs;
|
||||
} add;
|
||||
|
||||
struct
|
||||
{
|
||||
int lhs, rhs;
|
||||
} multiply;
|
||||
|
||||
struct
|
||||
{
|
||||
int r, g, b;
|
||||
} output;
|
||||
|
||||
struct
|
||||
{
|
||||
int input;
|
||||
} sine;
|
||||
};
|
||||
};
|
||||
|
||||
Graph<Node> graph_;
|
||||
std::vector<UiNode> nodes_;
|
||||
int root_node_id_;
|
||||
ImNodesMiniMapLocation minimap_location_;
|
||||
};
|
||||
|
||||
static ColorNodeEditor color_editor;
|
||||
} // namespace
|
||||
|
||||
void NodeEditorInitialize()
|
||||
{
|
||||
ImNodesIO& io = ImNodes::GetIO();
|
||||
io.LinkDetachWithModifierClick.Modifier = &ImGui::GetIO().KeyCtrl;
|
||||
}
|
||||
|
||||
void NodeEditorShow() { color_editor.show(); }
|
||||
|
||||
void NodeEditorShutdown() {}
|
||||
} // namespace example
|
||||
Vendored
+357
@@ -0,0 +1,357 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <iterator>
|
||||
#include <stack>
|
||||
#include <stddef.h>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace example
|
||||
{
|
||||
template<typename ElementType>
|
||||
struct Span
|
||||
{
|
||||
using iterator = ElementType*;
|
||||
|
||||
template<typename Container>
|
||||
Span(Container& c) : begin_(c.data()), end_(begin_ + c.size())
|
||||
{
|
||||
}
|
||||
|
||||
iterator begin() const { return begin_; }
|
||||
iterator end() const { return end_; }
|
||||
|
||||
private:
|
||||
iterator begin_;
|
||||
iterator end_;
|
||||
};
|
||||
|
||||
template<typename ElementType>
|
||||
class IdMap
|
||||
{
|
||||
public:
|
||||
using iterator = typename std::vector<ElementType>::iterator;
|
||||
using const_iterator = typename std::vector<ElementType>::const_iterator;
|
||||
|
||||
// Iterators
|
||||
|
||||
const_iterator begin() const { return elements_.begin(); }
|
||||
const_iterator end() const { return elements_.end(); }
|
||||
|
||||
// Element access
|
||||
|
||||
Span<const ElementType> elements() const { return elements_; }
|
||||
|
||||
// Capacity
|
||||
|
||||
bool empty() const { return sorted_ids_.empty(); }
|
||||
size_t size() const { return sorted_ids_.size(); }
|
||||
|
||||
// Modifiers
|
||||
|
||||
std::pair<iterator, bool> insert(int id, const ElementType& element);
|
||||
std::pair<iterator, bool> insert(int id, ElementType&& element);
|
||||
size_t erase(int id);
|
||||
void clear();
|
||||
|
||||
// Lookup
|
||||
|
||||
iterator find(int id);
|
||||
const_iterator find(int id) const;
|
||||
bool contains(int id) const;
|
||||
|
||||
private:
|
||||
std::vector<ElementType> elements_;
|
||||
std::vector<int> sorted_ids_;
|
||||
};
|
||||
|
||||
template<typename ElementType>
|
||||
std::pair<typename IdMap<ElementType>::iterator, bool> IdMap<ElementType>::insert(
|
||||
const int id,
|
||||
const ElementType& element)
|
||||
{
|
||||
auto lower_bound = std::lower_bound(sorted_ids_.begin(), sorted_ids_.end(), id);
|
||||
|
||||
if (lower_bound != sorted_ids_.end() && id == *lower_bound)
|
||||
{
|
||||
return std::make_pair(
|
||||
std::next(elements_.begin(), std::distance(sorted_ids_.begin(), lower_bound)), false);
|
||||
}
|
||||
|
||||
auto insert_element_at =
|
||||
std::next(elements_.begin(), std::distance(sorted_ids_.begin(), lower_bound));
|
||||
|
||||
sorted_ids_.insert(lower_bound, id);
|
||||
return std::make_pair(elements_.insert(insert_element_at, element), true);
|
||||
}
|
||||
|
||||
template<typename ElementType>
|
||||
std::pair<typename IdMap<ElementType>::iterator, bool> IdMap<ElementType>::insert(
|
||||
const int id,
|
||||
ElementType&& element)
|
||||
{
|
||||
auto lower_bound = std::lower_bound(sorted_ids_.begin(), sorted_ids_.end(), id);
|
||||
|
||||
if (lower_bound != sorted_ids_.end() && id == *lower_bound)
|
||||
{
|
||||
return std::make_pair(
|
||||
std::next(elements_.begin(), std::distance(sorted_ids_.begin(), lower_bound)), false);
|
||||
}
|
||||
|
||||
auto insert_element_at =
|
||||
std::next(elements_.begin(), std::distance(sorted_ids_.begin(), lower_bound));
|
||||
|
||||
sorted_ids_.insert(lower_bound, id);
|
||||
return std::make_pair(elements_.insert(insert_element_at, std::move(element)), true);
|
||||
}
|
||||
|
||||
template<typename ElementType>
|
||||
size_t IdMap<ElementType>::erase(const int id)
|
||||
{
|
||||
auto lower_bound = std::lower_bound(sorted_ids_.begin(), sorted_ids_.end(), id);
|
||||
|
||||
if (lower_bound == sorted_ids_.end() || id != *lower_bound)
|
||||
{
|
||||
return 0ull;
|
||||
}
|
||||
|
||||
auto erase_element_at =
|
||||
std::next(elements_.begin(), std::distance(sorted_ids_.begin(), lower_bound));
|
||||
|
||||
sorted_ids_.erase(lower_bound);
|
||||
elements_.erase(erase_element_at);
|
||||
|
||||
return 1ull;
|
||||
}
|
||||
|
||||
template<typename ElementType>
|
||||
void IdMap<ElementType>::clear()
|
||||
{
|
||||
elements_.clear();
|
||||
sorted_ids_.clear();
|
||||
}
|
||||
|
||||
template<typename ElementType>
|
||||
typename IdMap<ElementType>::iterator IdMap<ElementType>::find(const int id)
|
||||
{
|
||||
const auto lower_bound = std::lower_bound(sorted_ids_.cbegin(), sorted_ids_.cend(), id);
|
||||
return (lower_bound == sorted_ids_.cend() || *lower_bound != id)
|
||||
? elements_.end()
|
||||
: std::next(elements_.begin(), std::distance(sorted_ids_.cbegin(), lower_bound));
|
||||
}
|
||||
|
||||
template<typename ElementType>
|
||||
typename IdMap<ElementType>::const_iterator IdMap<ElementType>::find(const int id) const
|
||||
{
|
||||
const auto lower_bound = std::lower_bound(sorted_ids_.cbegin(), sorted_ids_.cend(), id);
|
||||
return (lower_bound == sorted_ids_.cend() || *lower_bound != id)
|
||||
? elements_.cend()
|
||||
: std::next(elements_.cbegin(), std::distance(sorted_ids_.cbegin(), lower_bound));
|
||||
}
|
||||
|
||||
template<typename ElementType>
|
||||
bool IdMap<ElementType>::contains(const int id) const
|
||||
{
|
||||
const auto lower_bound = std::lower_bound(sorted_ids_.cbegin(), sorted_ids_.cend(), id);
|
||||
|
||||
if (lower_bound == sorted_ids_.cend())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return *lower_bound == id;
|
||||
}
|
||||
|
||||
// a very simple directional graph
|
||||
template<typename NodeType>
|
||||
class Graph
|
||||
{
|
||||
public:
|
||||
Graph() : current_id_(0), nodes_(), edges_from_node_(), node_neighbors_(), edges_() {}
|
||||
|
||||
struct Edge
|
||||
{
|
||||
int id;
|
||||
int from, to;
|
||||
|
||||
Edge() = default;
|
||||
Edge(const int id, const int f, const int t) : id(id), from(f), to(t) {}
|
||||
|
||||
inline int opposite(const int n) const { return n == from ? to : from; }
|
||||
inline bool contains(const int n) const { return n == from || n == to; }
|
||||
};
|
||||
|
||||
// Element access
|
||||
|
||||
NodeType& node(int node_id);
|
||||
const NodeType& node(int node_id) const;
|
||||
Span<const int> neighbors(int node_id) const;
|
||||
Span<const Edge> edges() const;
|
||||
|
||||
// Capacity
|
||||
|
||||
size_t num_edges_from_node(int node_id) const;
|
||||
|
||||
// Modifiers
|
||||
|
||||
int insert_node(const NodeType& node);
|
||||
void erase_node(int node_id);
|
||||
|
||||
int insert_edge(int from, int to);
|
||||
void erase_edge(int edge_id);
|
||||
|
||||
private:
|
||||
int current_id_;
|
||||
// These contains map to the node id
|
||||
IdMap<NodeType> nodes_;
|
||||
IdMap<int> edges_from_node_;
|
||||
IdMap<std::vector<int>> node_neighbors_;
|
||||
|
||||
// This container maps to the edge id
|
||||
IdMap<Edge> edges_;
|
||||
};
|
||||
|
||||
template<typename NodeType>
|
||||
NodeType& Graph<NodeType>::node(const int id)
|
||||
{
|
||||
return const_cast<NodeType&>(static_cast<const Graph*>(this)->node(id));
|
||||
}
|
||||
|
||||
template<typename NodeType>
|
||||
const NodeType& Graph<NodeType>::node(const int id) const
|
||||
{
|
||||
const auto iter = nodes_.find(id);
|
||||
assert(iter != nodes_.end());
|
||||
return *iter;
|
||||
}
|
||||
|
||||
template<typename NodeType>
|
||||
Span<const int> Graph<NodeType>::neighbors(int node_id) const
|
||||
{
|
||||
const auto iter = node_neighbors_.find(node_id);
|
||||
assert(iter != node_neighbors_.end());
|
||||
return *iter;
|
||||
}
|
||||
|
||||
template<typename NodeType>
|
||||
Span<const typename Graph<NodeType>::Edge> Graph<NodeType>::edges() const
|
||||
{
|
||||
return edges_.elements();
|
||||
}
|
||||
|
||||
template<typename NodeType>
|
||||
size_t Graph<NodeType>::num_edges_from_node(const int id) const
|
||||
{
|
||||
auto iter = edges_from_node_.find(id);
|
||||
assert(iter != edges_from_node_.end());
|
||||
return *iter;
|
||||
}
|
||||
|
||||
template<typename NodeType>
|
||||
int Graph<NodeType>::insert_node(const NodeType& node)
|
||||
{
|
||||
const int id = current_id_++;
|
||||
assert(!nodes_.contains(id));
|
||||
nodes_.insert(id, node);
|
||||
edges_from_node_.insert(id, 0);
|
||||
node_neighbors_.insert(id, std::vector<int>());
|
||||
return id;
|
||||
}
|
||||
|
||||
template<typename NodeType>
|
||||
void Graph<NodeType>::erase_node(const int id)
|
||||
{
|
||||
|
||||
// first, remove any potential dangling edges
|
||||
{
|
||||
static std::vector<int> edges_to_erase;
|
||||
|
||||
for (const Edge& edge : edges_.elements())
|
||||
{
|
||||
if (edge.contains(id))
|
||||
{
|
||||
edges_to_erase.push_back(edge.id);
|
||||
}
|
||||
}
|
||||
|
||||
for (const int edge_id : edges_to_erase)
|
||||
{
|
||||
erase_edge(edge_id);
|
||||
}
|
||||
|
||||
edges_to_erase.clear();
|
||||
}
|
||||
|
||||
nodes_.erase(id);
|
||||
edges_from_node_.erase(id);
|
||||
node_neighbors_.erase(id);
|
||||
}
|
||||
|
||||
template<typename NodeType>
|
||||
int Graph<NodeType>::insert_edge(const int from, const int to)
|
||||
{
|
||||
const int id = current_id_++;
|
||||
assert(!edges_.contains(id));
|
||||
assert(nodes_.contains(from));
|
||||
assert(nodes_.contains(to));
|
||||
edges_.insert(id, Edge(id, from, to));
|
||||
|
||||
// update neighbor count
|
||||
assert(edges_from_node_.contains(from));
|
||||
*edges_from_node_.find(from) += 1;
|
||||
// update neighbor list
|
||||
assert(node_neighbors_.contains(from));
|
||||
node_neighbors_.find(from)->push_back(to);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
template<typename NodeType>
|
||||
void Graph<NodeType>::erase_edge(const int edge_id)
|
||||
{
|
||||
// This is a bit lazy, we find the pointer here, but we refind it when we erase the edge based
|
||||
// on id key.
|
||||
assert(edges_.contains(edge_id));
|
||||
const Edge& edge = *edges_.find(edge_id);
|
||||
|
||||
// update neighbor count
|
||||
assert(edges_from_node_.contains(edge.from));
|
||||
int& edge_count = *edges_from_node_.find(edge.from);
|
||||
assert(edge_count > 0);
|
||||
edge_count -= 1;
|
||||
|
||||
// update neighbor list
|
||||
{
|
||||
assert(node_neighbors_.contains(edge.from));
|
||||
auto neighbors = node_neighbors_.find(edge.from);
|
||||
auto iter = std::find(neighbors->begin(), neighbors->end(), edge.to);
|
||||
assert(iter != neighbors->end());
|
||||
neighbors->erase(iter);
|
||||
}
|
||||
|
||||
edges_.erase(edge_id);
|
||||
}
|
||||
|
||||
template<typename NodeType, typename Visitor>
|
||||
void dfs_traverse(const Graph<NodeType>& graph, const int start_node, Visitor visitor)
|
||||
{
|
||||
std::stack<int> stack;
|
||||
|
||||
stack.push(start_node);
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
const int current_node = stack.top();
|
||||
stack.pop();
|
||||
|
||||
visitor(current_node);
|
||||
|
||||
for (const int neighbor : graph.neighbors(current_node))
|
||||
{
|
||||
stack.push(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace example
|
||||
Vendored
+48
@@ -0,0 +1,48 @@
|
||||
#include "node_editor.h"
|
||||
#include <imnodes.h>
|
||||
#include <imgui.h>
|
||||
|
||||
namespace example
|
||||
{
|
||||
namespace
|
||||
{
|
||||
class HelloWorldNodeEditor
|
||||
{
|
||||
public:
|
||||
void show()
|
||||
{
|
||||
ImGui::Begin("simple node editor");
|
||||
|
||||
ImNodes::BeginNodeEditor();
|
||||
ImNodes::BeginNode(1);
|
||||
|
||||
ImNodes::BeginNodeTitleBar();
|
||||
ImGui::TextUnformatted("simple node :)");
|
||||
ImNodes::EndNodeTitleBar();
|
||||
|
||||
ImNodes::BeginInputAttribute(2);
|
||||
ImGui::Text("input");
|
||||
ImNodes::EndInputAttribute();
|
||||
|
||||
ImNodes::BeginOutputAttribute(3);
|
||||
ImGui::Indent(40);
|
||||
ImGui::Text("output");
|
||||
ImNodes::EndOutputAttribute();
|
||||
|
||||
ImNodes::EndNode();
|
||||
ImNodes::EndNodeEditor();
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
};
|
||||
|
||||
static HelloWorldNodeEditor editor;
|
||||
} // namespace
|
||||
|
||||
void NodeEditorInitialize() { ImNodes::SetNodeGridSpacePos(1, ImVec2(200.0f, 200.0f)); }
|
||||
|
||||
void NodeEditorShow() { editor.show(); }
|
||||
|
||||
void NodeEditorShutdown() {}
|
||||
|
||||
} // namespace example
|
||||
Vendored
+128
@@ -0,0 +1,128 @@
|
||||
#include "node_editor.h"
|
||||
#include <imgui.h>
|
||||
#include <imgui_impl_sdl.h>
|
||||
#include <imgui_impl_opengl3.h>
|
||||
#include <imnodes.h>
|
||||
#include <stdio.h>
|
||||
#include <SDL.h>
|
||||
#include <GL/gl3w.h>
|
||||
|
||||
int main(int, char**)
|
||||
{
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) != 0)
|
||||
{
|
||||
printf("Error: %s\n", SDL_GetError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
#if __APPLE__
|
||||
// GL 3.2 Core + GLSL 150
|
||||
const char* glsl_version = "#version 150";
|
||||
SDL_GL_SetAttribute(
|
||||
SDL_GL_CONTEXT_FLAGS,
|
||||
SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
|
||||
#else
|
||||
// GL 3.0 + GLSL 130
|
||||
const char* glsl_version = "#version 130";
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
||||
#endif
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
||||
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
||||
SDL_DisplayMode current;
|
||||
SDL_GetCurrentDisplayMode(0, ¤t);
|
||||
SDL_Window* window = SDL_CreateWindow(
|
||||
"imgui-node-editor example",
|
||||
SDL_WINDOWPOS_CENTERED,
|
||||
SDL_WINDOWPOS_CENTERED,
|
||||
1280,
|
||||
720,
|
||||
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||
SDL_GLContext gl_context = SDL_GL_CreateContext(window);
|
||||
SDL_GL_MakeCurrent(window, gl_context);
|
||||
SDL_GL_SetSwapInterval(1); // Enable vsync
|
||||
|
||||
if (gl3wInit())
|
||||
{
|
||||
fprintf(stderr, "Failed to initialize OpenGL loader!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
|
||||
ImGui_ImplSDL2_InitForOpenGL(window, gl_context);
|
||||
ImGui_ImplOpenGL3_Init(glsl_version);
|
||||
|
||||
ImNodes::CreateContext();
|
||||
|
||||
// Setup style
|
||||
ImGui::StyleColorsDark();
|
||||
ImNodes::StyleColorsDark();
|
||||
|
||||
bool done = false;
|
||||
bool initialized = false;
|
||||
|
||||
{
|
||||
const ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
|
||||
glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w);
|
||||
}
|
||||
|
||||
while (!done)
|
||||
{
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event))
|
||||
{
|
||||
ImGui_ImplSDL2_ProcessEvent(&event);
|
||||
if (event.type == SDL_QUIT)
|
||||
done = true;
|
||||
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE &&
|
||||
event.window.windowID == SDL_GetWindowID(window))
|
||||
done = true;
|
||||
}
|
||||
|
||||
// Start the Dear ImGui frame
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplSDL2_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
if (!initialized)
|
||||
{
|
||||
initialized = true;
|
||||
example::NodeEditorInitialize();
|
||||
}
|
||||
|
||||
example::NodeEditorShow();
|
||||
|
||||
// Rendering
|
||||
ImGui::Render();
|
||||
|
||||
int fb_width, fb_height;
|
||||
SDL_GL_GetDrawableSize(window, &fb_width, &fb_height);
|
||||
glViewport(0, 0, fb_width, fb_height);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
SDL_GL_SwapWindow(window);
|
||||
}
|
||||
|
||||
example::NodeEditorShutdown();
|
||||
ImNodes::DestroyContext();
|
||||
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplSDL2_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
|
||||
SDL_GL_DeleteContext(gl_context);
|
||||
SDL_DestroyWindow(window);
|
||||
SDL_Quit();
|
||||
|
||||
return 0;
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
#include "node_editor.h"
|
||||
#include <imnodes.h>
|
||||
#include <imgui.h>
|
||||
#include <SDL_scancode.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
namespace example
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct Node
|
||||
{
|
||||
int id;
|
||||
float value;
|
||||
|
||||
Node(const int i, const float v) : id(i), value(v) {}
|
||||
};
|
||||
|
||||
struct Link
|
||||
{
|
||||
int id;
|
||||
int start_attr, end_attr;
|
||||
};
|
||||
|
||||
struct Editor
|
||||
{
|
||||
ImNodesEditorContext* context = nullptr;
|
||||
std::vector<Node> nodes;
|
||||
std::vector<Link> links;
|
||||
int current_id = 0;
|
||||
};
|
||||
|
||||
void show_editor(const char* editor_name, Editor& editor)
|
||||
{
|
||||
ImNodes::EditorContextSet(editor.context);
|
||||
|
||||
ImGui::Begin(editor_name);
|
||||
ImGui::TextUnformatted("A -- add node");
|
||||
|
||||
ImNodes::BeginNodeEditor();
|
||||
|
||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
|
||||
ImNodes::IsEditorHovered() && ImGui::IsKeyReleased(SDL_SCANCODE_A))
|
||||
{
|
||||
const int node_id = ++editor.current_id;
|
||||
ImNodes::SetNodeScreenSpacePos(node_id, ImGui::GetMousePos());
|
||||
editor.nodes.push_back(Node(node_id, 0.f));
|
||||
}
|
||||
|
||||
for (Node& node : editor.nodes)
|
||||
{
|
||||
ImNodes::BeginNode(node.id);
|
||||
|
||||
ImNodes::BeginNodeTitleBar();
|
||||
ImGui::TextUnformatted("node");
|
||||
ImNodes::EndNodeTitleBar();
|
||||
|
||||
ImNodes::BeginInputAttribute(node.id << 8);
|
||||
ImGui::TextUnformatted("input");
|
||||
ImNodes::EndInputAttribute();
|
||||
|
||||
ImNodes::BeginStaticAttribute(node.id << 16);
|
||||
ImGui::PushItemWidth(120.0f);
|
||||
ImGui::DragFloat("value", &node.value, 0.01f);
|
||||
ImGui::PopItemWidth();
|
||||
ImNodes::EndStaticAttribute();
|
||||
|
||||
ImNodes::BeginOutputAttribute(node.id << 24);
|
||||
const float text_width = ImGui::CalcTextSize("output").x;
|
||||
ImGui::Indent(120.f + ImGui::CalcTextSize("value").x - text_width);
|
||||
ImGui::TextUnformatted("output");
|
||||
ImNodes::EndOutputAttribute();
|
||||
|
||||
ImNodes::EndNode();
|
||||
}
|
||||
|
||||
for (const Link& link : editor.links)
|
||||
{
|
||||
ImNodes::Link(link.id, link.start_attr, link.end_attr);
|
||||
}
|
||||
|
||||
ImNodes::EndNodeEditor();
|
||||
|
||||
{
|
||||
Link link;
|
||||
if (ImNodes::IsLinkCreated(&link.start_attr, &link.end_attr))
|
||||
{
|
||||
link.id = ++editor.current_id;
|
||||
editor.links.push_back(link);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
int link_id;
|
||||
if (ImNodes::IsLinkDestroyed(&link_id))
|
||||
{
|
||||
auto iter = std::find_if(
|
||||
editor.links.begin(), editor.links.end(), [link_id](const Link& link) -> bool {
|
||||
return link.id == link_id;
|
||||
});
|
||||
assert(iter != editor.links.end());
|
||||
editor.links.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
Editor editor1;
|
||||
Editor editor2;
|
||||
} // namespace
|
||||
|
||||
void NodeEditorInitialize()
|
||||
{
|
||||
editor1.context = ImNodes::EditorContextCreate();
|
||||
editor2.context = ImNodes::EditorContextCreate();
|
||||
ImNodes::PushAttributeFlag(ImNodesAttributeFlags_EnableLinkDetachWithDragClick);
|
||||
|
||||
ImNodesIO& io = ImNodes::GetIO();
|
||||
io.LinkDetachWithModifierClick.Modifier = &ImGui::GetIO().KeyCtrl;
|
||||
}
|
||||
|
||||
void NodeEditorShow()
|
||||
{
|
||||
show_editor("editor1", editor1);
|
||||
show_editor("editor2", editor2);
|
||||
}
|
||||
|
||||
void NodeEditorShutdown()
|
||||
{
|
||||
ImNodes::PopAttributeFlag();
|
||||
ImNodes::EditorContextFree(editor1.context);
|
||||
ImNodes::EditorContextFree(editor2.context);
|
||||
}
|
||||
} // namespace example
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
namespace example
|
||||
{
|
||||
void NodeEditorInitialize();
|
||||
void NodeEditorShow();
|
||||
void NodeEditorShutdown();
|
||||
} // namespace example
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
#include "node_editor.h"
|
||||
|
||||
#include <imnodes.h>
|
||||
#include <imgui.h>
|
||||
#include <SDL_keycode.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
#include <ios> // for std::streamsize
|
||||
#include <stddef.h>
|
||||
#include <vector>
|
||||
|
||||
namespace example
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct Node
|
||||
{
|
||||
int id;
|
||||
float value;
|
||||
|
||||
Node() = default;
|
||||
|
||||
Node(const int i, const float v) : id(i), value(v) {}
|
||||
};
|
||||
|
||||
struct Link
|
||||
{
|
||||
int id;
|
||||
int start_attr, end_attr;
|
||||
};
|
||||
|
||||
class SaveLoadEditor
|
||||
{
|
||||
public:
|
||||
SaveLoadEditor() : nodes_(), links_(), current_id_(0) {}
|
||||
|
||||
void show()
|
||||
{
|
||||
ImGui::Begin("Save & load example");
|
||||
ImGui::TextUnformatted("A -- add node");
|
||||
ImGui::TextUnformatted(
|
||||
"Close the executable and rerun it -- your nodes should be exactly "
|
||||
"where you left them!");
|
||||
|
||||
ImNodes::BeginNodeEditor();
|
||||
|
||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
|
||||
ImNodes::IsEditorHovered() && ImGui::IsKeyReleased(SDL_SCANCODE_A))
|
||||
{
|
||||
const int node_id = ++current_id_;
|
||||
ImNodes::SetNodeScreenSpacePos(node_id, ImGui::GetMousePos());
|
||||
nodes_.push_back(Node(node_id, 0.f));
|
||||
}
|
||||
|
||||
for (Node& node : nodes_)
|
||||
{
|
||||
ImNodes::BeginNode(node.id);
|
||||
|
||||
ImNodes::BeginNodeTitleBar();
|
||||
ImGui::TextUnformatted("node");
|
||||
ImNodes::EndNodeTitleBar();
|
||||
|
||||
ImNodes::BeginInputAttribute(node.id << 8);
|
||||
ImGui::TextUnformatted("input");
|
||||
ImNodes::EndInputAttribute();
|
||||
|
||||
ImNodes::BeginStaticAttribute(node.id << 16);
|
||||
ImGui::PushItemWidth(120.f);
|
||||
ImGui::DragFloat("value", &node.value, 0.01f);
|
||||
ImGui::PopItemWidth();
|
||||
ImNodes::EndStaticAttribute();
|
||||
|
||||
ImNodes::BeginOutputAttribute(node.id << 24);
|
||||
const float text_width = ImGui::CalcTextSize("output").x;
|
||||
ImGui::Indent(120.f + ImGui::CalcTextSize("value").x - text_width);
|
||||
ImGui::TextUnformatted("output");
|
||||
ImNodes::EndOutputAttribute();
|
||||
|
||||
ImNodes::EndNode();
|
||||
}
|
||||
|
||||
for (const Link& link : links_)
|
||||
{
|
||||
ImNodes::Link(link.id, link.start_attr, link.end_attr);
|
||||
}
|
||||
|
||||
ImNodes::EndNodeEditor();
|
||||
|
||||
{
|
||||
Link link;
|
||||
if (ImNodes::IsLinkCreated(&link.start_attr, &link.end_attr))
|
||||
{
|
||||
link.id = ++current_id_;
|
||||
links_.push_back(link);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
int link_id;
|
||||
if (ImNodes::IsLinkDestroyed(&link_id))
|
||||
{
|
||||
auto iter =
|
||||
std::find_if(links_.begin(), links_.end(), [link_id](const Link& link) -> bool {
|
||||
return link.id == link_id;
|
||||
});
|
||||
assert(iter != links_.end());
|
||||
links_.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void save()
|
||||
{
|
||||
// Save the internal imnodes state
|
||||
ImNodes::SaveCurrentEditorStateToIniFile("save_load.ini");
|
||||
|
||||
// Dump our editor state as bytes into a file
|
||||
|
||||
std::fstream fout(
|
||||
"save_load.bytes", std::ios_base::out | std::ios_base::binary | std::ios_base::trunc);
|
||||
|
||||
// copy the node vector to file
|
||||
const size_t num_nodes = nodes_.size();
|
||||
fout.write(
|
||||
reinterpret_cast<const char*>(&num_nodes),
|
||||
static_cast<std::streamsize>(sizeof(size_t)));
|
||||
fout.write(
|
||||
reinterpret_cast<const char*>(nodes_.data()),
|
||||
static_cast<std::streamsize>(sizeof(Node) * num_nodes));
|
||||
|
||||
// copy the link vector to file
|
||||
const size_t num_links = links_.size();
|
||||
fout.write(
|
||||
reinterpret_cast<const char*>(&num_links),
|
||||
static_cast<std::streamsize>(sizeof(size_t)));
|
||||
fout.write(
|
||||
reinterpret_cast<const char*>(links_.data()),
|
||||
static_cast<std::streamsize>(sizeof(Link) * num_links));
|
||||
|
||||
// copy the current_id to file
|
||||
fout.write(
|
||||
reinterpret_cast<const char*>(¤t_id_), static_cast<std::streamsize>(sizeof(int)));
|
||||
}
|
||||
|
||||
void load()
|
||||
{
|
||||
// Load the internal imnodes state
|
||||
ImNodes::LoadCurrentEditorStateFromIniFile("save_load.ini");
|
||||
|
||||
// Load our editor state into memory
|
||||
|
||||
std::fstream fin("save_load.bytes", std::ios_base::in | std::ios_base::binary);
|
||||
|
||||
if (!fin.is_open())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// copy nodes into memory
|
||||
size_t num_nodes;
|
||||
fin.read(reinterpret_cast<char*>(&num_nodes), static_cast<std::streamsize>(sizeof(size_t)));
|
||||
nodes_.resize(num_nodes);
|
||||
fin.read(
|
||||
reinterpret_cast<char*>(nodes_.data()),
|
||||
static_cast<std::streamsize>(sizeof(Node) * num_nodes));
|
||||
|
||||
// copy links into memory
|
||||
size_t num_links;
|
||||
fin.read(reinterpret_cast<char*>(&num_links), static_cast<std::streamsize>(sizeof(size_t)));
|
||||
links_.resize(num_links);
|
||||
fin.read(
|
||||
reinterpret_cast<char*>(links_.data()),
|
||||
static_cast<std::streamsize>(sizeof(Link) * num_links));
|
||||
|
||||
// copy current_id into memory
|
||||
fin.read(reinterpret_cast<char*>(¤t_id_), static_cast<std::streamsize>(sizeof(int)));
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Node> nodes_;
|
||||
std::vector<Link> links_;
|
||||
int current_id_;
|
||||
};
|
||||
|
||||
static SaveLoadEditor editor;
|
||||
} // namespace
|
||||
|
||||
void NodeEditorInitialize()
|
||||
{
|
||||
ImNodes::GetIO().LinkDetachWithModifierClick.Modifier = &ImGui::GetIO().KeyCtrl;
|
||||
ImNodes::PushAttributeFlag(ImNodesAttributeFlags_EnableLinkDetachWithDragClick);
|
||||
editor.load();
|
||||
}
|
||||
|
||||
void NodeEditorShow() { editor.show(); }
|
||||
|
||||
void NodeEditorShutdown()
|
||||
{
|
||||
ImNodes::PopAttributeFlag();
|
||||
editor.save();
|
||||
}
|
||||
} // namespace example
|
||||
Reference in New Issue
Block a user