node editor: dragging!
[zinnia.git] / src / gui / node_editor.h
blobe98e5d068f4356d280bb0e003c9f3371e911bc6c
1 #pragma once
3 #include <string>
4 #include <vector>
6 #define IMGUI_DEFINE_MATH_OPERATORS
7 #include <imgui.h>
8 #include <imgui_internal.h>
10 #include "../geom.h"
12 class GuiNode {
13 public:
14 GuiNode(std::string name, Rect bbox, uint n_slots_in, uint n_slots_out)
15 : name(name), bbox(bbox), n_slots_in(n_slots_in), n_slots_out(n_slots_out)
18 enum SlotDirection {
19 IN,
20 OUT,
23 ImVec2 slot_location(SlotDirection direction, uint i) const {
24 uint n_slots;
25 switch (direction) {
26 case IN:
27 n_slots = n_slots_in; break;
28 case OUT:
29 n_slots = n_slots_out; break;
32 auto x_stride = bbox.size.y/(n_slots+1);
34 ImVec2 base;
35 switch(direction) {
36 case IN:
37 base = bbox.pos; break;
38 case OUT:
39 base = bbox.pos + ImVec2(bbox.size.x, 0); break;
42 return base + ImVec2(0, (i + 1) * x_stride);
45 uint n_slots_in;
46 uint n_slots_out;
48 std::string name;
49 Rect bbox;
52 class GuiEdge {
53 public:
54 GuiEdge(GuiNode *lhs, GuiNode *rhs, uint fromSlot, uint toSlot)
55 : lhs(lhs), rhs(rhs), fromSlot(fromSlot), toSlot(toSlot) {}
58 GuiNode *lhs;
59 GuiNode *rhs;
60 uint fromSlot;
61 uint toSlot;
64 class NodeEditor {
65 public:
66 NodeEditor() {
67 nodeList.emplace_back("hi", Rect(10, 10, 60, 60), 3, 1);
68 nodeList.emplace_back("hi2", Rect(80, 10, 60, 60), 1, 1);
70 edgeList.emplace_back(&nodeList[0], &nodeList[1], 0, 0);
73 void draw_window(const char *title, bool *opened) {
74 ImGui::SetNextWindowSize(ImVec2(700,600), ImGuiSetCond_FirstUseEver);
75 if (ImGui::Begin(title, opened)) {
76 // -- Create child canvas
77 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1,1));
78 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0,0));
79 ImGui::PushStyleColor(ImGuiCol_ChildWindowBg, ImColor(60,60,70,200));
80 ImGui::BeginChild("nodes_region", ImVec2(0, 0), true, ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoMove);
82 // -- Prepare to draw
83 // Get the draw list for this canvas
84 ImDrawList* draw_list = ImGui::GetWindowDrawList();
85 draw_list->ChannelsSplit(2);
87 // this gets the offset to the upper-right corner of the child canvas.
88 // Note: here, cursor means "draw cursor", not "mouse pointing cursor".
89 ImVec2 canvas_pos = ImGui::GetCursorScreenPos();
90 ImVec2 canvas_size = ImGui::GetWindowSize();
92 ImVec2 offset = canvas_pos - scrolling; // TODO: add scrolling
94 // -- Draw the grid
95 draw_list->ChannelsSetCurrent(0);
96 if (show_grid) {
97 ImVec2 local_offset = ImGui::GetCursorPos() - scrolling;
99 float spacing = 32.0f; ImU32 color = IM_COL32(200, 200, 200, 40);
101 for (float x = fmodf(local_offset.x, spacing); x < canvas_size.x; x += spacing)
102 draw_list->AddLine(ImVec2(x, 0.0f)+canvas_pos,
103 ImVec2(x, canvas_size.y)+canvas_pos, color);
104 for (float y = fmodf(local_offset.y, spacing); y < canvas_size.y; y += spacing)
105 draw_list->AddLine(ImVec2(0.0f, y)+canvas_pos,
106 ImVec2(canvas_size.x, y)+canvas_pos, color);
110 draw_list->ChannelsSetCurrent(0);
111 for (const auto &edge : edgeList) {
112 auto head = offset + edge.lhs->slot_location(GuiNode::OUT, edge.fromSlot);
113 auto tail = offset + edge.rhs->slot_location(GuiNode::IN, edge.fromSlot);
115 draw_list->AddBezierCurve(head, head+ImVec2(50, 0),
116 tail+ImVec2(-50, 0), tail,
117 ImColor(200, 200, 200), 3.0f);
120 Rect viewportRect = Rect(scrolling, canvas_size);
122 for (auto &node : nodeList) {
123 bool visible = viewportRect.intersects(node.bbox);
124 if (visible) {
125 // TODO: this is garbage
126 ImGui::PushID((void *)&node);
128 // -- Draw the node boxes
129 draw_list->ChannelsSetCurrent(0); // Switch to the background layer
131 // draw an invisible button in the background, for dragging/hovering
132 ImGui::SetCursorScreenPos(offset + node.bbox.pos);
133 ImGui::InvisibleButton("node", node.bbox.size);
135 if (ImGui::IsItemActive() && ImGui::IsMouseDragging(0))
136 node.bbox.pos += ImGui::GetIO().MouseDelta;
138 ImVec2 upper_left = offset + node.bbox.pos;
139 ImVec2 lower_right = upper_left + node.bbox.size;
141 draw_list->AddRectFilled(upper_left, lower_right, ImColor(60, 60, 60), 4.0f);
142 draw_list->AddRect(upper_left, lower_right, ImColor(100, 100, 100), 4.0f);
144 // -- Draw the connection dots
145 for (int i=0; i<node.n_slots_in; i++) {
146 auto position = node.slot_location(GuiNode::IN, i);
147 draw_list->AddCircleFilled(offset + position, 4.0f, ImColor(150, 150, 150, 150));
150 for (int i=0; i<node.n_slots_out; i++) {
151 auto position = node.slot_location(GuiNode::OUT, i);
152 draw_list->AddCircleFilled(offset + position, 4.0f, ImColor(150, 150, 150, 150));
155 ImGui::PopID();
159 // -- merge channels back together
160 draw_list->ChannelsMerge();
162 // -- scrolling!
163 // [ pointer over neditor ] [ not interacting ctrls ] [ middle mouse dragging ]
164 if (ImGui::IsWindowHovered() && !ImGui::IsAnyItemActive() && ImGui::IsMouseDragging(2, 0.0f))
165 scrolling = scrolling - ImGui::GetIO().MouseDelta;
167 // -- clean up
168 ImGui::EndChild(); // end :nodes_region
169 ImGui::PopStyleColor(); // pop ChildWindowBg
170 ImGui::PopStyleVar(2); // pop FramePadding, WindowPadding
172 ImGui::End();
175 private:
176 std::vector<GuiNode> nodeList;
177 std::vector<GuiEdge> edgeList;
179 ImVec2 scrolling = ImVec2(0.0f, 0.0f);
180 bool show_grid = true;