Blender V5.0
link_drag_search.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "AS_asset_library.hh"
7
8#include "BLI_listbase.h"
9
10#include "DNA_space_types.h"
11
12#include "BKE_asset.hh"
13#include "BKE_context.hh"
14#include "BKE_idprop.hh"
15#include "BKE_lib_id.hh"
17#include "BKE_node_runtime.hh"
19#include "BKE_screen.hh"
20
21#include "UI_string_search.hh"
22
23#include "NOD_socket.hh"
25
26#include "BLT_translation.hh"
27
28#include "WM_api.hh"
29
31
32#include "ED_asset.hh"
33#include "ED_node.hh"
34
35#include "node_intern.hh"
36
38
40
54
56{
57 LinkDragSearchStorage &storage = *static_cast<LinkDragSearchStorage *>(arg);
58 const wmNotifier *wmn = params->notifier;
59
60 switch (wmn->category) {
61 case NC_ASSET:
62 if (wmn->data == ND_ASSET_LIST_READING) {
63 storage.update_items_tag = true;
64 }
65 break;
66 }
67}
68
70{
71 bNode &reroute = params.add_node("NodeReroute");
72 if (params.socket.in_out == SOCK_IN) {
73 bke::node_add_link(params.node_tree,
74 reroute,
75 *static_cast<bNodeSocket *>(reroute.outputs.first),
76 params.node,
77 params.socket);
78 }
79 else {
80 bke::node_add_link(params.node_tree,
81 params.node,
82 params.socket,
83 reroute,
84 *static_cast<bNodeSocket *>(reroute.inputs.first));
85 }
86}
87
89{
90 /* Add a group input based on the connected socket, and add a new group input node. */
92 params.node_tree,
93 params.node,
94 params.socket,
95 params.socket.typeinfo->idname,
96 params.socket.name);
97 params.node_tree.tree_interface.active_item_set(&socket_iface->item);
98
99 bNode &group_input = params.add_node("NodeGroupInput");
100
101 /* This is necessary to create the new sockets in the other input nodes. */
103
104 /* Hide the new input in all other group input nodes, to avoid making them taller. */
105 for (bNode *node : params.node_tree.all_nodes()) {
106 if (node->is_group_input()) {
107 bNodeSocket *new_group_input_socket = bke::node_find_socket(
108 *node, SOCK_OUT, socket_iface->identifier);
109 if (new_group_input_socket) {
110 new_group_input_socket->flag |= SOCK_HIDDEN;
111 }
112 }
113 }
114
115 /* Hide all existing inputs in the new group input node, to only display the new one. */
116 LISTBASE_FOREACH (bNodeSocket *, socket, &group_input.outputs) {
117 socket->flag |= SOCK_HIDDEN;
118 }
119
120 bNodeSocket *socket = bke::node_find_socket(group_input, SOCK_OUT, socket_iface->identifier);
121 if (socket) {
122 /* Unhide the socket for the new input in the new node and make a connection to it. */
123 socket->flag &= ~SOCK_HIDDEN;
124 bke::node_add_link(params.node_tree, group_input, *socket, params.node, params.socket);
125
127 *CTX_data_main(&params.C), params.node_tree, params.socket, *socket);
128 }
129}
130
132 const bNodeTreeInterfaceSocket &interface_socket)
133{
134 const eNodeSocketInOut in_out = eNodeSocketInOut(params.socket.in_out);
138
139 bNode &group_input = params.add_node("NodeGroupInput");
140
141 LISTBASE_FOREACH (bNodeSocket *, socket, &group_input.outputs) {
142 socket->flag |= SOCK_HIDDEN;
143 }
144
145 bNodeSocket *socket = bke::node_find_socket(group_input, SOCK_OUT, interface_socket.identifier);
146 if (socket != nullptr) {
147 socket->flag &= ~SOCK_HIDDEN;
148 bke::node_add_link(params.node_tree, group_input, *socket, params.node, params.socket);
149 }
150}
151
158 const bNodeSocket &socket,
160 Vector<SocketLinkOperation> &search_link_ops)
161{
162 const AssetMetaData &asset_data = asset.get_metadata();
163 const IDProperty *tree_type = BKE_asset_metadata_idprop_find(&asset_data, "type");
164 if (tree_type == nullptr || IDP_int_get(tree_type) != node_tree.type) {
165 return;
166 }
167
168 const bke::bNodeTreeType &node_tree_type = *node_tree.typeinfo;
169 const eNodeSocketInOut in_out = socket.in_out == SOCK_OUT ? SOCK_IN : SOCK_OUT;
170
172 &asset_data, in_out == SOCK_IN ? "inputs" : "outputs");
173
174 int weight = -1;
175 Set<StringRef> socket_names;
176 LISTBASE_FOREACH (IDProperty *, socket_property, &sockets->data.group) {
177 if (socket_property->type != IDP_STRING) {
178 continue;
179 }
180 const char *socket_idname = IDP_string_get(socket_property);
181 const bke::bNodeSocketType *socket_type = bke::node_socket_type_find(socket_idname);
182 if (socket_type == nullptr) {
183 continue;
184 }
186 eNodeSocketDatatype to = socket_type->type;
187 if (socket.in_out == SOCK_OUT) {
188 std::swap(from, to);
189 }
190 if (node_tree_type.validate_link && !node_tree_type.validate_link(from, to)) {
191 continue;
192 }
193 if (!socket_names.add(socket_property->name)) {
194 /* See comment in #search_link_ops_for_declarations. */
195 continue;
196 }
197
198 const StringRef asset_name = asset.get_name();
199 const StringRef socket_name = socket_property->name;
200
201 search_link_ops.append(
202 {asset_name + " " + UI_MENU_ARROW_SEP + socket_name,
203 [&asset, socket_property, in_out](nodes::LinkSearchOpParams &params) {
204 Main &bmain = *CTX_data_main(&params.C);
205
206 bNode &node = params.add_node(params.node_tree.typeinfo->group_idname);
207
208 bNodeTree *group = reinterpret_cast<bNodeTree *>(
210 node.id = &group->id;
211 id_us_plus(node.id);
214
215 node.flag &= ~NODE_OPTIONS;
216 node.width = group->default_group_node_width;
217
218 /* Create the inputs and outputs on the new node. */
220
222 node, in_out, socket_property->name);
223 if (new_node_socket != nullptr) {
224 /* Rely on the way #node_add_link switches in/out if necessary. */
226 params.node_tree, params.node, params.socket, node, *new_node_socket);
227 }
228 },
229 weight});
230
231 weight--;
232 }
233}
234
236 const bNodeTree &node_tree,
237 const bNodeSocket &socket,
238 Vector<SocketLinkOperation> &search_link_ops)
239{
241 asset::AssetFilterSettings filter_settings{};
242 filter_settings.id_types = FILTER_ID_NT;
243
244 asset::list::storage_fetch(&library_ref, &C);
246 if (!asset::filter_matches_asset(&filter_settings, asset)) {
247 return true;
248 }
249 search_link_ops_for_asset_metadata(node_tree, socket, asset, search_link_ops);
250 return true;
251 });
252}
253
260 bNodeTree &node_tree,
261 const bNodeSocket &socket,
262 Vector<SocketLinkOperation> &search_link_ops)
263{
264 const SpaceNode &snode = *CTX_wm_space_node(&C);
265 for (const bke::bNodeType *node_type : bke::node_types_get()) {
266 const char *disabled_hint;
267 if (node_type->poll && !node_type->poll(node_type, &node_tree, &disabled_hint)) {
268 continue;
269 }
270 if (node_type->add_ui_poll && !node_type->add_ui_poll(&C)) {
271 continue;
272 }
273 if (StringRefNull(node_type->ui_name).endswith("(Legacy)")) {
274 continue;
275 }
276 if (node_type->gather_link_search_ops) {
278 *node_type, snode, node_tree, socket, search_link_ops};
279 node_type->gather_link_search_ops(params);
280 }
281 }
282
283 search_link_ops.append({IFACE_("Reroute"), add_reroute_node_fn});
284
285 const bool is_node_group = !(node_tree.id.flag & ID_FLAG_EMBEDDED_DATA);
286
287 if (is_node_group && socket.in_out == SOCK_IN) {
288 search_link_ops.append({IFACE_("Group Input"), add_group_input_node_fn});
289
290 int weight = -1;
291 node_tree.tree_interface.foreach_item([&](const bNodeTreeInterfaceItem &item) {
292 if (item.item_type != NODE_INTERFACE_SOCKET) {
293 return true;
294 }
295 const bNodeTreeInterfaceSocket &interface_socket =
296 reinterpret_cast<const bNodeTreeInterfaceSocket &>(item);
297 if (!(interface_socket.flag & NODE_INTERFACE_SOCKET_INPUT)) {
298 return true;
299 }
300 {
302 interface_socket.socket_type);
303 const eNodeSocketDatatype from = from_typeinfo ? from_typeinfo->type : SOCK_CUSTOM;
304 const eNodeSocketDatatype to = socket.typeinfo->type;
305 if (node_tree.typeinfo->validate_link && !node_tree.typeinfo->validate_link(from, to)) {
306 return true;
307 }
308 }
309 search_link_ops.append({std::string(IFACE_("Group Input")) + " " + UI_MENU_ARROW_SEP +
310 (interface_socket.name ? interface_socket.name : ""),
311 [interface_socket](nodes::LinkSearchOpParams &params) {
312 add_existing_group_input_fn(params, interface_socket);
313 },
314 weight});
315 weight--;
316 return true;
317 });
318 }
319
320 gather_search_link_ops_for_all_assets(C, node_tree, socket, search_link_ops);
321}
322
324 const bContext *C, void *arg, const char *str, uiSearchItems *items, const bool is_first)
325{
326 LinkDragSearchStorage &storage = *static_cast<LinkDragSearchStorage *>(arg);
327 if (storage.update_items_tag) {
328 bNodeTree *node_tree = CTX_wm_space_node(C)->edittree;
329 storage.search_link_ops.clear();
330 gather_socket_link_operations(*C, *node_tree, storage.from_socket, storage.search_link_ops);
331 storage.update_items_tag = false;
332 }
333
336
337 for (SocketLinkOperation &op : storage.search_link_ops) {
338 search.add(op.name, &op, op.weight);
339 }
340
341 /* Don't filter when the menu is first opened, but still run the search
342 * so the items are in the same order they will appear in while searching. */
343 const char *string = is_first ? "" : str;
344 const Vector<SocketLinkOperation *> filtered_items = search.query(string);
345
346 for (SocketLinkOperation *item : filtered_items) {
347 if (!UI_search_item_add(items, item->name, item, ICON_NONE, 0, 0)) {
348 break;
349 }
350 }
351}
352
353static bNode *get_new_linked_node(bNodeSocket &socket, const Span<bNode *> new_nodes)
354{
355 for (const bNodeLink *link : socket.directly_linked_links()) {
356 if (new_nodes.contains(link->fromnode)) {
357 return link->fromnode;
358 }
359 if (new_nodes.contains(link->tonode)) {
360 return link->tonode;
361 }
362 }
363 return nullptr;
364}
365
366static void link_drag_search_exec_fn(bContext *C, void *arg1, void *arg2)
367{
368 Main &bmain = *CTX_data_main(C);
369 SpaceNode &snode = *CTX_wm_space_node(C);
370 bNodeTree &node_tree = *snode.edittree;
371 LinkDragSearchStorage &storage = *static_cast<LinkDragSearchStorage *>(arg1);
372 SocketLinkOperation *item = static_cast<SocketLinkOperation *>(arg2);
373 if (item == nullptr) {
374 return;
375 }
376
377 node_deselect_all(node_tree);
378
379 Vector<bNode *> new_nodes;
381 *C, node_tree, storage.from_node, storage.from_socket, new_nodes};
382 item->fn(params);
383 if (new_nodes.is_empty()) {
384 return;
385 }
386
387 /* Used to position the new nodes where the cursor is. */
388 const float2 cursor_offset = (storage.cursor / UI_SCALE_FAC) + float2(0.0f, 20.0f);
389
390 /* Used to position the new nodes so that the newly linked socket is aligned to the cursor. */
391 float2 link_offset{};
392 node_tree.ensure_topology_cache();
393 if (bNode *new_directly_linked_node = get_new_linked_node(storage.from_socket, new_nodes)) {
394 link_offset -= new_directly_linked_node->location;
395 if (storage.in_out() == SOCK_IN) {
396 link_offset.x -= new_directly_linked_node->width;
397 }
398 }
399
400 const float2 offset_in_tree = cursor_offset + link_offset;
401 for (bNode *new_node : new_nodes) {
402 /* The node may have an initial offset already, so use +=. */
403 new_node->location[0] += offset_in_tree.x;
404 new_node->location[1] += offset_in_tree.y;
405 bke::node_set_selected(*new_node, true);
406 }
407 bke::node_set_active(node_tree, *new_nodes[0]);
408
409 /* Ideally it would be possible to tag the node tree in some way so it updates only after the
410 * translate operation is finished, but normally moving nodes around doesn't cause updates. */
411 BKE_main_ensure_invariants(bmain, node_tree.id);
412
413 /* Start translation operator with the new node. */
414 wmOperatorType *ot = WM_operatortype_find("NODE_OT_translate_attach_remove_on_cancel", true);
415 BLI_assert(ot);
420}
421
422static void link_drag_search_free_fn(void *arg)
423{
424 LinkDragSearchStorage *storage = static_cast<LinkDragSearchStorage *>(arg);
425 delete storage;
426}
427
428static uiBlock *create_search_popup_block(bContext *C, ARegion *region, void *arg_op)
429{
430 LinkDragSearchStorage &storage = *(LinkDragSearchStorage *)arg_op;
431
432 uiBlock *block = UI_block_begin(C, region, "_popup", ui::EmbossType::Emboss);
435
436 uiBut *but = uiDefSearchBut(block,
437 storage.search,
438 0,
439 ICON_VIEWZOOM,
440 sizeof(storage.search),
441 storage.in_out() == SOCK_OUT ? 10 : 10 - UI_searchbox_size_x(),
442 0,
444 UI_UNIT_Y,
445 "");
449 nullptr,
451 &storage,
452 false,
455 nullptr);
457
458 /* Fake button to hold space for the search items. */
459 uiDefBut(block,
461 0,
462 "",
463 storage.in_out() == SOCK_OUT ? 10 : 10 - UI_searchbox_size_x(),
464 10 - UI_searchbox_size_y(),
467 nullptr,
468 0,
469 0,
470 std::nullopt);
471
472 const int2 offset = {0, -UI_UNIT_Y};
473 UI_block_bounds_set_popup(block, 0.3f * U.widget_unit, offset);
474 return block;
475}
476
478 bNode &node,
479 bNodeSocket &socket,
480 const float2 &cursor)
481{
482 LinkDragSearchStorage *storage = new LinkDragSearchStorage{node, socket, cursor};
483 /* Use the "_ex" variant with `can_refresh` false to avoid a double free when closing Blender. */
484 UI_popup_block_invoke_ex(&C, create_search_popup_block, storage, nullptr, false);
485}
486
487} // namespace blender::ed::space_node
Main runtime representation of an asset.
IDProperty * BKE_asset_metadata_idprop_find(const AssetMetaData *asset_data, const char *name) ATTR_WARN_UNUSED_RESULT
Definition asset.cc:179
SpaceNode * CTX_wm_space_node(const bContext *C)
Main * CTX_data_main(const bContext *C)
#define IDP_int_get(prop)
#define IDP_string_get(prop)
void id_us_plus(ID *id)
Definition lib_id.cc:358
void BKE_main_ensure_invariants(Main &bmain, std::optional< blender::Span< ID * > > modified_ids=std::nullopt)
void BKE_ntree_update_tag_node_property(bNodeTree *ntree, bNode *node)
#define BLI_assert(a)
Definition BLI_assert.h:46
#define LISTBASE_FOREACH(type, var, list)
#define SET_FLAG_FROM_TEST(value, test, flag)
#define IFACE_(msgid)
void DEG_relations_tag_update(Main *bmain)
@ ID_FLAG_EMBEDDED_DATA
Definition DNA_ID.h:774
#define FILTER_ID_NT
Definition DNA_ID.h:1213
@ IDP_STRING
@ NODE_OPTIONS
eNodeSocketInOut
@ SOCK_OUT
@ SOCK_IN
@ SOCK_HIDDEN
eNodeSocketDatatype
@ SOCK_CUSTOM
#define UI_SCALE_FAC
#define C
Definition RandGen.cpp:29
#define UI_UNIT_Y
@ UI_BLOCK_SEARCH_MENU
@ UI_BLOCK_LOOP
@ UI_BLOCK_MOVEMOUSE_QUIT
void UI_block_theme_style_set(uiBlock *block, char theme_style)
uiBlock * UI_block_begin(const bContext *C, ARegion *region, std::string name, blender::ui::EmbossType emboss)
@ UI_BUT_ACTIVATE_ON_INIT
void UI_block_bounds_set_popup(uiBlock *block, int addval, const int bounds_offset[2])
Definition interface.cc:653
void UI_but_func_search_set(uiBut *but, uiButSearchCreateFn search_create_fn, uiButSearchUpdateFn search_update_fn, void *arg, bool free_arg, uiFreeArgFunc search_arg_free_fn, uiButHandleFunc search_exec_fn, void *active)
void UI_popup_block_invoke_ex(bContext *C, uiBlockCreateFunc func, void *arg, uiFreeArgFunc arg_free, bool can_refresh)
uiBut * uiDefSearchBut(uiBlock *block, void *arg, int retval, int icon, int maxncpy, int x, int y, short width, short height, std::optional< blender::StringRef > tip)
int UI_searchbox_size_x()
bool UI_search_item_add(uiSearchItems *items, blender::StringRef name, void *poin, int iconid, int but_flag, uint8_t name_prefix_offset)
int UI_searchbox_size_y()
void UI_but_func_search_set_sep_string(uiBut *but, const char *search_sep_string)
@ UI_BLOCK_THEME_STYLE_POPUP
void UI_but_func_search_set_listen(uiBut *but, uiButSearchListenFn listen_fn)
void UI_block_flag_enable(uiBlock *block, int flag)
uiBut * uiDefBut(uiBlock *block, uiButTypeWithPointerType but_and_ptr_type, int retval, blender::StringRef str, int x, int y, short width, short height, void *poin, float min, float max, std::optional< blender::StringRef > tip)
void UI_but_flag_enable(uiBut *but, int flag)
#define ND_ASSET_LIST_READING
Definition WM_types.hh:550
#define NC_ASSET
Definition WM_types.hh:404
#define U
void clear()
bool add(const Key &key)
Definition BLI_set.hh:248
constexpr bool contains(const T &value) const
Definition BLI_span.hh:277
constexpr bool endswith(StringRef suffix) const
void append(const T &value)
bool is_empty() const
void add(const StringRef str, T *user_data, const int weight=0)
Vector< T * > query(const StringRef query) const
#define str(s)
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
AssetLibraryReference all_library_reference()
bNodeTreeInterfaceSocket * add_interface_socket_from_node(bNodeTree &ntree, const bNode &from_node, const bNodeSocket &from_sock, StringRef socket_type, StringRef name)
bNodeSocket * node_find_socket(bNode &node, eNodeSocketInOut in_out, StringRef identifier)
Definition node.cc:2532
Span< bNodeType * > node_types_get()
Definition node.cc:2447
bool node_set_selected(bNode &node, bool select)
Definition node.cc:4695
bNodeSocketType * node_socket_type_find(StringRef idname)
Definition node.cc:2462
bNodeLink & node_add_link(bNodeTree &ntree, bNode &fromnode, bNodeSocket &fromsock, bNode &tonode, bNodeSocket &tosock)
Definition node.cc:3810
bNodeSocket * node_find_enabled_socket(bNode &node, eNodeSocketInOut in_out, StringRef name)
Definition node.cc:2553
void node_set_active(bNodeTree &ntree, bNode &node)
Definition node.cc:4724
void node_socket_move_default_value(Main &bmain, bNodeTree &tree, bNodeSocket &src, bNodeSocket &dst)
Definition node.cc:3724
void storage_fetch(const AssetLibraryReference *library_reference, const bContext *C)
void iterate(const AssetLibraryReference &library_reference, AssetListIterFn fn)
bool filter_matches_asset(const AssetFilterSettings *filter, const blender::asset_system::AssetRepresentation &asset)
ID * asset_local_id_ensure_imported(Main &bmain, const asset_system::AssetRepresentation &asset, const std::optional< eAssetImportMethod > import_method=std::nullopt)
void invoke_node_link_drag_add_menu(bContext &C, bNode &node, bNodeSocket &socket, const float2 &cursor)
bool node_deselect_all(bNodeTree &node_tree)
static bNode * get_new_linked_node(bNodeSocket &socket, const Span< bNode * > new_nodes)
static void add_reroute_node_fn(nodes::LinkSearchOpParams &params)
static void link_drag_search_update_fn(const bContext *C, void *arg, const char *str, uiSearchItems *items, const bool is_first)
static void link_drag_search_free_fn(void *arg)
static void gather_search_link_ops_for_all_assets(const bContext &C, const bNodeTree &node_tree, const bNodeSocket &socket, Vector< SocketLinkOperation > &search_link_ops)
static void add_existing_group_input_fn(nodes::LinkSearchOpParams &params, const bNodeTreeInterfaceSocket &interface_socket)
static void add_group_input_node_fn(nodes::LinkSearchOpParams &params)
static void link_drag_search_listen_fn(const wmRegionListenerParams *params, void *arg)
static void link_drag_search_exec_fn(bContext *C, void *arg1, void *arg2)
static uiBlock * create_search_popup_block(bContext *C, ARegion *region, void *arg_op)
static void gather_socket_link_operations(const bContext &C, bNodeTree &node_tree, const bNodeSocket &socket, Vector< SocketLinkOperation > &search_link_ops)
static void search_link_ops_for_asset_metadata(const bNodeTree &node_tree, const bNodeSocket &socket, const asset_system::AssetRepresentation &asset, Vector< SocketLinkOperation > &search_link_ops)
void update_node_declaration_and_sockets(bNodeTree &ntree, bNode &node)
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
#define UI_MENU_ARROW_SEP
The meta-data of an asset. By creating and giving this for a data-block (ID.asset_data),...
ListBase group
Definition DNA_ID.h:143
IDPropertyData data
Definition DNA_ID.h:169
short flag
Definition DNA_ID.h:438
void * first
struct bNodeTree * edittree
bNodeSocketTypeHandle * typeinfo
int default_group_node_width
bNodeTreeTypeHandle * typeinfo
bNodeTreeInterface tree_interface
float width
ListBase inputs
struct ID * id
char name[64]
ListBase outputs
Defines a socket type.
Definition BKE_node.hh:158
eNodeSocketDatatype type
Definition BKE_node.hh:193
bool(* validate_link)(eNodeSocketDatatype from, eNodeSocketDatatype to)
Definition BKE_node.hh:524
Defines a node type.
Definition BKE_node.hh:238
unsigned int data
Definition WM_types.hh:358
unsigned int category
Definition WM_types.hh:358
wmOperatorStatus WM_operator_name_call_ptr(bContext *C, wmOperatorType *ot, blender::wm::OpCallContext context, PointerRNA *properties, const wmEvent *event)
PointerRNA * ptr
Definition wm_files.cc:4238
wmOperatorType * ot
Definition wm_files.cc:4237
wmOperatorType * WM_operatortype_find(const char *idname, bool quiet)
void WM_operator_properties_create_ptr(PointerRNA *ptr, wmOperatorType *ot)
void WM_operator_properties_free(PointerRNA *ptr)
uint8_t flag
Definition wm_window.cc:145