Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHans Goudey <h.goudey@me.com>2021-12-15 18:51:57 +0300
committerHans Goudey <h.goudey@me.com>2021-12-15 18:51:57 +0300
commit11be151d58ec0ca955f019b0eed738e3245110b8 (patch)
tree5f9673d5a91060fb3bb2156dddb32c2e95409b48 /source/blender/editors/space_node
parent474adc6f883c2d5a854d7324364f7996044d83cb (diff)
Node Editor: Link Drag Search Menu
This commit adds a search menu when links are dragged above empty space. When releasing the drag, a menu displays all compatible sockets with the source link. The "main" sockets (usually the first) are weighted above other sockets in the search, so they appear first when you type the name of the node. A few special operators for creating a reroute or a group input node are also added to the search. Translation is started after choosing a node so it can be placed quickly, since users would likely adjust the position after anyway. A small "+" is displayed next to the cursor to give a hint about this. Further improvements are possible after this first iteration: - Support custom node trees. - Better drawing of items in the search menu. - Potential tweaks to filtering of items, depending on user feedback. Thanks to Juanfran Matheu for developing an initial patch. Differential Revision: https://developer.blender.org/D8286
Diffstat (limited to 'source/blender/editors/space_node')
-rw-r--r--source/blender/editors/space_node/CMakeLists.txt1
-rw-r--r--source/blender/editors/space_node/link_drag_search.cc291
-rw-r--r--source/blender/editors/space_node/node_intern.hh35
-rw-r--r--source/blender/editors/space_node/node_relationships.cc155
4 files changed, 458 insertions, 24 deletions
diff --git a/source/blender/editors/space_node/CMakeLists.txt b/source/blender/editors/space_node/CMakeLists.txt
index e88d61fe880..94b67e43651 100644
--- a/source/blender/editors/space_node/CMakeLists.txt
+++ b/source/blender/editors/space_node/CMakeLists.txt
@@ -39,6 +39,7 @@ set(INC
set(SRC
drawnode.cc
+ link_drag_search.cc
node_add.cc
node_context_path.cc
node_draw.cc
diff --git a/source/blender/editors/space_node/link_drag_search.cc b/source/blender/editors/space_node/link_drag_search.cc
new file mode 100644
index 00000000000..e1ba36e81c0
--- /dev/null
+++ b/source/blender/editors/space_node/link_drag_search.cc
@@ -0,0 +1,291 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "BLI_listbase.h"
+#include "BLI_string_search.h"
+
+#include "DNA_space_types.h"
+
+#include "BKE_context.h"
+
+#include "NOD_socket_search_link.hh"
+
+#include "BLT_translation.h"
+
+#include "RNA_access.h"
+
+#include "WM_api.h"
+
+#include "node_intern.hh"
+
+using blender::nodes::SocketLinkOperation;
+
+namespace blender::ed::space_node {
+
+struct LinkDragSearchStorage {
+ bNode &from_node;
+ bNodeSocket &from_socket;
+ float2 cursor;
+ Vector<SocketLinkOperation> search_link_ops;
+ char search[256];
+
+ eNodeSocketInOut in_out() const
+ {
+ return static_cast<eNodeSocketInOut>(from_socket.in_out);
+ }
+};
+
+static void add_reroute_node_fn(nodes::LinkSearchOpParams &params)
+{
+ bNode &reroute = params.add_node("NodeReroute");
+ if (params.socket.in_out == SOCK_IN) {
+ nodeAddLink(&params.node_tree,
+ &reroute,
+ static_cast<bNodeSocket *>(reroute.outputs.first),
+ &params.node,
+ &params.socket);
+ }
+ else {
+ nodeAddLink(&params.node_tree,
+ &params.node,
+ &params.socket,
+ &reroute,
+ static_cast<bNodeSocket *>(reroute.inputs.first));
+ }
+}
+
+static void add_group_input_node_fn(nodes::LinkSearchOpParams &params)
+{
+ /* Add a group input based on the connected socket, and add a new group input node. */
+ bNodeSocket *interface_socket = ntreeAddSocketInterfaceFromSocket(
+ &params.node_tree, &params.node, &params.socket);
+ const int group_input_index = BLI_findindex(&params.node_tree.inputs, interface_socket);
+
+ bNode &group_input = params.add_node("NodeGroupInput");
+
+ /* This is necessary to create the new sockets in the other input nodes. */
+ ntreeUpdateTree(CTX_data_main(&params.C), &params.node_tree);
+
+ /* Hide the new input in all other group input nodes, to avoid making them taller. */
+ LISTBASE_FOREACH (bNode *, node, &params.node_tree.nodes) {
+ if (node->type == NODE_GROUP_INPUT) {
+ bNodeSocket *new_group_input_socket = (bNodeSocket *)BLI_findlink(&node->outputs,
+ group_input_index);
+ new_group_input_socket->flag |= SOCK_HIDDEN;
+ }
+ }
+
+ /* Hide all existing inputs in the new group input node, to only display the new one. */
+ LISTBASE_FOREACH (bNodeSocket *, socket, &group_input.outputs) {
+ socket->flag |= SOCK_HIDDEN;
+ }
+
+ bNodeSocket *socket = (bNodeSocket *)BLI_findlink(&group_input.outputs, group_input_index);
+ if (socket == nullptr) {
+ /* Adding sockets can fail in some cases. There's no good reason not to be safe here. */
+ return;
+ }
+ /* Unhide the socket for the new input in the new node and make a connection to it. */
+ socket->flag &= ~SOCK_HIDDEN;
+ nodeAddLink(&params.node_tree, &group_input, socket, &params.node, &params.socket);
+}
+
+/**
+ * Call the callback to gather compatible socket connections for all node types, and the operations
+ * that will actually make the connections. Also add some custom operations like connecting a group
+ * output node.
+ */
+static void gather_socket_link_operations(bNodeTree &node_tree,
+ const bNodeSocket &socket,
+ Vector<SocketLinkOperation> &search_link_ops)
+{
+ NODE_TYPES_BEGIN (node_type) {
+ if (StringRef(node_type->idname).find("Legacy") != StringRef::not_found) {
+ continue;
+ }
+ const char *disabled_hint;
+ if (!(node_type->poll && node_type->poll(node_type, &node_tree, &disabled_hint))) {
+ continue;
+ }
+
+ if (node_type->gather_link_search_ops) {
+ nodes::GatherLinkSearchOpParams params{*node_type, node_tree, socket, search_link_ops};
+ node_type->gather_link_search_ops(params);
+ }
+ }
+ NODE_TYPES_END;
+
+ search_link_ops.append({IFACE_("Reroute"), add_reroute_node_fn});
+
+ const bool is_node_group = !(node_tree.id.flag & LIB_EMBEDDED_DATA);
+
+ if (is_node_group && socket.in_out == SOCK_IN) {
+ search_link_ops.append({IFACE_("Group Input"), add_group_input_node_fn});
+ }
+}
+
+static void link_drag_search_update_fn(const bContext *UNUSED(C),
+ void *arg,
+ const char *str,
+ uiSearchItems *items,
+ const bool is_first)
+{
+ LinkDragSearchStorage &storage = *static_cast<LinkDragSearchStorage *>(arg);
+
+ StringSearch *search = BLI_string_search_new();
+
+ for (SocketLinkOperation &op : storage.search_link_ops) {
+ BLI_string_search_add(search, op.name.c_str(), &op, op.weight);
+ }
+
+ /* Don't filter when the menu is first opened, but still run the search
+ * so the items are in the same order they will appear in while searching. */
+ const char *string = is_first ? "" : str;
+ SocketLinkOperation **filtered_items;
+ const int filtered_amount = BLI_string_search_query(search, string, (void ***)&filtered_items);
+
+ for (const int i : IndexRange(filtered_amount)) {
+ SocketLinkOperation &item = *filtered_items[i];
+ if (!UI_search_item_add(items, item.name.c_str(), &item, ICON_NONE, 0, 0)) {
+ break;
+ }
+ }
+
+ MEM_freeN(filtered_items);
+ BLI_string_search_free(search);
+}
+
+static void link_drag_search_exec_fn(bContext *C, void *arg1, void *arg2)
+{
+ Main &bmain = *CTX_data_main(C);
+ SpaceNode &snode = *CTX_wm_space_node(C);
+ LinkDragSearchStorage &storage = *static_cast<LinkDragSearchStorage *>(arg1);
+ SocketLinkOperation *item = static_cast<SocketLinkOperation *>(arg2);
+ if (item == nullptr) {
+ return;
+ }
+
+ node_deselect_all(snode);
+
+ Vector<bNode *> new_nodes;
+ nodes::LinkSearchOpParams params{
+ *C, *snode.edittree, storage.from_node, storage.from_socket, new_nodes};
+ item->fn(params);
+ if (new_nodes.is_empty()) {
+ return;
+ }
+
+ /* For now, assume that only one node is created by the callback. */
+ BLI_assert(new_nodes.size() == 1);
+ bNode *new_node = new_nodes.first();
+
+ new_node->locx = storage.cursor.x / UI_DPI_FAC;
+ new_node->locy = storage.cursor.y / UI_DPI_FAC + 20 * UI_DPI_FAC;
+ if (storage.in_out() == SOCK_IN) {
+ new_node->locx -= new_node->width;
+ }
+
+ nodeSetSelected(new_node, true);
+ nodeSetActive(snode.edittree, new_node);
+
+ /* Ideally it would be possible to tag the node tree in some way so it updates only after the
+ * translate operation is finished, but normally moving nodes around doesn't cause updates. */
+ ntreeUpdateTree(&bmain, snode.edittree);
+ snode_notify(*C, snode);
+ snode_dag_update(*C, snode);
+
+ /* Start translation operator with the new node. */
+ wmOperatorType *ot = WM_operatortype_find("TRANSFORM_OT_translate", true);
+ BLI_assert(ot);
+ PointerRNA ptr;
+ WM_operator_properties_create_ptr(&ptr, ot);
+ RNA_boolean_set(&ptr, "view2d_edge_pan", true);
+ WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &ptr);
+ WM_operator_properties_free(&ptr);
+}
+
+static void link_drag_search_free_fn(void *arg)
+{
+ LinkDragSearchStorage *storage = static_cast<LinkDragSearchStorage *>(arg);
+ delete storage;
+}
+
+static uiBlock *create_search_popup_block(bContext *C, ARegion *region, void *arg_op)
+{
+ LinkDragSearchStorage &storage = *(LinkDragSearchStorage *)arg_op;
+
+ bNodeTree *node_tree = CTX_wm_space_node(C)->nodetree;
+ gather_socket_link_operations(*node_tree, storage.from_socket, storage.search_link_ops);
+
+ uiBlock *block = UI_block_begin(C, region, "_popup", UI_EMBOSS);
+ UI_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_SEARCH_MENU);
+ UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
+
+ uiBut *but = uiDefSearchBut(block,
+ storage.search,
+ 0,
+ ICON_VIEWZOOM,
+ sizeof(storage.search),
+ storage.in_out() == SOCK_OUT ? 10 : 10 - UI_searchbox_size_x(),
+ 10,
+ UI_searchbox_size_x(),
+ UI_UNIT_Y,
+ 0,
+ 0,
+ "");
+ UI_but_func_search_set_sep_string(but, UI_MENU_ARROW_SEP);
+ UI_but_func_search_set(but,
+ nullptr,
+ link_drag_search_update_fn,
+ &storage,
+ false,
+ link_drag_search_free_fn,
+ link_drag_search_exec_fn,
+ nullptr);
+ UI_but_flag_enable(but, UI_BUT_ACTIVATE_ON_INIT);
+
+ /* Fake button to hold space for the search items. */
+ uiDefBut(block,
+ UI_BTYPE_LABEL,
+ 0,
+ "",
+ storage.in_out() == SOCK_OUT ? 10 : 10 - UI_searchbox_size_x(),
+ 10 - UI_searchbox_size_y(),
+ UI_searchbox_size_x(),
+ UI_searchbox_size_y(),
+ nullptr,
+ 0,
+ 0,
+ 0,
+ 0,
+ nullptr);
+
+ const int offset[2] = {0, -UI_UNIT_Y};
+ UI_block_bounds_set_popup(block, 0.3f * U.widget_unit, offset);
+ return block;
+}
+
+void invoke_node_link_drag_add_menu(bContext &C,
+ bNode &node,
+ bNodeSocket &socket,
+ const float2 &cursor)
+{
+ LinkDragSearchStorage *storage = new LinkDragSearchStorage{node, socket, cursor};
+ /* Use the "_ex" variant with `can_refresh` false to avoid a double free when closing Blender. */
+ UI_popup_block_invoke_ex(&C, create_search_popup_block, storage, nullptr, false);
+}
+
+} // namespace blender::ed::space_node \ No newline at end of file
diff --git a/source/blender/editors/space_node/node_intern.hh b/source/blender/editors/space_node/node_intern.hh
index f3ba405c6d0..2e55bb0cb28 100644
--- a/source/blender/editors/space_node/node_intern.hh
+++ b/source/blender/editors/space_node/node_intern.hh
@@ -43,6 +43,9 @@ struct bNodeLink;
struct bNodeSocket;
struct wmGizmoGroupType;
struct wmKeyConfig;
+namespace blender {
+struct float2;
+}
struct wmWindow;
/** Temporary data used in node link drag modal operator. */
@@ -50,14 +53,29 @@ struct bNodeLinkDrag {
/** Links dragged by the operator. */
blender::Vector<bNodeLink *> links;
bool from_multi_input_socket;
- int in_out;
+ eNodeSocketInOut in_out;
+
+ /** Draw handler for the "+" icon when dragging a link in empty space. */
+ void *draw_handle;
/** Temporarily stores the last picked link from multi-input socket operator. */
- struct bNodeLink *last_picked_multi_input_socket_link;
+ bNodeLink *last_picked_multi_input_socket_link;
+
+ /**
+ * Temporarily stores the last hovered socket for multi-input socket operator.
+ * Store it to recalculate sorting after it is no longer hovered.
+ */
+ bNode *last_node_hovered_while_dragging_a_link;
- /** Temporarily stores the last hovered socket for multi-input socket operator.
- * Store it to recalculate sorting after it is no longer hovered. */
- struct bNode *last_node_hovered_while_dragging_a_link;
+ /* The cursor position, used for drawing a + icon when dragging a node link. */
+ std::array<int, 2> cursor;
+
+ /** The node the drag started at. */
+ bNode *start_node;
+ /** The socket the drag started at. */
+ bNodeSocket *start_socket;
+ /** The number of links connected to the #start_socket when the drag started. */
+ int start_link_count;
/* Data for edge panning */
View2DEdgePanData pan_data;
@@ -327,4 +345,9 @@ namespace blender::ed::space_node {
Vector<ui::ContextPathItem> context_path_for_space_node(const bContext &C);
-}
+void invoke_node_link_drag_add_menu(bContext &C,
+ bNode &node,
+ bNodeSocket &socket,
+ const float2 &cursor);
+
+} // namespace blender::ed::space_node
diff --git a/source/blender/editors/space_node/node_relationships.cc b/source/blender/editors/space_node/node_relationships.cc
index ce07496068d..90b53258d5e 100644
--- a/source/blender/editors/space_node/node_relationships.cc
+++ b/source/blender/editors/space_node/node_relationships.cc
@@ -39,6 +39,7 @@
#include "ED_node.h" /* own include */
#include "ED_render.h"
#include "ED_screen.h"
+#include "ED_space_api.h"
#include "ED_spreadsheet.h"
#include "ED_util.h"
@@ -50,6 +51,9 @@
#include "WM_api.h"
#include "WM_types.h"
+#include "GPU_state.h"
+
+#include "UI_interface_icons.h"
#include "UI_resources.h"
#include "UI_view2d.h"
@@ -57,11 +61,15 @@
#include "NOD_node_declaration.hh"
#include "NOD_node_tree_ref.hh"
+#include "NOD_socket_declarations.hh"
+#include "NOD_socket_declarations_geometry.hh"
#include "node_intern.hh" /* own include */
using namespace blender::nodes::node_tree_ref_types;
using blender::float2;
+using blender::StringRef;
+using blender::StringRefNull;
using blender::Vector;
/* -------------------------------------------------------------------- */
@@ -890,6 +898,83 @@ void NODE_OT_link_viewer(wmOperatorType *ot)
/** \name Add Link Operator
* \{ */
+/**
+ * Check if any of the dragged links are connected to a socket on the side that they are dragged
+ * from.
+ */
+static bool dragged_links_are_detached(const bNodeLinkDrag &nldrag)
+{
+ if (nldrag.in_out == SOCK_OUT) {
+ for (const bNodeLink *link : nldrag.links) {
+ if (link->tonode && link->tosock) {
+ return false;
+ }
+ }
+ }
+ else {
+ for (const bNodeLink *link : nldrag.links) {
+ if (link->fromnode && link->fromsock) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+static bool should_create_drag_link_search_menu(const bNodeTree &node_tree,
+ const bNodeLinkDrag &nldrag)
+{
+ /* Custom node trees aren't supported yet. */
+ if (node_tree.type == NTREE_CUSTOM) {
+ return false;
+ }
+ /* Only create the search menu when the drag has not already connected the links to a socket. */
+ if (!dragged_links_are_detached(nldrag)) {
+ return false;
+ }
+ /* Don't create the search menu if the drag is disconnecting a link from an input node. */
+ if (nldrag.start_socket->in_out == SOCK_IN && nldrag.start_link_count > 0) {
+ return false;
+ }
+ /* Don't allow a drag from the "new socket" of a group input node. Handling these
+ * properly in node callbacks increases the complexity too much for now. */
+ if (ELEM(nldrag.start_node->type, NODE_GROUP_INPUT, NODE_GROUP_OUTPUT)) {
+ if (nldrag.start_socket->type == SOCK_CUSTOM) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static void draw_draglink_tooltip(const bContext *UNUSED(C), ARegion *UNUSED(region), void *arg)
+{
+ bNodeLinkDrag *nldrag = static_cast<bNodeLinkDrag *>(arg);
+
+ const uchar text_col[4] = {255, 255, 255, 255};
+ const int padding = 4 * UI_DPI_FAC;
+ const float x = nldrag->in_out == SOCK_IN ? nldrag->cursor[0] - 3.3f * padding :
+ nldrag->cursor[0];
+ const float y = nldrag->cursor[1] - 2.0f * UI_DPI_FAC;
+
+ UI_icon_draw_ex(x, y, ICON_ADD, U.inv_dpi_fac, 1.0f, 0.0f, text_col, false);
+}
+
+static void draw_draglink_tooltip_activate(const ARegion &region, bNodeLinkDrag &nldrag)
+{
+ if (nldrag.draw_handle == nullptr) {
+ nldrag.draw_handle = ED_region_draw_cb_activate(
+ region.type, draw_draglink_tooltip, &nldrag, REGION_DRAW_POST_PIXEL);
+ }
+}
+
+static void draw_draglink_tooltip_deactivate(const ARegion &region, bNodeLinkDrag &nldrag)
+{
+ if (nldrag.draw_handle) {
+ ED_region_draw_cb_exit(region.type, nldrag.draw_handle);
+ nldrag.draw_handle = nullptr;
+ }
+}
+
static void node_link_update_header(bContext *C, bNodeLinkDrag *UNUSED(nldrag))
{
char header[UI_MAX_DRAW_STR];
@@ -952,12 +1037,11 @@ static void node_remove_extra_links(SpaceNode &snode, bNodeLink &link)
static void node_link_exit(bContext &C, wmOperator &op, const bool apply_links)
{
Main *bmain = CTX_data_main(&C);
+ ARegion &region = *CTX_wm_region(&C);
SpaceNode &snode = *CTX_wm_space_node(&C);
bNodeTree &ntree = *snode.edittree;
bNodeLinkDrag *nldrag = (bNodeLinkDrag *)op.customdata;
bool do_tag_update = false;
- /* View will be reset if no links connect. */
- bool reset_view = true;
/* avoid updates while applying links */
ntree.is_updating = true;
@@ -995,8 +1079,6 @@ static void node_link_exit(bContext &C, wmOperator &op, const bool apply_links)
if (link->tonode) {
do_tag_update |= (do_tag_update || node_connected_to_output(*bmain, ntree, *link->tonode));
}
-
- reset_view = false;
}
else {
nodeRemLink(&ntree, link);
@@ -1010,9 +1092,12 @@ static void node_link_exit(bContext &C, wmOperator &op, const bool apply_links)
snode_dag_update(C, snode);
}
- if (reset_view) {
- UI_view2d_edge_pan_cancel(&C, &nldrag->pan_data);
- }
+ /* Ensure draglink tooltip is disabled. */
+ draw_draglink_tooltip_deactivate(*CTX_wm_region(&C), *nldrag);
+
+ ED_workspace_status_text(&C, nullptr);
+ ED_region_tag_redraw(&region);
+ clear_picking_highlight(&snode.edittree->links);
snode.runtime->linkdrag.reset();
}
@@ -1099,12 +1184,15 @@ static void node_link_find_socket(bContext &C, wmOperator &op, const float2 &cur
static int node_link_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
bNodeLinkDrag *nldrag = (bNodeLinkDrag *)op->customdata;
+ SpaceNode &snode = *CTX_wm_space_node(C);
ARegion *region = CTX_wm_region(C);
UI_view2d_edge_pan_apply_event(C, &nldrag->pan_data, event);
float2 cursor;
UI_view2d_region_to_view(&region->v2d, event->mval[0], event->mval[1], &cursor.x, &cursor.y);
+ nldrag->cursor[0] = event->mval[0];
+ nldrag->cursor[1] = event->mval[1];
switch (event->type) {
case MOUSEMOVE:
@@ -1117,22 +1205,46 @@ static int node_link_modal(bContext *C, wmOperator *op, const wmEvent *event)
node_link_update_header(C, nldrag);
ED_region_tag_redraw(region);
}
- break;
+ if (should_create_drag_link_search_menu(*snode.edittree, *nldrag)) {
+ draw_draglink_tooltip_activate(*region, *nldrag);
+ }
+ else {
+ draw_draglink_tooltip_deactivate(*region, *nldrag);
+ }
+ break;
case LEFTMOUSE:
+ if (event->val == KM_RELEASE) {
+ /* Add a search menu for compatible sockets if the drag released on empty space. */
+ if (should_create_drag_link_search_menu(*snode.edittree, *nldrag)) {
+ bNodeLink &link = *nldrag->links.first();
+ if (nldrag->in_out == SOCK_OUT) {
+ blender::ed::space_node::invoke_node_link_drag_add_menu(
+ *C, *link.fromnode, *link.fromsock, cursor);
+ }
+ else {
+ blender::ed::space_node::invoke_node_link_drag_add_menu(
+ *C, *link.tonode, *link.tosock, cursor);
+ }
+ }
+
+ /* Finish link. */
+ node_link_exit(*C, *op, true);
+ return OPERATOR_FINISHED;
+ }
+ break;
case RIGHTMOUSE:
case MIDDLEMOUSE: {
if (event->val == KM_RELEASE) {
node_link_exit(*C, *op, true);
-
- ED_workspace_status_text(C, nullptr);
- ED_region_tag_redraw(region);
- SpaceNode &snode = *CTX_wm_space_node(C);
- clear_picking_highlight(&snode.edittree->links);
return OPERATOR_FINISHED;
}
break;
}
+ case EVT_ESCKEY: {
+ node_link_exit(*C, *op, true);
+ return OPERATOR_FINISHED;
+ }
}
return OPERATOR_RUNNING_MODAL;
@@ -1148,10 +1260,11 @@ static std::unique_ptr<bNodeLinkDrag> node_link_init(Main &bmain,
bNodeSocket *sock;
if (node_find_indicated_socket(snode, &node, &sock, cursor, SOCK_OUT)) {
std::unique_ptr<bNodeLinkDrag> nldrag = std::make_unique<bNodeLinkDrag>();
-
- const int num_links = nodeCountSocketLinks(snode.edittree, sock);
+ nldrag->start_node = node;
+ nldrag->start_socket = sock;
+ nldrag->start_link_count = nodeCountSocketLinks(snode.edittree, sock);
int link_limit = nodeSocketLinkLimit(sock);
- if (num_links > 0 && (num_links >= link_limit || detach)) {
+ if (nldrag->start_link_count > 0 && (nldrag->start_link_count >= link_limit || detach)) {
/* dragged links are fixed on input side */
nldrag->in_out = SOCK_IN;
/* detach current links and store them in the operator data */
@@ -1192,9 +1305,11 @@ static std::unique_ptr<bNodeLinkDrag> node_link_init(Main &bmain,
if (node_find_indicated_socket(snode, &node, &sock, cursor, SOCK_IN)) {
std::unique_ptr<bNodeLinkDrag> nldrag = std::make_unique<bNodeLinkDrag>();
nldrag->last_node_hovered_while_dragging_a_link = node;
+ nldrag->start_node = node;
+ nldrag->start_socket = sock;
- const int num_links = nodeCountSocketLinks(snode.edittree, sock);
- if (num_links > 0) {
+ nldrag->start_link_count = nodeCountSocketLinks(snode.edittree, sock);
+ if (nldrag->start_link_count > 0) {
/* dragged links are fixed on output side */
nldrag->in_out = SOCK_OUT;
/* detach current links and store them in the operator data */
@@ -1260,6 +1375,10 @@ static int node_link_invoke(bContext *C, wmOperator *op, const wmEvent *event)
if (nldrag) {
UI_view2d_edge_pan_operator_init(C, &nldrag->pan_data, op);
+ /* Add "+" icon when the link is dragged in empty space. */
+ if (should_create_drag_link_search_menu(*snode.edittree, *nldrag)) {
+ draw_draglink_tooltip_activate(*CTX_wm_region(C), *nldrag);
+ }
snode.runtime->linkdrag = std::move(nldrag);
op->customdata = snode.runtime->linkdrag.get();