| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574 |
- /*
- A basic node-based UI built with Nuklear.
- Builds on the node editor example included in Nuklear v1.00, with the aim of
- being used as a prototype for implementing a functioning node editor.
- Features:
- - Nodes of different types. Currently their implementations are #included in
- the main file, but they could easily be turned into eg. a plugin system.
- - Connectors/slots of different types -- currently float values and colors.
- - Adding and removing nodes.
- - Linking nodes, with validation (one link per input, only link similar connectors).
- - Detaching and moving links.
- - Evaluation of output values of connected nodes.
- - Memory management based on fixed size arrays for links and node pointers
- */
- struct node_connector {
- enum {fValue, fColor} type;
- nk_bool is_connected;
- struct node* connected_node;
- int connected_slot;
- };
- struct node {
- int ID;
- char name[32];
- struct nk_rect bounds;
- int input_count;
- int output_count;
- struct node_connector *inputs;
- struct node_connector *outputs;
- struct {
- float in_top;
- float in_space;
- float out_top;
- float out_space;
- } slot_spacing; /* Maybe this should be called "node_layout" and include the bounds? */
- struct node *next; /* Z ordering only */
- struct node *prev; /* Z ordering only */
- void* (*eval_func)(struct node*, int oIndex);
- void (*display_func)(struct nk_context*, struct node*);
- };
- struct node_link {
- struct node* input_node;
- int input_slot;
- struct node* output_node;
- int output_slot;
- nk_bool is_active;
- };
- struct node_linking {
- int active;
- struct node *node;
- int input_id;
- int input_slot;
- };
- struct node_editor {
- int initialized;
- struct node *node_buf[32];
- struct node_link links[64];
- struct node *output_node;
- struct node *begin;
- struct node *end;
- int node_count;
- int link_count;
- struct nk_rect bounds;
- struct node *selected;
- int show_grid;
- struct nk_vec2 scrolling;
- struct node_linking linking;
- };
- static struct node_editor nodeEditor;
- /* === PROTOTYPES === */
- /* The node implementations need these two functions. */
- /* These could/should go in a header file along with the node and node_connector structs and be #included in the node implementations */
- struct node* node_editor_add(struct node_editor *editor, size_t nodeSize, const char *name, struct nk_rect bounds,
- int in_count, int out_count);
- void* node_editor_eval_connected(struct node *node, int inputSlot);
- /* ================== */
- /* === NODE TYPE IMPLEMENTATIONS === */
- #include "nodeeditor/node_type_float.c"
- #include "nodeeditor/node_type_color.c"
- #include "nodeeditor/node_type_blend.c"
- #include "nodeeditor/node_type_output.c"
- /* ================================= */
- static void
- node_editor_push(struct node_editor *editor, struct node *node)
- {
- if (!editor->begin) {
- node->next = NULL;
- node->prev = NULL;
- editor->begin = node;
- editor->end = node;
- } else {
- node->prev = editor->end;
- if (editor->end)
- editor->end->next = node;
- node->next = NULL;
- editor->end = node;
- }
- }
- static void
- node_editor_pop(struct node_editor *editor, struct node *node)
- {
- if (node->next)
- node->next->prev = node->prev;
- if (node->prev)
- node->prev->next = node->next;
- if (editor->end == node)
- editor->end = node->prev;
- if (editor->begin == node)
- editor->begin = node->next;
- node->next = NULL;
- node->prev = NULL;
- }
- static struct node*
- node_editor_find(struct node_editor *editor, int ID)
- {
- struct node *iter = editor->begin;
- while (iter) {
- if (iter->ID == ID)
- return iter;
- iter = iter->next;
- }
- return NULL;
- }
- static struct node_link*
- node_editor_find_link_by_output(struct node_editor *editor, struct node *output_node, int node_input_connector)
- {
- int i;
- for (i = 0; i < editor->link_count; i++)
- {
- if (editor->links[i].output_node == output_node &&
- editor->links[i].output_slot == node_input_connector &&
- editor->links[i].is_active == nk_true)
- {
- return &editor->links[i];
- }
- }
- return NULL;
- }
- static struct node_link*
- node_editor_find_link_by_input(struct node_editor *editor, struct node *input_node, int node_output_connector)
- {
- int i;
- for (i = 0; i < editor->link_count; i++)
- {
- if (editor->links[i].input_node == input_node &&
- editor->links[i].input_slot == node_output_connector &&
- editor->links[i].is_active == nk_true)
- {
- return &editor->links[i];
- }
- }
- return NULL;
- }
- static void
- node_editor_delete_link(struct node_link *link)
- {
- link->is_active = nk_false;
- link->input_node->outputs[link->input_slot].is_connected = nk_false;
- link->output_node->inputs[link->output_slot].is_connected = nk_false;
- }
- struct node*
- node_editor_add(struct node_editor *editor, size_t nodeSize, const char *name, struct nk_rect bounds,
- int in_count, int out_count)
- {
- int i;
- static int IDs = 0;
- struct node *node = NULL;
- if ((nk_size)editor->node_count < NK_LEN(editor->node_buf))
- {
- /* node_buf has unused slots */
- node = malloc(nodeSize);
- editor->node_buf[editor->node_count++] = node;
- node->ID = IDs++;
- }
- else {
- /* check for freed up slots in node_buf */
- for (i = 0; i < editor->node_count; i++)
- {
- if (editor->node_buf[i] == NULL) {
- node = malloc(nodeSize);
- editor->node_buf[i] = node;
- node->ID = i;
- break;
- }
- }
- }
- if (node == NULL) {
- fprintf(stdout, "Node creation failed\n");
- return NULL;
- }
- node->bounds = bounds;
- node->input_count = in_count;
- node->output_count = out_count;
- node->inputs = malloc(node->input_count * sizeof(struct node_connector));
- node->outputs = malloc(node->output_count * sizeof(struct node_connector));
- for (i = 0; i < node->input_count; i++) {
- node->inputs[i].is_connected = nk_false;
- node->inputs[i].type = fValue; /* default connector type */
- }
- for (i = 0; i < node->output_count; i++) {
- node->outputs[i].is_connected = nk_false;
- node->outputs[i].type = fValue; /* default connector type */
- }
- /* default connector spacing */
- node->slot_spacing.in_top = node->slot_spacing.in_space = node->bounds.h / (float)((node->input_count) + 1);
- node->slot_spacing.out_top = node->slot_spacing.out_space = node->bounds.h / (float)((node->output_count) + 1);
- strcpy(node->name, name);
- node_editor_push(editor, node);
- return node;
- }
- void *
- node_editor_eval_connected(struct node* node, int inputSlot)
- {
- NK_ASSERT(node->inputs[inputSlot].is_connected);
- return node->inputs[inputSlot].connected_node->eval_func(node->inputs[inputSlot].connected_node, node->inputs[inputSlot].connected_slot);
- }
- static void
- node_editor_link(struct node_editor *editor, struct node *in_node, int in_slot,
- struct node *out_node, int out_slot)
- {
- /* Confusingly, in and out nodes/slots here refer to the inputs and outputs OF THE LINK ITSELF, not the nodes */
- struct node_link *link = NULL;
- if ((nk_size)editor->link_count < NK_LEN(editor->links))
- link = &editor->links[editor->link_count++];
- else {
- int i;
- for (i = 0; i < (int)NK_LEN(editor->links); i++)
- {
- if (editor->links[i].is_active == nk_false) {
- link = &editor->links[i];
- break;
- }
- }
- }
- if (link) {
- out_node->inputs[out_slot].is_connected = nk_true;
- in_node->outputs[in_slot].is_connected = nk_true;
- out_node->inputs[out_slot].connected_node = in_node;
- out_node->inputs[out_slot].connected_slot = in_slot;
- link->input_node = in_node;
- link->input_slot = in_slot;
- link->output_node = out_node;
- link->output_slot = out_slot;
- link->is_active = nk_true;
- }
- else
- fprintf(stdout, "Too many links\n");
- }
- static void
- node_editor_init(struct node_editor *editor)
- {
- struct nk_vec2 color_node_position = {40, 10};
- struct nk_vec2 color2_node_position = {40, 260};
- struct nk_vec2 blend_node_position = {300, 140};
- struct nk_vec2 output_node_position = {540, 154};
- struct node_type_color *color1;
- struct node_type_color *color2;
- struct node *blend;
- memset(editor, 0, sizeof(*editor));
- editor->begin = NULL;
- editor->end = NULL;
- /* Create the nodes */
- editor->output_node = node_output_create(editor, output_node_position);
- color1 = node_color_create(editor, color_node_position);
- color2 = node_color_create(editor, color2_node_position);
- blend = (struct node *)node_blend_create(editor, blend_node_position);
- /* Set the color values */
- color1->input_val[0] = 1.0f;
- color1->input_val[1] = 0.0f;
- color1->input_val[2] = 0.0f;
- color2->input_val[0] = 0.0f;
- color2->input_val[1] = 0.0f;
- color2->input_val[2] = 1.0f;
- /* Link the nodes */
- node_editor_link(editor, (struct node*)color1, 0, blend, 0);
- node_editor_link(editor, (struct node*)color2, 0, blend, 1);
- node_editor_link(editor, blend, 0, editor->output_node, 0);
- editor->show_grid = nk_true;
- }
- static int
- node_editor(struct nk_context *ctx)
- {
- int n = 0;
- struct nk_rect total_space;
- const struct nk_input *in = &ctx->input;
- struct nk_command_buffer *canvas;
- struct node *updated = 0;
- struct node_editor *editor = &nodeEditor;
- if (!nodeEditor.initialized) {
- node_editor_init(&nodeEditor);
- nodeEditor.initialized = 1;
- }
- if (nk_begin(ctx, "NodeEdit", nk_rect(0, 0, 800, 600),
- NK_WINDOW_BORDER|NK_WINDOW_NO_SCROLLBAR|NK_WINDOW_MOVABLE|NK_WINDOW_CLOSABLE))
- {
- /* allocate complete window space */
- canvas = nk_window_get_canvas(ctx);
- total_space = nk_window_get_content_region(ctx);
- nk_layout_space_begin(ctx, NK_STATIC, total_space.h, editor->node_count);
- {
- struct node *it = editor->begin;
- struct nk_rect size = nk_layout_space_bounds(ctx);
- struct nk_panel *nodePanel = 0;
- if (editor->show_grid) {
- /* display grid */
- float x, y;
- const float grid_size = 32.0f;
- const struct nk_color grid_color = nk_rgb(50, 50, 50);
- for (x = (float)fmod(size.x - editor->scrolling.x, grid_size); x < size.w; x += grid_size)
- nk_stroke_line(canvas, x+size.x, size.y, x+size.x, size.y+size.h, 1.0f, grid_color);
- for (y = (float)fmod(size.y - editor->scrolling.y, grid_size); y < size.h; y += grid_size)
- nk_stroke_line(canvas, size.x, y+size.y, size.x+size.w, y+size.y, 1.0f, grid_color);
- }
- /* execute each node as a movable group */
- /* (loop through nodes) */
- while (it) {
- /* Output node window should not have a close button */
- nk_flags nodePanel_flags = NK_WINDOW_MOVABLE|NK_WINDOW_NO_SCROLLBAR|NK_WINDOW_BORDER|NK_WINDOW_TITLE;
- if (it != editor->output_node)
- nodePanel_flags |= NK_WINDOW_CLOSABLE;
- /* calculate scrolled node window position and size */
- nk_layout_space_push(ctx, nk_rect(it->bounds.x - editor->scrolling.x,
- it->bounds.y - editor->scrolling.y, it->bounds.w, it->bounds.h));
- /* execute node window */
- if (nk_group_begin(ctx, it->name, nodePanel_flags))
- {
- /* always have last selected node on top */
- nodePanel = nk_window_get_panel(ctx);
- if (nk_input_mouse_clicked(in, NK_BUTTON_LEFT, nodePanel->bounds) &&
- (!(it->prev && nk_input_mouse_clicked(in, NK_BUTTON_LEFT,
- nk_layout_space_rect_to_screen(ctx, nodePanel->bounds)))) &&
- editor->end != it)
- {
- updated = it;
- }
- if ((nodePanel->flags & NK_WINDOW_HIDDEN)) /* Node close button has been clicked */
- {
- /* Delete node */
- struct node_link *link_remove;
- node_editor_pop(editor, it);
- for (n = 0; n < it->input_count; n++)
- {
- if ((link_remove = node_editor_find_link_by_output(editor, it, n)))
- {
- node_editor_delete_link(link_remove);
- }
- }
- for (n = 0; n < it -> output_count; n++)
- {
- while((link_remove = node_editor_find_link_by_input(editor, it, n)))
- {
- node_editor_delete_link(link_remove);
- }
- }
- NK_ASSERT(editor->node_buf[it->ID] == it);
- editor->node_buf[it->ID] = NULL;
- free(it->inputs);
- free(it->outputs);
- free(it);
- }
- else {
- /* ================= NODE CONTENT ===================== */
- it->display_func(ctx, it);
- /* ==================================================== */
- }
- nk_group_end(ctx);
- }
- if (!(nodePanel->flags & NK_WINDOW_HIDDEN))
- {
- /* node connector and linking */
- struct nk_rect bounds;
- bounds = nk_layout_space_rect_to_local(ctx, nodePanel->bounds);
- bounds.x += editor->scrolling.x;
- bounds.y += editor->scrolling.y;
- it->bounds = bounds;
- /* output connectors */
- for (n = 0; n < it->output_count; ++n) {
- struct nk_rect circle;
- struct nk_color color;
- circle.x = nodePanel->bounds.x + nodePanel->bounds.w-4;
- circle.y = nodePanel->bounds.y + it->slot_spacing.out_top + it->slot_spacing.out_space * (float)n;
- circle.w = 8; circle.h = 8;
- if (it->outputs[n].type == fColor)
- color = nk_rgb(200, 200, 0);
- else color = nk_rgb(100, 100, 100);
- nk_fill_circle(canvas, circle, color);
- /* start linking process */
- /* (set linking active) */
- if (nk_input_has_mouse_click_down_in_rect(in, NK_BUTTON_LEFT, circle, nk_true)) {
- editor->linking.active = nk_true;
- editor->linking.node = it;
- editor->linking.input_id = it->ID;
- editor->linking.input_slot = n;
- }
- /* draw curve from linked node slot to mouse position */
- /* (if linking active) */
- if (editor->linking.active && editor->linking.node == it &&
- editor->linking.input_slot == n) {
- struct nk_vec2 l0 = nk_vec2(circle.x + 3, circle.y + 3);
- struct nk_vec2 l1 = in->mouse.pos;
- nk_stroke_curve(canvas, l0.x, l0.y, l0.x + 50.0f, l0.y,
- l1.x - 50.0f, l1.y, l1.x, l1.y, 1.0f, nk_rgb(100, 100, 100));
- }
- }
- /* input connectors */
- for (n = 0; n < it->input_count; ++n) {
- struct nk_rect circle;
- struct nk_color color;
- circle.x = nodePanel->bounds.x-4;
- circle.y = nodePanel->bounds.y + it->slot_spacing.in_top + it->slot_spacing.in_space * (float)n;
- circle.w = 8; circle.h = 8;
- if (it->inputs[n].type == fColor)
- color = nk_rgb(200, 200, 0);
- else color = nk_rgb(100, 100, 100);
- nk_fill_circle(canvas, circle, color);
- /* Detach link */
- if (nk_input_has_mouse_click_down_in_rect(in, NK_BUTTON_LEFT, circle, nk_true) &&
- editor->linking.active == nk_false &&
- it->inputs[n].is_connected == nk_true) {
- struct node_link *node_relink = node_editor_find_link_by_output(editor, it, n);
- editor->linking.active = nk_true;
- editor->linking.node = node_relink->input_node;
- editor->linking.input_id = node_relink->input_node->ID;
- editor->linking.input_slot = node_relink->input_slot;
- node_editor_delete_link(node_relink);
- }
- /* (Create link) */
- if (nk_input_is_mouse_released(in, NK_BUTTON_LEFT) &&
- nk_input_is_mouse_hovering_rect(in, circle) &&
- editor->linking.active &&
- editor->linking.node != it &&
- it->inputs[n].type == editor->linking.node->outputs[editor->linking.input_slot].type &&
- it->inputs[n].is_connected != nk_true) {
- editor->linking.active = nk_false;
- node_editor_link(editor, editor->linking.node,
- editor->linking.input_slot, it, n);
- }
- }
- }
- it = it->next;
- }
- /* reset linking connection */
- if (editor->linking.active && nk_input_is_mouse_released(in, NK_BUTTON_LEFT)) {
- editor->linking.active = nk_false;
- editor->linking.node = NULL;
- fprintf(stdout, "linking failed\n");
- }
- /* draw each link */
- for (n = 0; n < editor->link_count; ++n) {
- struct node_link *link = &editor->links[n];
- if (link->is_active == nk_true){
- struct node *ni = link->input_node;
- struct node *no = link->output_node;
- struct nk_vec2 l0 = nk_layout_space_to_screen(ctx,
- 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)));
- struct nk_vec2 l1 = nk_layout_space_to_screen(ctx,
- nk_vec2(no->bounds.x, 3.0f + no->bounds.y + no->slot_spacing.in_top + no->slot_spacing.in_space * (float)(link->output_slot)));
- l0.x -= editor->scrolling.x;
- l0.y -= editor->scrolling.y;
- l1.x -= editor->scrolling.x;
- l1.y -= editor->scrolling.y;
- nk_stroke_curve(canvas, l0.x, l0.y, l0.x + 50.0f, l0.y,
- l1.x - 50.0f, l1.y, l1.x, l1.y, 1.0f, nk_rgb(100, 100, 100));
- }
- }
- if (updated) {
- /* reshuffle nodes to have least recently selected node on top */
- node_editor_pop(editor, updated);
- node_editor_push(editor, updated);
- }
- /* node selection */
- if (nk_input_mouse_clicked(in, NK_BUTTON_LEFT, nk_layout_space_bounds(ctx))) {
- it = editor->begin;
- editor->selected = NULL;
- editor->bounds = nk_rect(in->mouse.pos.x, in->mouse.pos.y, 100, 200);
- while (it) {
- struct nk_rect b = nk_layout_space_rect_to_screen(ctx, it->bounds);
- b.x -= editor->scrolling.x;
- b.y -= editor->scrolling.y;
- if (nk_input_is_mouse_hovering_rect(in, b))
- editor->selected = it;
- it = it->next;
- }
- }
- /* contextual menu */
- if (nk_contextual_begin(ctx, 0, nk_vec2(150, 220), nk_window_get_bounds(ctx))) {
- const char *grid_option[] = {"Show Grid", "Hide Grid"};
- nk_layout_row_dynamic(ctx, 25, 1);
- if (nk_contextual_item_label(ctx, "Add Color node", NK_TEXT_CENTERED))
- node_color_create(editor, in->mouse.pos);
- if (nk_contextual_item_label(ctx, "Add Float node", NK_TEXT_CENTERED))
- node_float_create(editor, in->mouse.pos);
- if (nk_contextual_item_label(ctx, "Add Blend Node", NK_TEXT_CENTERED))
- node_blend_create(editor, in->mouse.pos);
- if (nk_contextual_item_label(ctx, grid_option[editor->show_grid],NK_TEXT_CENTERED))
- editor->show_grid = !editor->show_grid;
- nk_contextual_end(ctx);
- }
- }
- nk_layout_space_end(ctx);
- /* window content scrolling */
- if (nk_input_is_mouse_hovering_rect(in, nk_window_get_bounds(ctx)) &&
- nk_input_is_mouse_down(in, NK_BUTTON_MIDDLE)) {
- editor->scrolling.x += in->mouse.delta.x;
- editor->scrolling.y += in->mouse.delta.y;
- }
- }
- nk_end(ctx);
- return !nk_window_is_closed(ctx, "NodeEdit");
- }
|