Changeset View
Changeset View
Standalone View
Standalone View
source/blender/editors/space_node/node_geometry_attribute_search.cc
| Show All 24 Lines | |||||
| #include "DNA_node_types.h" | #include "DNA_node_types.h" | ||||
| #include "DNA_object_types.h" | #include "DNA_object_types.h" | ||||
| #include "DNA_space_types.h" | #include "DNA_space_types.h" | ||||
| #include "BKE_context.h" | #include "BKE_context.h" | ||||
| #include "BKE_node_ui_storage.hh" | #include "BKE_node_ui_storage.hh" | ||||
| #include "BKE_object.h" | #include "BKE_object.h" | ||||
| #include "RNA_access.h" | |||||
| #include "RNA_enum_types.h" | |||||
| #include "BLT_translation.h" | |||||
| #include "UI_interface.h" | #include "UI_interface.h" | ||||
| #include "UI_resources.h" | #include "UI_resources.h" | ||||
| #include "node_intern.h" | #include "node_intern.h" | ||||
| using blender::IndexRange; | using blender::IndexRange; | ||||
| using blender::Map; | using blender::Map; | ||||
| using blender::MultiValueMap; | |||||
| using blender::Set; | using blender::Set; | ||||
| using blender::StringRef; | using blender::StringRef; | ||||
| struct AttributeSearchData { | struct AttributeSearchData { | ||||
| const bNodeTree &node_tree; | AvailableAttributeInfo &dummy_info_for_search; | ||||
| const bNode &node; | const NodeUIStorage &ui_storage; | ||||
| bNodeSocket &socket; | |||||
| }; | |||||
| uiBut *search_button; | /* This class must not have a destructor, since it is used by buttons and freed with #MEM_freeN. */ | ||||
| BLI_STATIC_ASSERT(std::is_trivially_destructible_v<AttributeSearchData>, ""); | |||||
| /* Used to keep track of a button pointer over multiple redraws. Since the UI code | static StringRef attribute_data_type_string(const CustomDataType type) | ||||
| * may reallocate the button, without this we might end up with a dangling pointer. */ | { | ||||
| uiButStore *button_store; | const char *name = nullptr; | ||||
| uiBlock *button_store_block; | RNA_enum_name_from_value(rna_enum_attribute_type_items, type, &name); | ||||
| }; | return StringRef(IFACE_(name)); | ||||
| } | |||||
| static void attribute_search_update_fn( | static StringRef attribute_domain_string(const AttributeDomain domain) | ||||
| const bContext *C, void *arg, const char *str, uiSearchItems *items, const bool is_first) | |||||
| { | { | ||||
| AttributeSearchData *data = static_cast<AttributeSearchData *>(arg); | const char *name = nullptr; | ||||
| const NodeUIStorage *ui_storage = BKE_node_tree_ui_storage_get_from_context( | RNA_enum_name_from_value(rna_enum_attribute_domain_items, domain, &name); | ||||
| C, data->node_tree, data->node); | return StringRef(IFACE_(name)); | ||||
| if (ui_storage == nullptr) { | } | ||||
| return; | |||||
| /* Unicode arrow. */ | |||||
| #define MENU_SEP "\xe2\x96\xb6" | |||||
| static bool attribute_search_item_add(uiSearchItems *items, const AvailableAttributeInfo &item) | |||||
| { | |||||
| const StringRef data_type_name = attribute_data_type_string(item.data_type); | |||||
| const StringRef domain_name = attribute_domain_string(item.domain); | |||||
| std::string search_item_text = domain_name + " " + MENU_SEP + item.name + UI_SEP_CHAR + | |||||
| data_type_name; | |||||
| return UI_search_item_add( | |||||
| items, search_item_text.c_str(), (void *)&item, ICON_NONE, UI_BUT_HAS_SEP_CHAR, 0); | |||||
| } | } | ||||
| const MultiValueMap<std::string, AvailableAttributeInfo> &attribute_hints = | static void attribute_search_update_fn(const bContext *UNUSED(C), | ||||
| ui_storage->attribute_hints; | void *arg, | ||||
| const char *str, | |||||
| uiSearchItems *items, | |||||
| const bool is_first) | |||||
| { | |||||
| AttributeSearchData *data = static_cast<AttributeSearchData *>(arg); | |||||
| if (str[0] != '\0' && attribute_hints.lookup_as(StringRef(str)).is_empty()) { | const Set<AvailableAttributeInfo> &attribute_hints = data->ui_storage.attribute_hints; | ||||
| /* Any string may be valid, so add the current search string with the hints. */ | |||||
| UI_search_item_add(items, str, (void *)str, ICON_ADD, 0, 0); | /* Any string may be valid, so add the current search string along with the hints. */ | ||||
| if (str[0] != '\0') { | |||||
| /* Note that the attribute domain and data type are dummies, since | |||||
| * #AvailableAttributeInfo equality is only based on the string. */ | |||||
| if (!attribute_hints.contains(AvailableAttributeInfo{str, ATTR_DOMAIN_AUTO, CD_PROP_BOOL})) { | |||||
| data->dummy_info_for_search.name = std::string(str); | |||||
| UI_search_item_add(items, str, &data->dummy_info_for_search, ICON_ADD, 0, 0); | |||||
| } | |||||
| } | } | ||||
| if (str[0] == '\0' && !is_first) { | if (str[0] == '\0' && !is_first) { | ||||
| /* Allow clearing the text field when the string is empty, but not on the first pass, | /* Allow clearing the text field when the string is empty, but not on the first pass, | ||||
| * or opening an attribute field for the first time would show this search item. */ | * or opening an attribute field for the first time would show this search item. */ | ||||
| UI_search_item_add(items, str, (void *)str, ICON_X, 0, 0); | data->dummy_info_for_search.name = std::string(str); | ||||
| UI_search_item_add(items, str, &data->dummy_info_for_search, ICON_X, 0, 0); | |||||
| } | } | ||||
| /* Don't filter when the menu is first opened, but still run the search | /* 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. */ | * so the items are in the same order they will appear in while searching. */ | ||||
| const char *string = is_first ? "" : str; | const char *string = is_first ? "" : str; | ||||
| StringSearch *search = BLI_string_search_new(); | StringSearch *search = BLI_string_search_new(); | ||||
| for (const std::string &attribute_name : attribute_hints.keys()) { | for (const AvailableAttributeInfo &item : attribute_hints) { | ||||
| BLI_string_search_add(search, attribute_name.c_str(), (void *)&attribute_name); | BLI_string_search_add(search, item.name.c_str(), (void *)&item); | ||||
| } | } | ||||
| std::string **filtered_items; | AvailableAttributeInfo **filtered_items; | ||||
| const int filtered_amount = BLI_string_search_query(search, string, (void ***)&filtered_items); | const int filtered_amount = BLI_string_search_query(search, string, (void ***)&filtered_items); | ||||
| for (const int i : IndexRange(filtered_amount)) { | for (const int i : IndexRange(filtered_amount)) { | ||||
| std::string *item = filtered_items[i]; | const AvailableAttributeInfo *item = filtered_items[i]; | ||||
| if (!UI_search_item_add(items, item->c_str(), item, ICON_NONE, 0, 0)) { | if (!attribute_search_item_add(items, *item)) { | ||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| MEM_freeN(filtered_items); | MEM_freeN(filtered_items); | ||||
| BLI_string_search_free(search); | BLI_string_search_free(search); | ||||
| } | } | ||||
| static void attribute_search_free_fn(void *arg) | static void attribute_search_exec_fn(bContext *UNUSED(C), void *data_v, void *item_v) | ||||
| { | { | ||||
| AttributeSearchData *data = static_cast<AttributeSearchData *>(arg); | AttributeSearchData *data = static_cast<AttributeSearchData *>(data_v); | ||||
| AvailableAttributeInfo *item = static_cast<AvailableAttributeInfo *>(item_v); | |||||
| UI_butstore_free(data->button_store_block, data->button_store); | bNodeSocket &socket = data->socket; | ||||
| delete data; | bNodeSocketValueString *value = static_cast<bNodeSocketValueString *>(socket.default_value); | ||||
| BLI_strncpy(value->value, item->name.c_str(), MAX_NAME); | |||||
| } | } | ||||
| void node_geometry_add_attribute_search_button(const bNodeTree *node_tree, | void node_geometry_add_attribute_search_button(const bContext *C, | ||||
| const bNodeTree *node_tree, | |||||
| const bNode *node, | const bNode *node, | ||||
| PointerRNA *socket_ptr, | PointerRNA *socket_ptr, | ||||
| uiLayout *layout) | uiLayout *layout) | ||||
| { | { | ||||
| const NodeUIStorage *ui_storage = BKE_node_tree_ui_storage_get_from_context( | |||||
| C, *node_tree, *node); | |||||
| if (ui_storage == nullptr) { | |||||
| uiItemR(layout, socket_ptr, "default_value", 0, "", 0); | |||||
| return; | |||||
| } | |||||
| const NodeTreeUIStorage *tree_ui_storage = node_tree->ui_storage; | |||||
| uiBlock *block = uiLayoutGetBlock(layout); | uiBlock *block = uiLayoutGetBlock(layout); | ||||
| uiBut *but = uiDefIconTextButR(block, | uiBut *but = uiDefIconTextButR(block, | ||||
| UI_BTYPE_SEARCH_MENU, | UI_BTYPE_SEARCH_MENU, | ||||
| 0, | 0, | ||||
| ICON_NONE, | ICON_NONE, | ||||
| "", | "", | ||||
| 0, | 0, | ||||
| 0, | 0, | ||||
| 10 * UI_UNIT_X, /* Dummy value, replaced by layout system. */ | 10 * UI_UNIT_X, /* Dummy value, replaced by layout system. */ | ||||
| UI_UNIT_Y, | UI_UNIT_Y, | ||||
| socket_ptr, | socket_ptr, | ||||
| "default_value", | "default_value", | ||||
| 0, | 0, | ||||
| 0.0f, | 0.0f, | ||||
| 0.0f, | 0.0f, | ||||
| 0.0f, | 0.0f, | ||||
| 0.0f, | 0.0f, | ||||
| ""); | ""); | ||||
| AttributeSearchData *data = new AttributeSearchData{ | AttributeSearchData *data = OBJECT_GUARDED_NEW(AttributeSearchData, | ||||
| *node_tree, | {tree_ui_storage->dummy_info_for_search, | ||||
| *node, | *ui_storage, | ||||
| but, | *static_cast<bNodeSocket *>(socket_ptr->data)}); | ||||
| UI_butstore_create(block), | |||||
| block, | |||||
| }; | |||||
| UI_butstore_register(data->button_store, &data->search_button); | |||||
| UI_but_func_search_set_results_are_suggestions(but, true); | UI_but_func_search_set_results_are_suggestions(but, true); | ||||
| UI_but_func_search_set_sep_string(but, MENU_SEP); | |||||
| UI_but_func_search_set(but, | UI_but_func_search_set(but, | ||||
| nullptr, | nullptr, | ||||
| attribute_search_update_fn, | attribute_search_update_fn, | ||||
| static_cast<void *>(data), | static_cast<void *>(data), | ||||
| attribute_search_free_fn, | true, | ||||
| nullptr, | nullptr, | ||||
| attribute_search_exec_fn, | |||||
| nullptr); | nullptr); | ||||
| } | } | ||||