Always position the nodes added with the node search at the point where the search operator was invoked by ensuring the operator context is the main node editor region. This was an unintended change of rBbdb57541475f, caused by the operator now getting the cursor position in region space. So when the operator was called from the menu, it would get the cursor position in the region space of the menu, which lead to an offset when adding the node since it expected the coordinates to be in the space of the node editor. Setting the correct operator context also fixes inconsistent transform sensitivity depending on zoom when adding nodes via the search in the menu which has been an issue since as far back as Blender 2.79. Also includes a small fix for the vertical offset of nodes added by the search which varied depending on the UI scale. Same fix as in rB998ffcbf096e. Reviewed By: Hans Goudey Differential Revision: http://developer.blender.org/D16555
360 lines
12 KiB
C++
360 lines
12 KiB
C++
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#include <optional>
|
|
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_string_search.h"
|
|
|
|
#include "DNA_space_types.h"
|
|
|
|
#include "BKE_asset.h"
|
|
#include "BKE_asset_catalog.hh"
|
|
#include "BKE_asset_library.hh"
|
|
#include "BKE_context.h"
|
|
#include "BKE_idprop.h"
|
|
#include "BKE_lib_id.h"
|
|
#include "BKE_main.h"
|
|
#include "BKE_node_tree_update.h"
|
|
#include "BKE_screen.h"
|
|
|
|
#include "DEG_depsgraph_build.h"
|
|
|
|
#include "BLT_translation.h"
|
|
|
|
#include "RNA_access.h"
|
|
|
|
#include "WM_api.h"
|
|
|
|
#include "ED_asset.h"
|
|
#include "ED_node.h"
|
|
|
|
#include "node_intern.hh"
|
|
|
|
struct bContext;
|
|
|
|
namespace blender::ed::space_node {
|
|
|
|
struct AddNodeItem {
|
|
std::string ui_name;
|
|
std::string identifier;
|
|
std::string description;
|
|
std::optional<AssetHandle> asset;
|
|
std::function<void(const bContext &, bNodeTree &, bNode &)> after_add_fn;
|
|
int weight = 0;
|
|
};
|
|
|
|
struct AddNodeSearchStorage {
|
|
float2 cursor;
|
|
bool use_transform;
|
|
Vector<AddNodeItem> search_add_items;
|
|
char search[256];
|
|
bool update_items_tag = true;
|
|
};
|
|
|
|
static void add_node_search_listen_fn(const wmRegionListenerParams *params, void *arg)
|
|
{
|
|
AddNodeSearchStorage &storage = *static_cast<AddNodeSearchStorage *>(arg);
|
|
const wmNotifier *wmn = params->notifier;
|
|
|
|
switch (wmn->category) {
|
|
case NC_ASSET:
|
|
if (wmn->data == ND_ASSET_LIST_READING) {
|
|
storage.update_items_tag = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void search_items_for_asset_metadata(const bNodeTree &node_tree,
|
|
const AssetLibraryReference &library_ref,
|
|
const AssetHandle asset,
|
|
Vector<AddNodeItem> &search_items)
|
|
{
|
|
const AssetMetaData &asset_data = *ED_asset_handle_get_metadata(&asset);
|
|
const IDProperty *tree_type = BKE_asset_metadata_idprop_find(&asset_data, "type");
|
|
if (tree_type == nullptr || IDP_Int(tree_type) != node_tree.type) {
|
|
return;
|
|
}
|
|
|
|
AddNodeItem item{};
|
|
item.ui_name = ED_asset_handle_get_name(&asset);
|
|
item.identifier = node_tree.typeinfo->group_idname;
|
|
item.description = asset_data.description == nullptr ? "" : asset_data.description;
|
|
item.asset = asset;
|
|
item.after_add_fn = [asset, library_ref](const bContext &C, bNodeTree &node_tree, bNode &node) {
|
|
Main &bmain = *CTX_data_main(&C);
|
|
node.flag &= ~NODE_OPTIONS;
|
|
node.id = asset::get_local_id_from_asset_or_append_and_reuse(bmain, library_ref, asset);
|
|
id_us_plus(node.id);
|
|
BKE_ntree_update_tag_node_property(&node_tree, &node);
|
|
DEG_relations_tag_update(&bmain);
|
|
};
|
|
|
|
search_items.append(std::move(item));
|
|
}
|
|
|
|
static void gather_search_items_for_asset_library(const bContext &C,
|
|
const bNodeTree &node_tree,
|
|
const AssetLibraryReference &library_ref,
|
|
Set<std::string> &r_added_assets,
|
|
Vector<AddNodeItem> &search_items)
|
|
{
|
|
AssetFilterSettings filter_settings{};
|
|
filter_settings.id_types = FILTER_ID_NT;
|
|
|
|
ED_assetlist_storage_fetch(&library_ref, &C);
|
|
ED_assetlist_ensure_previews_job(&library_ref, &C);
|
|
ED_assetlist_iterate(library_ref, [&](AssetHandle asset) {
|
|
if (!ED_asset_filter_matches_asset(&filter_settings, &asset)) {
|
|
return true;
|
|
}
|
|
if (!r_added_assets.add(ED_asset_handle_get_name(&asset))) {
|
|
/* If an asset with the same name has already been added, skip this. */
|
|
return true;
|
|
}
|
|
search_items_for_asset_metadata(node_tree, library_ref, asset, search_items);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
static void gather_search_items_for_all_assets(const bContext &C,
|
|
const bNodeTree &node_tree,
|
|
Set<std::string> &r_added_assets,
|
|
Vector<AddNodeItem> &search_items)
|
|
{
|
|
int i;
|
|
LISTBASE_FOREACH_INDEX (const bUserAssetLibrary *, asset_library, &U.asset_libraries, i) {
|
|
AssetLibraryReference library_ref{};
|
|
library_ref.custom_library_index = i;
|
|
library_ref.type = ASSET_LIBRARY_CUSTOM;
|
|
/* Skip local assets to avoid duplicates when the asset is part of the local file library. */
|
|
gather_search_items_for_asset_library(C, node_tree, library_ref, r_added_assets, search_items);
|
|
}
|
|
|
|
AssetLibraryReference library_ref{};
|
|
library_ref.custom_library_index = -1;
|
|
library_ref.type = ASSET_LIBRARY_LOCAL;
|
|
gather_search_items_for_asset_library(C, node_tree, library_ref, r_added_assets, search_items);
|
|
}
|
|
|
|
static void gather_search_items_for_node_groups(const bContext &C,
|
|
const bNodeTree &node_tree,
|
|
const Set<std::string> &local_assets,
|
|
Vector<AddNodeItem> &search_items)
|
|
{
|
|
const StringRef group_node_id = node_tree.typeinfo->group_idname;
|
|
|
|
Main &bmain = *CTX_data_main(&C);
|
|
LISTBASE_FOREACH (bNodeTree *, node_group, &bmain.nodetrees) {
|
|
if (node_group->typeinfo->group_idname != group_node_id) {
|
|
continue;
|
|
}
|
|
if (local_assets.contains(node_group->id.name)) {
|
|
continue;
|
|
}
|
|
if (!nodeGroupPoll(&node_tree, node_group, nullptr)) {
|
|
continue;
|
|
}
|
|
AddNodeItem item{};
|
|
item.ui_name = node_group->id.name + 2;
|
|
item.identifier = node_tree.typeinfo->group_idname;
|
|
item.after_add_fn = [node_group](const bContext &C, bNodeTree &node_tree, bNode &node) {
|
|
Main &bmain = *CTX_data_main(&C);
|
|
node.id = &node_group->id;
|
|
id_us_plus(node.id);
|
|
BKE_ntree_update_tag_node_property(&node_tree, &node);
|
|
DEG_relations_tag_update(&bmain);
|
|
};
|
|
search_items.append(std::move(item));
|
|
}
|
|
}
|
|
|
|
static void gather_add_node_operations(const bContext &C,
|
|
bNodeTree &node_tree,
|
|
Vector<AddNodeItem> &r_search_items)
|
|
{
|
|
NODE_TYPES_BEGIN (node_type) {
|
|
const char *disabled_hint;
|
|
if (!(node_type->poll && node_type->poll(node_type, &node_tree, &disabled_hint))) {
|
|
continue;
|
|
}
|
|
if ((StringRefNull(node_tree.typeinfo->group_idname) == node_type->idname)) {
|
|
/* Skip the empty group type. */
|
|
continue;
|
|
}
|
|
if (StringRefNull(node_type->ui_name).endswith("(Legacy)")) {
|
|
continue;
|
|
}
|
|
|
|
AddNodeItem item{};
|
|
item.ui_name = IFACE_(node_type->ui_name);
|
|
item.identifier = node_type->idname;
|
|
item.description = TIP_(node_type->ui_description);
|
|
r_search_items.append(std::move(item));
|
|
}
|
|
NODE_TYPES_END;
|
|
|
|
/* Use a set to avoid adding items for node groups that are also assets. Using data-block
|
|
* names is a crutch, since different assets may have the same name. However, an alternative
|
|
* using #ED_asset_handle_get_local_id didn't work in this case. */
|
|
Set<std::string> added_assets;
|
|
gather_search_items_for_all_assets(C, node_tree, added_assets, r_search_items);
|
|
gather_search_items_for_node_groups(C, node_tree, added_assets, r_search_items);
|
|
}
|
|
|
|
static void add_node_search_update_fn(
|
|
const bContext *C, void *arg, const char *str, uiSearchItems *items, const bool is_first)
|
|
{
|
|
AddNodeSearchStorage &storage = *static_cast<AddNodeSearchStorage *>(arg);
|
|
if (storage.update_items_tag) {
|
|
bNodeTree *node_tree = CTX_wm_space_node(C)->edittree;
|
|
storage.search_add_items.clear();
|
|
gather_add_node_operations(*C, *node_tree, storage.search_add_items);
|
|
storage.update_items_tag = false;
|
|
}
|
|
|
|
StringSearch *search = BLI_string_search_new();
|
|
|
|
for (AddNodeItem &item : storage.search_add_items) {
|
|
BLI_string_search_add(search, item.ui_name.c_str(), &item, item.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;
|
|
AddNodeItem **filtered_items;
|
|
const int filtered_amount = BLI_string_search_query(search, string, (void ***)&filtered_items);
|
|
|
|
for (const int i : IndexRange(filtered_amount)) {
|
|
AddNodeItem &item = *filtered_items[i];
|
|
if (!UI_search_item_add(items, item.ui_name.c_str(), &item, ICON_NONE, 0, 0)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
MEM_freeN(filtered_items);
|
|
BLI_string_search_free(search);
|
|
}
|
|
|
|
static void add_node_search_exec_fn(bContext *C, void *arg1, void *arg2)
|
|
{
|
|
Main &bmain = *CTX_data_main(C);
|
|
SpaceNode &snode = *CTX_wm_space_node(C);
|
|
bNodeTree &node_tree = *snode.edittree;
|
|
AddNodeSearchStorage &storage = *static_cast<AddNodeSearchStorage *>(arg1);
|
|
AddNodeItem *item = static_cast<AddNodeItem *>(arg2);
|
|
if (item == nullptr) {
|
|
return;
|
|
}
|
|
|
|
node_deselect_all(snode);
|
|
bNode *new_node = nodeAddNode(C, &node_tree, item->identifier.c_str());
|
|
BLI_assert(new_node != nullptr);
|
|
|
|
if (item->after_add_fn) {
|
|
item->after_add_fn(*C, node_tree, *new_node);
|
|
}
|
|
|
|
new_node->locx = storage.cursor.x / UI_DPI_FAC;
|
|
new_node->locy = storage.cursor.y / UI_DPI_FAC + 20;
|
|
|
|
nodeSetSelected(new_node, true);
|
|
nodeSetActive(&node_tree, 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. */
|
|
ED_node_tree_propagate_change(C, &bmain, &node_tree);
|
|
|
|
if (storage.use_transform) {
|
|
wmOperatorType *ot = WM_operatortype_find("NODE_OT_translate_attach_remove_on_cancel", true);
|
|
BLI_assert(ot);
|
|
PointerRNA ptr;
|
|
WM_operator_properties_create_ptr(&ptr, ot);
|
|
WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &ptr, nullptr);
|
|
WM_operator_properties_free(&ptr);
|
|
}
|
|
}
|
|
|
|
static ARegion *add_node_search_tooltip_fn(
|
|
bContext *C, ARegion *region, const rcti *item_rect, void * /*arg*/, void *active)
|
|
{
|
|
const AddNodeItem *item = static_cast<const AddNodeItem *>(active);
|
|
|
|
uiSearchItemTooltipData tooltip_data{};
|
|
|
|
BLI_strncpy(tooltip_data.description,
|
|
item->asset ? item->description.c_str() : TIP_(item->description.c_str()),
|
|
sizeof(tooltip_data.description));
|
|
|
|
return UI_tooltip_create_from_search_item_generic(C, region, item_rect, &tooltip_data);
|
|
}
|
|
|
|
static void add_node_search_free_fn(void *arg)
|
|
{
|
|
AddNodeSearchStorage *storage = static_cast<AddNodeSearchStorage *>(arg);
|
|
delete storage;
|
|
}
|
|
|
|
static uiBlock *create_search_popup_block(bContext *C, ARegion *region, void *arg_op)
|
|
{
|
|
AddNodeSearchStorage &storage = *(AddNodeSearchStorage *)arg_op;
|
|
|
|
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),
|
|
10,
|
|
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,
|
|
add_node_search_update_fn,
|
|
&storage,
|
|
false,
|
|
add_node_search_free_fn,
|
|
add_node_search_exec_fn,
|
|
nullptr);
|
|
UI_but_flag_enable(but, UI_BUT_ACTIVATE_ON_INIT);
|
|
UI_but_func_search_set_tooltip(but, add_node_search_tooltip_fn);
|
|
UI_but_func_search_set_listen(but, add_node_search_listen_fn);
|
|
|
|
/* Fake button to hold space for the search items. */
|
|
uiDefBut(block,
|
|
UI_BTYPE_LABEL,
|
|
0,
|
|
"",
|
|
10,
|
|
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_add_node_search_menu(bContext &C, const float2 &cursor, const bool use_transform)
|
|
{
|
|
AddNodeSearchStorage *storage = new AddNodeSearchStorage{cursor, use_transform};
|
|
/* 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
|