node_editor.c 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. /*
  2. A basic node-based UI built with Nuklear.
  3. Builds on the node editor example included in Nuklear v1.00, with the aim of
  4. being used as a prototype for implementing a functioning node editor.
  5. Features:
  6. - Nodes of different types. Currently their implementations are #included in
  7. the main file, but they could easily be turned into eg. a plugin system.
  8. - Connectors/slots of different types -- currently float values and colors.
  9. - Adding and removing nodes.
  10. - Linking nodes, with validation (one link per input, only link similar connectors).
  11. - Detaching and moving links.
  12. - Evaluation of output values of connected nodes.
  13. - Memory management based on fixed size arrays for links and node pointers
  14. */
  15. struct node_connector {
  16. enum {fValue, fColor} type;
  17. nk_bool is_connected;
  18. struct node* connected_node;
  19. int connected_slot;
  20. };
  21. struct node {
  22. int ID;
  23. char name[32];
  24. struct nk_rect bounds;
  25. int input_count;
  26. int output_count;
  27. struct node_connector *inputs;
  28. struct node_connector *outputs;
  29. struct {
  30. float in_top;
  31. float in_space;
  32. float out_top;
  33. float out_space;
  34. } slot_spacing; /* Maybe this should be called "node_layout" and include the bounds? */
  35. struct node *next; /* Z ordering only */
  36. struct node *prev; /* Z ordering only */
  37. void* (*eval_func)(struct node*, int oIndex);
  38. void (*display_func)(struct nk_context*, struct node*);
  39. };
  40. struct node_link {
  41. struct node* input_node;
  42. int input_slot;
  43. struct node* output_node;
  44. int output_slot;
  45. nk_bool is_active;
  46. };
  47. struct node_linking {
  48. int active;
  49. struct node *node;
  50. int input_id;
  51. int input_slot;
  52. };
  53. struct node_editor {
  54. int initialized;
  55. struct node *node_buf[32];
  56. struct node_link links[64];
  57. struct node *output_node;
  58. struct node *begin;
  59. struct node *end;
  60. int node_count;
  61. int link_count;
  62. struct nk_rect bounds;
  63. struct node *selected;
  64. int show_grid;
  65. struct nk_vec2 scrolling;
  66. struct node_linking linking;
  67. };
  68. static struct node_editor nodeEditor;
  69. /* === PROTOTYPES === */
  70. /* The node implementations need these two functions. */
  71. /* These could/should go in a header file along with the node and node_connector structs and be #included in the node implementations */
  72. struct node* node_editor_add(struct node_editor *editor, size_t nodeSize, const char *name, struct nk_rect bounds,
  73. int in_count, int out_count);
  74. void* node_editor_eval_connected(struct node *node, int inputSlot);
  75. /* ================== */
  76. /* === NODE TYPE IMPLEMENTATIONS === */
  77. #include "nodeeditor/node_type_float.c"
  78. #include "nodeeditor/node_type_color.c"
  79. #include "nodeeditor/node_type_blend.c"
  80. #include "nodeeditor/node_type_output.c"
  81. /* ================================= */
  82. static void
  83. node_editor_push(struct node_editor *editor, struct node *node)
  84. {
  85. if (!editor->begin) {
  86. node->next = NULL;
  87. node->prev = NULL;
  88. editor->begin = node;
  89. editor->end = node;
  90. } else {
  91. node->prev = editor->end;
  92. if (editor->end)
  93. editor->end->next = node;
  94. node->next = NULL;
  95. editor->end = node;
  96. }
  97. }
  98. static void
  99. node_editor_pop(struct node_editor *editor, struct node *node)
  100. {
  101. if (node->next)
  102. node->next->prev = node->prev;
  103. if (node->prev)
  104. node->prev->next = node->next;
  105. if (editor->end == node)
  106. editor->end = node->prev;
  107. if (editor->begin == node)
  108. editor->begin = node->next;
  109. node->next = NULL;
  110. node->prev = NULL;
  111. }
  112. static struct node*
  113. node_editor_find(struct node_editor *editor, int ID)
  114. {
  115. struct node *iter = editor->begin;
  116. while (iter) {
  117. if (iter->ID == ID)
  118. return iter;
  119. iter = iter->next;
  120. }
  121. return NULL;
  122. }
  123. static struct node_link*
  124. node_editor_find_link_by_output(struct node_editor *editor, struct node *output_node, int node_input_connector)
  125. {
  126. int i;
  127. for (i = 0; i < editor->link_count; i++)
  128. {
  129. if (editor->links[i].output_node == output_node &&
  130. editor->links[i].output_slot == node_input_connector &&
  131. editor->links[i].is_active == nk_true)
  132. {
  133. return &editor->links[i];
  134. }
  135. }
  136. return NULL;
  137. }
  138. static struct node_link*
  139. node_editor_find_link_by_input(struct node_editor *editor, struct node *input_node, int node_output_connector)
  140. {
  141. int i;
  142. for (i = 0; i < editor->link_count; i++)
  143. {
  144. if (editor->links[i].input_node == input_node &&
  145. editor->links[i].input_slot == node_output_connector &&
  146. editor->links[i].is_active == nk_true)
  147. {
  148. return &editor->links[i];
  149. }
  150. }
  151. return NULL;
  152. }
  153. static void
  154. node_editor_delete_link(struct node_link *link)
  155. {
  156. link->is_active = nk_false;
  157. link->input_node->outputs[link->input_slot].is_connected = nk_false;
  158. link->output_node->inputs[link->output_slot].is_connected = nk_false;
  159. }
  160. struct node*
  161. node_editor_add(struct node_editor *editor, size_t nodeSize, const char *name, struct nk_rect bounds,
  162. int in_count, int out_count)
  163. {
  164. int i;
  165. static int IDs = 0;
  166. struct node *node = NULL;
  167. if ((nk_size)editor->node_count < NK_LEN(editor->node_buf))
  168. {
  169. /* node_buf has unused slots */
  170. node = malloc(nodeSize);
  171. editor->node_buf[editor->node_count++] = node;
  172. node->ID = IDs++;
  173. }
  174. else {
  175. /* check for freed up slots in node_buf */
  176. for (i = 0; i < editor->node_count; i++)
  177. {
  178. if (editor->node_buf[i] == NULL) {
  179. node = malloc(nodeSize);
  180. editor->node_buf[i] = node;
  181. node->ID = i;
  182. break;
  183. }
  184. }
  185. }
  186. if (node == NULL) {
  187. fprintf(stdout, "Node creation failed\n");
  188. return NULL;
  189. }
  190. node->bounds = bounds;
  191. node->input_count = in_count;
  192. node->output_count = out_count;
  193. node->inputs = malloc(node->input_count * sizeof(struct node_connector));
  194. node->outputs = malloc(node->output_count * sizeof(struct node_connector));
  195. for (i = 0; i < node->input_count; i++) {
  196. node->inputs[i].is_connected = nk_false;
  197. node->inputs[i].type = fValue; /* default connector type */
  198. }
  199. for (i = 0; i < node->output_count; i++) {
  200. node->outputs[i].is_connected = nk_false;
  201. node->outputs[i].type = fValue; /* default connector type */
  202. }
  203. /* default connector spacing */
  204. node->slot_spacing.in_top = node->slot_spacing.in_space = node->bounds.h / (float)((node->input_count) + 1);
  205. node->slot_spacing.out_top = node->slot_spacing.out_space = node->bounds.h / (float)((node->output_count) + 1);
  206. strcpy(node->name, name);
  207. node_editor_push(editor, node);
  208. return node;
  209. }
  210. void *
  211. node_editor_eval_connected(struct node* node, int inputSlot)
  212. {
  213. NK_ASSERT(node->inputs[inputSlot].is_connected);
  214. return node->inputs[inputSlot].connected_node->eval_func(node->inputs[inputSlot].connected_node, node->inputs[inputSlot].connected_slot);
  215. }
  216. static void
  217. node_editor_link(struct node_editor *editor, struct node *in_node, int in_slot,
  218. struct node *out_node, int out_slot)
  219. {
  220. /* Confusingly, in and out nodes/slots here refer to the inputs and outputs OF THE LINK ITSELF, not the nodes */
  221. struct node_link *link = NULL;
  222. if ((nk_size)editor->link_count < NK_LEN(editor->links))
  223. link = &editor->links[editor->link_count++];
  224. else {
  225. int i;
  226. for (i = 0; i < (int)NK_LEN(editor->links); i++)
  227. {
  228. if (editor->links[i].is_active == nk_false) {
  229. link = &editor->links[i];
  230. break;
  231. }
  232. }
  233. }
  234. if (link) {
  235. out_node->inputs[out_slot].is_connected = nk_true;
  236. in_node->outputs[in_slot].is_connected = nk_true;
  237. out_node->inputs[out_slot].connected_node = in_node;
  238. out_node->inputs[out_slot].connected_slot = in_slot;
  239. link->input_node = in_node;
  240. link->input_slot = in_slot;
  241. link->output_node = out_node;
  242. link->output_slot = out_slot;
  243. link->is_active = nk_true;
  244. }
  245. else
  246. fprintf(stdout, "Too many links\n");
  247. }
  248. static void
  249. node_editor_init(struct node_editor *editor)
  250. {
  251. struct nk_vec2 color_node_position = {40, 10};
  252. struct nk_vec2 color2_node_position = {40, 260};
  253. struct nk_vec2 blend_node_position = {300, 140};
  254. struct nk_vec2 output_node_position = {540, 154};
  255. struct node_type_color *color1;
  256. struct node_type_color *color2;
  257. struct node *blend;
  258. memset(editor, 0, sizeof(*editor));
  259. editor->begin = NULL;
  260. editor->end = NULL;
  261. /* Create the nodes */
  262. editor->output_node = node_output_create(editor, output_node_position);
  263. color1 = node_color_create(editor, color_node_position);
  264. color2 = node_color_create(editor, color2_node_position);
  265. blend = (struct node *)node_blend_create(editor, blend_node_position);
  266. /* Set the color values */
  267. color1->input_val[0] = 1.0f;
  268. color1->input_val[1] = 0.0f;
  269. color1->input_val[2] = 0.0f;
  270. color2->input_val[0] = 0.0f;
  271. color2->input_val[1] = 0.0f;
  272. color2->input_val[2] = 1.0f;
  273. /* Link the nodes */
  274. node_editor_link(editor, (struct node*)color1, 0, blend, 0);
  275. node_editor_link(editor, (struct node*)color2, 0, blend, 1);
  276. node_editor_link(editor, blend, 0, editor->output_node, 0);
  277. editor->show_grid = nk_true;
  278. }
  279. static int
  280. node_editor(struct nk_context *ctx)
  281. {
  282. int n = 0;
  283. struct nk_rect total_space;
  284. const struct nk_input *in = &ctx->input;
  285. struct nk_command_buffer *canvas;
  286. struct node *updated = 0;
  287. struct node_editor *editor = &nodeEditor;
  288. if (!nodeEditor.initialized) {
  289. node_editor_init(&nodeEditor);
  290. nodeEditor.initialized = 1;
  291. }
  292. if (nk_begin(ctx, "NodeEdit", nk_rect(0, 0, 800, 600),
  293. NK_WINDOW_BORDER|NK_WINDOW_NO_SCROLLBAR|NK_WINDOW_MOVABLE|NK_WINDOW_CLOSABLE))
  294. {
  295. /* allocate complete window space */
  296. canvas = nk_window_get_canvas(ctx);
  297. total_space = nk_window_get_content_region(ctx);
  298. nk_layout_space_begin(ctx, NK_STATIC, total_space.h, editor->node_count);
  299. {
  300. struct node *it = editor->begin;
  301. struct nk_rect size = nk_layout_space_bounds(ctx);
  302. struct nk_panel *nodePanel = 0;
  303. if (editor->show_grid) {
  304. /* display grid */
  305. float x, y;
  306. const float grid_size = 32.0f;
  307. const struct nk_color grid_color = nk_rgb(50, 50, 50);
  308. for (x = (float)fmod(size.x - editor->scrolling.x, grid_size); x < size.w; x += grid_size)
  309. nk_stroke_line(canvas, x+size.x, size.y, x+size.x, size.y+size.h, 1.0f, grid_color);
  310. for (y = (float)fmod(size.y - editor->scrolling.y, grid_size); y < size.h; y += grid_size)
  311. nk_stroke_line(canvas, size.x, y+size.y, size.x+size.w, y+size.y, 1.0f, grid_color);
  312. }
  313. /* execute each node as a movable group */
  314. /* (loop through nodes) */
  315. while (it) {
  316. /* Output node window should not have a close button */
  317. nk_flags nodePanel_flags = NK_WINDOW_MOVABLE|NK_WINDOW_NO_SCROLLBAR|NK_WINDOW_BORDER|NK_WINDOW_TITLE;
  318. if (it != editor->output_node)
  319. nodePanel_flags |= NK_WINDOW_CLOSABLE;
  320. /* calculate scrolled node window position and size */
  321. nk_layout_space_push(ctx, nk_rect(it->bounds.x - editor->scrolling.x,
  322. it->bounds.y - editor->scrolling.y, it->bounds.w, it->bounds.h));
  323. /* execute node window */
  324. if (nk_group_begin(ctx, it->name, nodePanel_flags))
  325. {
  326. /* always have last selected node on top */
  327. nodePanel = nk_window_get_panel(ctx);
  328. if (nk_input_mouse_clicked(in, NK_BUTTON_LEFT, nodePanel->bounds) &&
  329. (!(it->prev && nk_input_mouse_clicked(in, NK_BUTTON_LEFT,
  330. nk_layout_space_rect_to_screen(ctx, nodePanel->bounds)))) &&
  331. editor->end != it)
  332. {
  333. updated = it;
  334. }
  335. if ((nodePanel->flags & NK_WINDOW_HIDDEN)) /* Node close button has been clicked */
  336. {
  337. /* Delete node */
  338. struct node_link *link_remove;
  339. node_editor_pop(editor, it);
  340. for (n = 0; n < it->input_count; n++)
  341. {
  342. if ((link_remove = node_editor_find_link_by_output(editor, it, n)))
  343. {
  344. node_editor_delete_link(link_remove);
  345. }
  346. }
  347. for (n = 0; n < it -> output_count; n++)
  348. {
  349. while((link_remove = node_editor_find_link_by_input(editor, it, n)))
  350. {
  351. node_editor_delete_link(link_remove);
  352. }
  353. }
  354. NK_ASSERT(editor->node_buf[it->ID] == it);
  355. editor->node_buf[it->ID] = NULL;
  356. free(it->inputs);
  357. free(it->outputs);
  358. free(it);
  359. }
  360. else {
  361. /* ================= NODE CONTENT ===================== */
  362. it->display_func(ctx, it);
  363. /* ==================================================== */
  364. }
  365. nk_group_end(ctx);
  366. }
  367. if (!(nodePanel->flags & NK_WINDOW_HIDDEN))
  368. {
  369. /* node connector and linking */
  370. struct nk_rect bounds;
  371. bounds = nk_layout_space_rect_to_local(ctx, nodePanel->bounds);
  372. bounds.x += editor->scrolling.x;
  373. bounds.y += editor->scrolling.y;
  374. it->bounds = bounds;
  375. /* output connectors */
  376. for (n = 0; n < it->output_count; ++n) {
  377. struct nk_rect circle;
  378. struct nk_color color;
  379. circle.x = nodePanel->bounds.x + nodePanel->bounds.w-4;
  380. circle.y = nodePanel->bounds.y + it->slot_spacing.out_top + it->slot_spacing.out_space * (float)n;
  381. circle.w = 8; circle.h = 8;
  382. if (it->outputs[n].type == fColor)
  383. color = nk_rgb(200, 200, 0);
  384. else color = nk_rgb(100, 100, 100);
  385. nk_fill_circle(canvas, circle, color);
  386. /* start linking process */
  387. /* (set linking active) */
  388. if (nk_input_has_mouse_click_down_in_rect(in, NK_BUTTON_LEFT, circle, nk_true)) {
  389. editor->linking.active = nk_true;
  390. editor->linking.node = it;
  391. editor->linking.input_id = it->ID;
  392. editor->linking.input_slot = n;
  393. }
  394. /* draw curve from linked node slot to mouse position */
  395. /* (if linking active) */
  396. if (editor->linking.active && editor->linking.node == it &&
  397. editor->linking.input_slot == n) {
  398. struct nk_vec2 l0 = nk_vec2(circle.x + 3, circle.y + 3);
  399. struct nk_vec2 l1 = in->mouse.pos;
  400. nk_stroke_curve(canvas, l0.x, l0.y, l0.x + 50.0f, l0.y,
  401. l1.x - 50.0f, l1.y, l1.x, l1.y, 1.0f, nk_rgb(100, 100, 100));
  402. }
  403. }
  404. /* input connectors */
  405. for (n = 0; n < it->input_count; ++n) {
  406. struct nk_rect circle;
  407. struct nk_color color;
  408. circle.x = nodePanel->bounds.x-4;
  409. circle.y = nodePanel->bounds.y + it->slot_spacing.in_top + it->slot_spacing.in_space * (float)n;
  410. circle.w = 8; circle.h = 8;
  411. if (it->inputs[n].type == fColor)
  412. color = nk_rgb(200, 200, 0);
  413. else color = nk_rgb(100, 100, 100);
  414. nk_fill_circle(canvas, circle, color);
  415. /* Detach link */
  416. if (nk_input_has_mouse_click_down_in_rect(in, NK_BUTTON_LEFT, circle, nk_true) &&
  417. editor->linking.active == nk_false &&
  418. it->inputs[n].is_connected == nk_true) {
  419. struct node_link *node_relink = node_editor_find_link_by_output(editor, it, n);
  420. editor->linking.active = nk_true;
  421. editor->linking.node = node_relink->input_node;
  422. editor->linking.input_id = node_relink->input_node->ID;
  423. editor->linking.input_slot = node_relink->input_slot;
  424. node_editor_delete_link(node_relink);
  425. }
  426. /* (Create link) */
  427. if (nk_input_is_mouse_released(in, NK_BUTTON_LEFT) &&
  428. nk_input_is_mouse_hovering_rect(in, circle) &&
  429. editor->linking.active &&
  430. editor->linking.node != it &&
  431. it->inputs[n].type == editor->linking.node->outputs[editor->linking.input_slot].type &&
  432. it->inputs[n].is_connected != nk_true) {
  433. editor->linking.active = nk_false;
  434. node_editor_link(editor, editor->linking.node,
  435. editor->linking.input_slot, it, n);
  436. }
  437. }
  438. }
  439. it = it->next;
  440. }
  441. /* reset linking connection */
  442. if (editor->linking.active && nk_input_is_mouse_released(in, NK_BUTTON_LEFT)) {
  443. editor->linking.active = nk_false;
  444. editor->linking.node = NULL;
  445. fprintf(stdout, "linking failed\n");
  446. }
  447. /* draw each link */
  448. for (n = 0; n < editor->link_count; ++n) {
  449. struct node_link *link = &editor->links[n];
  450. if (link->is_active == nk_true){
  451. struct node *ni = link->input_node;
  452. struct node *no = link->output_node;
  453. struct nk_vec2 l0 = nk_layout_space_to_screen(ctx,
  454. nk_vec2(ni->bounds.x + ni->bounds.w, 3.0f + ni->bounds.y + ni->slot_spacing.out_top + ni->slot_spacing.out_space * (float)(link->input_slot)));
  455. struct nk_vec2 l1 = nk_layout_space_to_screen(ctx,
  456. nk_vec2(no->bounds.x, 3.0f + no->bounds.y + no->slot_spacing.in_top + no->slot_spacing.in_space * (float)(link->output_slot)));
  457. l0.x -= editor->scrolling.x;
  458. l0.y -= editor->scrolling.y;
  459. l1.x -= editor->scrolling.x;
  460. l1.y -= editor->scrolling.y;
  461. nk_stroke_curve(canvas, l0.x, l0.y, l0.x + 50.0f, l0.y,
  462. l1.x - 50.0f, l1.y, l1.x, l1.y, 1.0f, nk_rgb(100, 100, 100));
  463. }
  464. }
  465. if (updated) {
  466. /* reshuffle nodes to have least recently selected node on top */
  467. node_editor_pop(editor, updated);
  468. node_editor_push(editor, updated);
  469. }
  470. /* node selection */
  471. if (nk_input_mouse_clicked(in, NK_BUTTON_LEFT, nk_layout_space_bounds(ctx))) {
  472. it = editor->begin;
  473. editor->selected = NULL;
  474. editor->bounds = nk_rect(in->mouse.pos.x, in->mouse.pos.y, 100, 200);
  475. while (it) {
  476. struct nk_rect b = nk_layout_space_rect_to_screen(ctx, it->bounds);
  477. b.x -= editor->scrolling.x;
  478. b.y -= editor->scrolling.y;
  479. if (nk_input_is_mouse_hovering_rect(in, b))
  480. editor->selected = it;
  481. it = it->next;
  482. }
  483. }
  484. /* contextual menu */
  485. if (nk_contextual_begin(ctx, 0, nk_vec2(150, 220), nk_window_get_bounds(ctx))) {
  486. const char *grid_option[] = {"Show Grid", "Hide Grid"};
  487. nk_layout_row_dynamic(ctx, 25, 1);
  488. if (nk_contextual_item_label(ctx, "Add Color node", NK_TEXT_CENTERED))
  489. node_color_create(editor, in->mouse.pos);
  490. if (nk_contextual_item_label(ctx, "Add Float node", NK_TEXT_CENTERED))
  491. node_float_create(editor, in->mouse.pos);
  492. if (nk_contextual_item_label(ctx, "Add Blend Node", NK_TEXT_CENTERED))
  493. node_blend_create(editor, in->mouse.pos);
  494. if (nk_contextual_item_label(ctx, grid_option[editor->show_grid],NK_TEXT_CENTERED))
  495. editor->show_grid = !editor->show_grid;
  496. nk_contextual_end(ctx);
  497. }
  498. }
  499. nk_layout_space_end(ctx);
  500. /* window content scrolling */
  501. if (nk_input_is_mouse_hovering_rect(in, nk_window_get_bounds(ctx)) &&
  502. nk_input_is_mouse_down(in, NK_BUTTON_MIDDLE)) {
  503. editor->scrolling.x += in->mouse.delta.x;
  504. editor->scrolling.y += in->mouse.delta.y;
  505. }
  506. }
  507. nk_end(ctx);
  508. return !nk_window_is_closed(ctx, "NodeEdit");
  509. }