#define NODES_ICON ICON_MDI_GRAPH #define NODES_TITLE "Nodes " NODES_ICON EDITOR_BIND(nodes, "held(CTRL)&down(5)", { ui_show(NODES_TITLE, ui_visible(NODES_TITLE) ^ true); }); /* 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. - Pins/pins of different types -- currently float values and colors. - Adding and removing nodes. - Linking nodes, with validation (one link per input, only link similar pins). - Detaching and moving links. - Evaluation of output values of connected nodes. - Memory management based on fixed size arrays for links and node pointers - Multiple node types - Multiple pin types - Linking between pins of the same type - Detaching and reattaching links - Getting value from linked node if pin is connected Todo: - Complete pin types. - Allow dragging from output to input pin. - Cut link by CTRL+clicking input pin. - Cut link by drawing intersect line on a link. - Group elemnts together with mouse, or LSHIFT+clicking. - Drag groups. - DEL elements. - DEL groups. - CTRL-C/CTRL-V/CTRL-X elements. - CTRL-C/CTRL-V/CTRL-X groups. - CTRL-Z,CTRL-Y. - CTRL-N. - CTRL-L,CTRL-S. - CTRL-F. - CTRL-Wheel Zooming. - Allow to extend node types from Lua. Extra todo: - Execution Flow (see: nk_stroke_triangle, nk_fill_triangle) - Complete missing nodes (see: nk_draw_image, nk_draw_text) - Right-click could visualize node/board diagram as Lua script. - Once that done, copy/pasting scripts should work within editor. Sources: - https://github.com/Immediate-Mode-UI/Nuklear/pull/561 - https://github.com/vurtun/nuklear/blob/master/demo/node_editor.c */ typedef enum pin_type_t { type_flow, type_int,type_float, type_block,type_texture,type_image, type_color, /* type_bool, type_char, type_string, type_int2, type_int3, type_int4, type_float2, type_float3, type_float4, type_array, type_map, */ type_total } pin_type_t; struct node_pin { pin_type_t pin_type; nk_bool is_connected; struct node* connected_node; int connected_pin; }; struct node { int ID; char name[32]; struct nk_rect bounds; int input_count; int output_count; struct node_pin *inputs; struct node_pin *outputs; struct { float in_padding_x; float in_padding_y; float in_spacing_y; float out_padding_x; float out_padding_y; float out_spacing_y; } pin_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_pin; struct node* output_node; int output_pin; nk_bool is_active; }; struct node_linking { int active; struct node *node; int input_id; int input_pin; }; 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; }; /* === PROTOTYPES === */ /* The node implementations need these two functions. */ /* These could/should go in a header file along with the node and node_pin 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 input_pin_number); /* ================== */ /* === NODE TYPE IMPLEMENTATIONS === */ #define NODE_DEFAULT_ROW_HEIGHT 25 // ---------------------------------------------------------------------------------------------------- // #include "node_output.h" struct node_type_output { struct node node; struct nk_colorf input_val; }; struct nk_colorf *node_output_get(struct node* node) { struct node_type_output *output_node = (struct node_type_output*)node; if (!node->inputs[0].is_connected) { struct nk_colorf black = {0.0f, 0.0f, 0.0f, 0.0f}; output_node->input_val = black; } return &output_node->input_val; } static void node_output_display(struct nk_context *ctx, struct node *node) { if (node->inputs[0].is_connected) { struct node_type_output *output_node = (struct node_type_output*)node; output_node->input_val = *(struct nk_colorf*)node_editor_eval_connected(node, 0); nk_layout_row_dynamic(ctx, 60, 1); nk_button_color(ctx, nk_rgba_cf(output_node->input_val)); } } struct node* node_output_create(struct node_editor *editor, struct nk_vec2 position) { struct node_type_output *output_node = (struct node_type_output*)node_editor_add(editor, sizeof(struct node_type_output), "Output", nk_rect(position.x, position.y, 100, 100), 1, 0); if (output_node){ output_node->node.inputs[0].pin_type = type_color; output_node->node.display_func = node_output_display; } return (struct node*)output_node; } // ---------------------------------------------------------------------------------------------------- // #include "node_float.h" struct node_type_float { struct node node; float output_val; }; static float *node_float_eval(struct node* node, int oIndex) { struct node_type_float *float_node = (struct node_type_float*)node; NK_ASSERT(oIndex == 0); return &float_node->output_val; } static void node_float_draw(struct nk_context *ctx, struct node *node) { struct node_type_float *float_node = (struct node_type_float*)node; nk_layout_row_dynamic(ctx, NODE_DEFAULT_ROW_HEIGHT, 1); float_node->output_val = nk_propertyf(ctx, "#Value:", 0.0f, float_node->output_val, 1.0f, 0.01f, 0.01f); } void node_float_create(struct node_editor *editor, struct nk_vec2 position) { struct node_type_float *float_node = (struct node_type_float*)node_editor_add(editor, sizeof(struct node_type_float), "Float", nk_rect(position.x, position.y, 180, 75), 0, 1); if (float_node) { float_node->output_val = 1.0f; float_node->node.display_func = node_float_draw; float_node->node.eval_func = (void*(*)(struct node*, int)) node_float_eval; } } // ---------------------------------------------------------------------------------------------------- // #include "node_color.h" struct node_type_color { struct node node; float input_val[4]; struct nk_colorf output_val; }; static struct nk_colorf *node_color_eval(struct node* node, int oIndex) { struct node_type_color *color_node = (struct node_type_color*)node; NK_ASSERT(oIndex == 0); /* only one output connector */ return &color_node->output_val; } static void node_color_draw(struct nk_context *ctx, struct node *node) { struct node_type_color *color_node = (struct node_type_color*)node; float eval_result; /* Get the values from connected nodes into this so the inputs revert on disconnect */ const char* labels[4] = {"#R:","#G:","#B:","#A:"}; float color_val[4]; /* Because we can't just loop through the struct... */ nk_layout_row_dynamic(ctx, NODE_DEFAULT_ROW_HEIGHT, 1); nk_button_color(ctx, nk_rgba_cf(color_node->output_val)); for (int i = 0; i < 4; i++) { if (color_node->node.inputs[i].is_connected) { eval_result = *(float*)node_editor_eval_connected(node, i); eval_result = nk_propertyf(ctx, labels[i], eval_result, eval_result, eval_result, 0.01f, 0.01f); color_val[i] = eval_result; } else { color_node->input_val[i] = nk_propertyf(ctx, labels[i], 0.0f, color_node->input_val[i], 1.0f, 0.01f, 0.01f); color_val[i] = color_node->input_val[i]; } } color_node->output_val.r = color_val[0]; color_node->output_val.g = color_val[1]; color_node->output_val.b = color_val[2]; color_node->output_val.a = color_val[3]; } void node_color_create(struct node_editor *editor, struct nk_vec2 position) { struct node_type_color *color_node = (struct node_type_color*)node_editor_add(editor, sizeof(struct node_type_color), "Color", nk_rect(position.x, position.y, 180, 190), 4, 1); if (color_node) { const struct nk_colorf black = {0.0f, 0.0f, 0.0f, 1.0f}; for (int i = 0; i < color_node->node.input_count; i++) color_node->node.inputs[i].pin_type = type_float; color_node->node.outputs[0].pin_type = type_color; color_node->node.pin_spacing.in_padding_y += NODE_DEFAULT_ROW_HEIGHT; color_node->input_val[0] = 0.0f; color_node->input_val[1] = 0.0f; color_node->input_val[2] = 0.0f; color_node->input_val[3] = 1.0f; color_node->output_val = black; color_node->node.display_func = node_color_draw; color_node->node.eval_func = (void*(*)(struct node*, int)) node_color_eval; } } // ---------------------------------------------------------------------------------------------------- // #include "node_blend.h" struct node_type_blend { struct node node; struct nk_colorf input_val[2]; struct nk_colorf output_val; float blend_val; }; static struct nk_colorf *node_blend_eval(struct node *node, int oIndex) { struct node_type_blend* blend_node = (struct node_type_blend*)node; return &blend_node->output_val; } static void node_blend_display(struct nk_context *ctx, struct node *node) { struct node_type_blend *blend_node = (struct node_type_blend*)node; const struct nk_colorf blank = {0.0f, 0.0f, 0.0f, 0.0f}; float blend_amnt; nk_layout_row_dynamic(ctx, NODE_DEFAULT_ROW_HEIGHT, 1); for (int i = 0; i < 2; i++){ if(node->inputs[i].is_connected) { blend_node->input_val[i] = *(struct nk_colorf*)node_editor_eval_connected(node, i); } else { blend_node->input_val[i] = blank; } nk_button_color(ctx, nk_rgba_cf(blend_node->input_val[i])); } if (node->inputs[2].is_connected) { blend_amnt = *(float*)node_editor_eval_connected(node, 2); blend_amnt = nk_propertyf(ctx, "#Blend", blend_amnt, blend_amnt, blend_amnt, 0.01f, 0.01f); } else { blend_node->blend_val = nk_propertyf(ctx, "#Blend", 0.0f, blend_node->blend_val, 1.0f, 0.01f, 0.01f); blend_amnt = blend_node->blend_val; } if(node->inputs[0].is_connected && node->inputs[1].is_connected) { blend_node->output_val.r = blend_node->input_val[0].r * (1.0f-blend_amnt) + blend_node->input_val[1].r * blend_amnt; blend_node->output_val.g = blend_node->input_val[0].g * (1.0f-blend_amnt) + blend_node->input_val[1].g * blend_amnt; blend_node->output_val.b = blend_node->input_val[0].b * (1.0f-blend_amnt) + blend_node->input_val[1].b * blend_amnt; blend_node->output_val.a = blend_node->input_val[0].a * (1.0f-blend_amnt) + blend_node->input_val[1].a * blend_amnt; } else { blend_node->output_val = blank; } } void node_blend_create(struct node_editor *editor, struct nk_vec2 position) { struct node_type_blend* blend_node = (struct node_type_blend*)node_editor_add(editor, sizeof(struct node_type_blend), "Blend", nk_rect(position.x, position.y, 180, 130), 3, 1); if (blend_node) { const struct nk_colorf blank = {0.0f, 0.0f, 0.0f, 0.0f}; for (int i = 0; i < (int)NK_LEN(blend_node->input_val); i++) blend_node->node.inputs[i].pin_type = type_color; blend_node->node.outputs[0].pin_type = type_color; // blend_node->node.pin_spacing.in_padding_y = 42.0f; // blend_node->node.pin_spacing.in_spacing_y = 29.0f; for (int i = 0; i < (int)NK_LEN(blend_node->input_val); i++) blend_node->input_val[i] = blank; blend_node->output_val = blank; blend_node->blend_val = 0.5f; blend_node->node.display_func = node_blend_display; blend_node->node.eval_func = (void*(*)(struct node*, int)) node_blend_eval; } } /* ================================= */ #define NK_RGB3(r,g,b) {r,g,b,255} #define BG_COLOR ((struct nk_color){60,60,60,192}) // nk_rgba(0,0,0,192) static struct editor_node_style { int pin_type; const char *shape; struct nk_color color_idle; struct nk_color color_hover; } styles[] = { // order matters: { type_flow, "triangle_right", NK_RGB3(200,200,200), NK_RGB3(255,255,255) }, // if .num_links == 0 { type_int, "circle", NK_RGB3(33,227,175), NK_RGB3(135,239,195) }, { type_float, "circle", NK_RGB3(156,253,65), NK_RGB3(144,225,137) }, { type_block, "circle", NK_RGB3(6,165,239), NK_RGB3(137,196,247) }, { type_texture, "circle", NK_RGB3(148,0,0), NK_RGB3(183,137,137) }, { type_image, "circle", NK_RGB3(200,130,255), NK_RGB3(220,170,255) }, { type_color, "circle", NK_RGB3(252,200,35), NK_RGB3(255,217,140) }, }; #define COLOR_FLOW_HI styles[type_flow].color_hover #define COLOR_FLOW_LO styles[type_flow].color_idle #define GRID_SIZE 64.0f #define GRID_COLOR ((struct nk_color)NK_RGB3(80,80,120)) #define GRID_THICKNESS 1.0f // 4 colors: top-left, top-right, bottom-right, bottom-left #define GRID_BG_COLORS ((struct nk_color){30,30,30,255}), ((struct nk_color){40,20,0,255}), ((struct nk_color){30,30,30,255}), ((struct nk_color){20,30,40,255}) #define LINK_THICKNESS 1.0f #define LINK_DRAW(POINT_A,POINT_B,COLOR) do { \ vec2 a = (POINT_A); \ vec2 b = (POINT_B); \ nk_stroke_line(canvas, a.x, a.y, b.x, b.y, LINK_THICKNESS, COLOR); \ } while(0) #undef LINK_DRAW #define LINK_DRAW(POINT_A,POINT_B,COLOR) do { \ vec2 a = (POINT_A); \ vec2 b = (POINT_B); \ nk_stroke_curve(canvas, a.x, a.y, a.x+50, a.y, b.x-50, b.y, b.x, b.y, LINK_THICKNESS, COLOR); \ } while(0) #undef LINK_DRAW #define LINK_DRAW(POINT_A,POINT_B,COLOR) do { \ vec2 a = (POINT_A); \ vec2 b = (POINT_B); \ float dist2 = len2( sub2( ptr2(&b.x), ptr2(&a.x) ) ); \ vec2 mid_a = mix2( ptr2(&a.x), ptr2(&b.x), 0.25 ); mid_a.y += dist2/2; \ vec2 mid_b = mix2( ptr2(&a.x), ptr2(&b.x), 0.75 ); mid_b.y += dist2/3; \ nk_stroke_curve(canvas, a.x, a.y, mid_a.x, mid_a.y, mid_b.x, mid_b.y, b.x, b.y, LINK_THICKNESS, COLOR); \ } while(0) #define PIN_RADIUS 12 #define PIN_THICKNESS 1.0f #define PIN_DRAW(PIN_ADDR,POINT,RADIUS) do { \ circle.x = (POINT).x - (RADIUS) / 2; \ circle.y = (POINT).y - (RADIUS) / 2; \ circle.w = circle.h = (RADIUS); \ struct nk_color color = node_get_type_color((PIN_ADDR).pin_type); \ if((PIN_ADDR).is_connected) \ nk_fill_circle(canvas, circle, color); \ else \ nk_stroke_circle(canvas, circle, PIN_THICKNESS, color); \ } while(0) static struct nk_color node_get_type_color(unsigned pin_type) { for( int i = 0; i < type_total; ++i ) if( styles[i].pin_type == pin_type ) return styles[i].color_idle; return ((struct nk_color)NK_RGB3(255,0,255)); } 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_by_id(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) { for( int i = 0; i < editor->link_count; i++ ) { if (editor->links[i].output_node == output_node && editor->links[i].output_pin == 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) { for( int i = 0; i < editor->link_count; i++ ) { if (editor->links[i].input_node == input_node && editor->links[i].input_pin == 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_pin].is_connected = nk_false; link->output_node->inputs[link->output_pin].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) { static int IDs = 0; struct node *node = NULL; if ((nk_size)editor->node_count < NK_LEN(editor->node_buf)) { /* node_buf has unused pins */ node = MALLOC(nodeSize); editor->node_buf[editor->node_count++] = node; node->ID = IDs++; } else { /* check for freed up pins in node_buf */ for (int 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) { puts("Node creation failed"); return NULL; } node->bounds = bounds; node->input_count = in_count; node->output_count = out_count; node->inputs = MALLOC(node->input_count * sizeof(struct node_pin)); node->outputs = MALLOC(node->output_count * sizeof(struct node_pin)); for (int i = 0; i < node->input_count; i++) { node->inputs[i].is_connected = nk_false; node->inputs[i].pin_type = type_float; /* default pin type */ } for (int i = 0; i < node->output_count; i++) { node->outputs[i].is_connected = nk_false; node->outputs[i].pin_type = type_float; /* default pin type */ } /* default pin spacing */ node->pin_spacing.in_padding_x = 2; node->pin_spacing.in_padding_y = 32 + 25/2 + 6; // titlebar height + next half row + adjust node->pin_spacing.in_spacing_y = 25; // row height+border node->pin_spacing.out_padding_x = 3; node->pin_spacing.out_padding_y = 32 + 25/2 + 6; // titlebar height + next half row + adjust node->pin_spacing.out_spacing_y = 25; // row height+border strcpy(node->name, name); node_editor_push(editor, node); return node; } void *node_editor_eval_connected(struct node* node, int input_pin_number) { NK_ASSERT(node->inputs[input_pin_number].is_connected); return node->inputs[input_pin_number].connected_node->eval_func(node->inputs[input_pin_number].connected_node, node->inputs[input_pin_number].connected_pin); } static void node_editor_link(struct node_editor *editor, struct node *in_node, int in_pin, struct node *out_node, int out_pin) { /* Confusingly, in and out nodes/pins 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 { for (int 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_pin].is_connected = nk_true; in_node->outputs[in_pin].is_connected = nk_true; out_node->inputs[out_pin].connected_node = in_node; out_node->inputs[out_pin].connected_pin = in_pin; link->input_node = in_node; link->input_pin = in_pin; link->output_node = out_node; link->output_pin = out_pin; link->is_active = nk_true; } else { puts("Too many links"); } } static void node_editor_init(struct node_editor *editor) { if (editor->initialized) return; struct nk_rect total_space = nk_window_get_content_region(ui_ctx); struct nk_vec2 output_node_position = { total_space.w*2/3, total_space.h/3 }; struct nk_vec2 color_node_position = { total_space.w*1/4, total_space.h/3 }; memset(editor, 0, sizeof(*editor)); editor->output_node = node_output_create(editor, output_node_position); node_color_create(editor, color_node_position); editor->show_grid = nk_true; editor->initialized = 1; } static int node_editor(struct node_editor *editor) { int n = 0; struct nk_rect total_space; const struct nk_input *in = &ui_ctx->input; struct nk_command_buffer *canvas = nk_window_get_canvas(ui_ctx); struct node *updated = 0; node_editor_init(editor); { /* allocate complete window space */ total_space = nk_window_get_content_region(ui_ctx); nk_layout_space_begin(ui_ctx, NK_STATIC, total_space.h, editor->node_count); { struct node *it = editor->begin; struct nk_rect size = nk_layout_space_bounds(ui_ctx); struct nk_panel *nodePanel = 0; //nk_fill_rect(canvas, size, 0/*rounding*/, ((struct nk_color){30,30,30,255})); // 20,30,40,255 nk_fill_rect_multi_color(canvas, size, GRID_BG_COLORS); if (editor->show_grid) { /* display grid */ for (float 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, GRID_THICKNESS, GRID_COLOR); for (float 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, GRID_THICKNESS, 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(ui_ctx, nk_rect(it->bounds.x - editor->scrolling.x, it->bounds.y - editor->scrolling.y, it->bounds.w, it->bounds.h)); /* execute node window */ char *name = va(" " ICON_MD_MENU " %s",it->name); //< @r-lyeh added some spacing+icon because of our UI customizations struct nk_color bak = ui_ctx->style.window.fixed_background.data.color; ui_ctx->style.window.fixed_background.data.color = BG_COLOR; if (nk_group_begin(ui_ctx, name, nodePanel_flags)) { /* always have last selected node on top */ nodePanel = nk_window_get_panel(ui_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(ui_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 (int 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 (int 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(ui_ctx, it); /* ==================================================== */ } nk_group_end(ui_ctx); } ui_ctx->style.window.fixed_background.data.color = bak; if (!(nodePanel->flags & NK_WINDOW_HIDDEN)) { /* node pin and linking */ struct nk_rect bounds; bounds = nk_layout_space_rect_to_local(ui_ctx, nodePanel->bounds); bounds.x += editor->scrolling.x; bounds.y += editor->scrolling.y; it->bounds = bounds; /* output pins */ for (int n = 0; n < it->output_count; ++n) { struct nk_rect circle; struct nk_vec2 pt = {nodePanel->bounds.x, nodePanel->bounds.y}; pt.x += nodePanel->bounds.w - PIN_RADIUS / 2 + it->pin_spacing.out_padding_x; pt.y += it->pin_spacing.out_padding_y + it->pin_spacing.out_spacing_y * (n); PIN_DRAW(it->outputs[n],pt,PIN_RADIUS); /* 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_pin = n; } /* draw link being dragged (from linked pin to mouse position) */ if (editor->linking.active && editor->linking.node == it && editor->linking.input_pin == n) { LINK_DRAW(vec2(circle.x+3,circle.y+3),ptr2(&in->mouse.pos.x),COLOR_FLOW_HI); } } /* input pins */ for (int n = 0; n < it->input_count; ++n) { struct nk_rect circle; struct nk_vec2 pt = {nodePanel->bounds.x, nodePanel->bounds.y}; pt.x += it->pin_spacing.in_padding_x; pt.y += it->pin_spacing.in_padding_y + it->pin_spacing.in_spacing_y * (n); PIN_DRAW(it->inputs[n],pt,PIN_RADIUS); /* 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_pin = node_relink->input_pin; 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].pin_type == editor->linking.node->outputs[editor->linking.input_pin].pin_type && it->inputs[n].is_connected != nk_true) { editor->linking.active = nk_false; node_editor_link(editor, editor->linking.node, editor->linking.input_pin, it, n); } } } it = it->next; } /* reset (output) linking connection */ if (editor->linking.active && (!!input(KEY_LCTRL) || !!input(KEY_RCTRL) || nk_input_is_mouse_released(in, NK_BUTTON_LEFT))) { editor->linking.active = nk_false; editor->linking.node = NULL; } /* draw each static link */ for (int 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(ui_ctx, nk_vec2(ni->bounds.x + ni->bounds.w + ni->pin_spacing.out_padding_x, 3.0f + ni->bounds.y + ni->pin_spacing.out_padding_y + ni->pin_spacing.out_spacing_y * (link->input_pin))); struct nk_vec2 l1 = nk_layout_space_to_screen(ui_ctx, nk_vec2(no->bounds.x + no->pin_spacing.in_padding_x, 3.0f + no->bounds.y + no->pin_spacing.in_padding_y + no->pin_spacing.in_spacing_y * (link->output_pin))); l0.x -= editor->scrolling.x; l0.y -= editor->scrolling.y; l1.x -= editor->scrolling.x; l1.y -= editor->scrolling.y; struct nk_color color = node_get_type_color(no->inputs[link->output_pin].pin_type); LINK_DRAW(ptr2(&l0.x), ptr2(&l1.x), color); } } 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(ui_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(ui_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(ui_ctx, 0, nk_vec2(150, 220), nk_window_get_bounds(ui_ctx))) { struct nk_vec2 wincoords = { in->mouse.pos.x-total_space.x-50, in->mouse.pos.y-total_space.y-32 }; #if 1 static char *filter = 0; static int do_filter = 0; if( input_down(KEY_F) ) if( input(KEY_LCTRL) || input(KEY_RCTRL) ) do_filter ^= 1; int choice = ui_toolbar(ICON_MD_SEARCH ";"); if( choice == 1 ) do_filter = 1; if( do_filter ) { ui_string(ICON_MD_CLOSE " Filter " ICON_MD_SEARCH, &filter); if( ui_label_icon_clicked_L.x > 0 && ui_label_icon_clicked_L.x <= 24 ) { // if clicked on CANCEL icon (1st icon) do_filter = 0; } } else { if( filter ) filter[0] = '\0'; } char *filter_mask = filter && filter[0] ? va("*%s*", filter) : "*"; #endif #define ui_label_filtered(lbl) (strmatchi(lbl,filter_mask) && ui_label(lbl)) int close = 0; if (ui_label_filtered("=Add Color node")) close=1,node_color_create(editor, wincoords); if (ui_label_filtered("=Add Float node")) close=1,node_float_create(editor, wincoords); if (ui_label_filtered("=Add Blend Node")) close=1,node_blend_create(editor, wincoords); if (ui_label_filtered(editor->show_grid ? "=Hide Grid" : "=Show Grid")) close=1,editor->show_grid = !editor->show_grid; if(close) do_filter = 0, (filter ? filter[0] = '\0' : '\0'), nk_contextual_close(ui_ctx); nk_contextual_end(ui_ctx); } } nk_layout_space_end(ui_ctx); /* window content scrolling */ if (nk_input_is_mouse_hovering_rect(in, nk_window_get_bounds(ui_ctx)) && nk_input_is_mouse_down(in, NK_BUTTON_MIDDLE)) { editor->scrolling.x += in->mouse.delta.x; editor->scrolling.y += in->mouse.delta.y; } } return !nk_window_is_closed(ui_ctx, "NodeEdit"); } int editor_nodes(int window_mode) { window_mode = EDITOR_WINDOW; // force window if( editor_begin(NODES_TITLE, window_mode) ) { static struct node_editor nodeEditor = {0}; node_editor(&nodeEditor); editor_end(window_mode); } return 0; } AUTORUN { array_push(editor.subeditors, editor_nodes); }