diff --git a/source/blender/asset_system/AS_asset_representation.h b/source/blender/asset_system/AS_asset_representation.h index 359567cbf0b..366c1a1f16e 100644 --- a/source/blender/asset_system/AS_asset_representation.h +++ b/source/blender/asset_system/AS_asset_representation.h @@ -21,6 +21,8 @@ const char *AS_asset_representation_name_get(const AssetRepresentation *asset) ATTR_WARN_UNUSED_RESULT; AssetMetaData *AS_asset_representation_metadata_get(const AssetRepresentation *asset) ATTR_WARN_UNUSED_RESULT; +struct ID *AS_asset_representation_local_id_get(const AssetRepresentation *asset) + ATTR_WARN_UNUSED_RESULT; bool AS_asset_representation_is_local_id(const AssetRepresentation *asset) ATTR_WARN_UNUSED_RESULT; bool AS_asset_representation_is_never_link(const AssetRepresentation *asset) ATTR_WARN_UNUSED_RESULT; diff --git a/source/blender/asset_system/AS_asset_representation.hh b/source/blender/asset_system/AS_asset_representation.hh index 22ae9893a47..6e84fa2aa20 100644 --- a/source/blender/asset_system/AS_asset_representation.hh +++ b/source/blender/asset_system/AS_asset_representation.hh @@ -82,6 +82,9 @@ class AssetRepresentation { * #get_import_method(). Also returns true if there is no predefined import method * (when #get_import_method() returns no value). */ bool may_override_import_method() const; + /** If this asset is stored inside this current file (#is_local_id() is true), this returns the + * ID's pointer, otherwise null. */ + ID *local_id() const; /** Returns if this asset is stored inside this current file, and as such fully editable. */ bool is_local_id() const; const AssetLibrary &owner_asset_library() const; diff --git a/source/blender/asset_system/intern/asset_representation.cc b/source/blender/asset_system/intern/asset_representation.cc index 8dded789d1a..6ebd6dd4c6c 100644 --- a/source/blender/asset_system/intern/asset_representation.cc +++ b/source/blender/asset_system/intern/asset_representation.cc @@ -97,6 +97,11 @@ bool AssetRepresentation::may_override_import_method() const return owner_asset_library_->may_override_import_method_; } +ID *AssetRepresentation::local_id() const +{ + return is_local_id_ ? local_asset_id_ : nullptr; +} + bool AssetRepresentation::is_local_id() const { return is_local_id_; @@ -159,6 +164,13 @@ AssetMetaData *AS_asset_representation_metadata_get(const AssetRepresentation *a return &asset->get_metadata(); } +ID *AS_asset_representation_local_id_get(const AssetRepresentation *asset_handle) +{ + const asset_system::AssetRepresentation *asset = + reinterpret_cast(asset_handle); + return asset->local_id(); +} + bool AS_asset_representation_is_local_id(const AssetRepresentation *asset_handle) { const asset_system::AssetRepresentation *asset = diff --git a/source/blender/blenkernel/BKE_context.h b/source/blender/blenkernel/BKE_context.h index f836b98f3b2..0b58adf7ca7 100644 --- a/source/blender/blenkernel/BKE_context.h +++ b/source/blender/blenkernel/BKE_context.h @@ -381,6 +381,8 @@ bool CTX_data_editable_gpencil_strokes(const bContext *C, ListBase *list); const struct AssetLibraryReference *CTX_wm_asset_library_ref(const bContext *C); struct AssetHandle CTX_wm_asset_handle(const bContext *C, bool *r_is_valid); +struct AssetRepresentation *CTX_wm_asset(const bContext *C); + bool CTX_wm_interface_locked(const bContext *C); /** diff --git a/source/blender/blenkernel/intern/context.cc b/source/blender/blenkernel/intern/context.cc index 114ed1a49c8..ef0896c044d 100644 --- a/source/blender/blenkernel/intern/context.cc +++ b/source/blender/blenkernel/intern/context.cc @@ -1493,6 +1493,11 @@ AssetHandle CTX_wm_asset_handle(const bContext *C, bool *r_is_valid) return AssetHandle{nullptr}; } +AssetRepresentation *CTX_wm_asset(const bContext *C) +{ + return static_cast(ctx_data_pointer_get(C, "asset")); +} + Depsgraph *CTX_data_depsgraph_pointer(const bContext *C) { Main *bmain = CTX_data_main(C); diff --git a/source/blender/editors/asset/CMakeLists.txt b/source/blender/editors/asset/CMakeLists.txt index a4d05ebaffe..9d5f10eddd2 100644 --- a/source/blender/editors/asset/CMakeLists.txt +++ b/source/blender/editors/asset/CMakeLists.txt @@ -24,6 +24,7 @@ set(SRC intern/asset_catalog.cc intern/asset_filter.cc intern/asset_handle.cc + intern/asset_import.cc intern/asset_indexer.cc intern/asset_library_reference.cc intern/asset_library_reference_enum.cc @@ -37,6 +38,7 @@ set(SRC ED_asset_catalog.hh ED_asset_filter.h ED_asset_handle.h + ED_asset_import.h ED_asset_indexer.h ED_asset_library.h ED_asset_list.h diff --git a/source/blender/editors/asset/ED_asset_handle.h b/source/blender/editors/asset/ED_asset_handle.h index 436194fd885..8bfd4f7150e 100644 --- a/source/blender/editors/asset/ED_asset_handle.h +++ b/source/blender/editors/asset/ED_asset_handle.h @@ -21,6 +21,7 @@ extern "C" { struct AssetHandle; +struct AssetRepresentation *ED_asset_handle_get_representation(const struct AssetHandle *asset); const char *ED_asset_handle_get_name(const struct AssetHandle *asset); struct AssetMetaData *ED_asset_handle_get_metadata(const struct AssetHandle *asset); struct ID *ED_asset_handle_get_local_id(const struct AssetHandle *asset); @@ -45,11 +46,4 @@ void ED_asset_handle_get_full_library_path( std::optional ED_asset_handle_get_import_method( const struct AssetHandle *asset); -namespace blender::ed::asset { - -/** If the ID already exists in the database, return it, otherwise add it. */ -ID *get_local_id_from_asset_or_append_and_reuse(Main &bmain, AssetHandle asset); - -} // namespace blender::ed::asset - #endif diff --git a/source/blender/editors/asset/ED_asset_import.h b/source/blender/editors/asset/ED_asset_import.h new file mode 100644 index 00000000000..16c06c6416c --- /dev/null +++ b/source/blender/editors/asset/ED_asset_import.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edasset + */ + +#pragma once + +#include "DNA_ID_enums.h" + +struct AssetRepresentation; +struct Main; + +#ifdef __cplusplus +extern "C" { +#endif + +struct ID *ED_asset_get_local_id_from_asset_or_append_and_reuse( + struct Main *bmain, const struct AssetRepresentation *asset_c_ptr, ID_Type idtype); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/editors/asset/ED_asset_list.h b/source/blender/editors/asset/ED_asset_list.h index 30f961421a5..19528c5dde6 100644 --- a/source/blender/editors/asset/ED_asset_list.h +++ b/source/blender/editors/asset/ED_asset_list.h @@ -6,11 +6,12 @@ #pragma once +#include "DNA_asset_types.h" + #ifdef __cplusplus extern "C" { #endif -struct AssetHandle; struct AssetLibraryReference; struct ID; struct bContext; @@ -49,6 +50,9 @@ void ED_assetlist_storage_id_remap(struct ID *id_old, struct ID *id_new); */ void ED_assetlist_storage_exit(void); +AssetHandle ED_assetlist_asset_get_by_index(const AssetLibraryReference *library_reference, + int asset_index); + struct ImBuf *ED_assetlist_asset_image_get(const AssetHandle *asset_handle); /** diff --git a/source/blender/editors/asset/ED_asset_list.hh b/source/blender/editors/asset/ED_asset_list.hh index 524fee274f8..eeca6390bca 100644 --- a/source/blender/editors/asset/ED_asset_list.hh +++ b/source/blender/editors/asset/ED_asset_list.hh @@ -30,4 +30,8 @@ blender::asset_system::AssetLibrary *ED_assetlist_library_get_once_available( /* Can return false to stop iterating. */ using AssetListIterFn = blender::FunctionRef; +/** + * \warning Never keep the asset handle passed to \a fn outside of \a fn's scope. While iterating, + * the file data wrapped by the asset handle can be freed, since the file cache has a maximum size. + */ void ED_assetlist_iterate(const AssetLibraryReference &library_reference, AssetListIterFn fn); diff --git a/source/blender/editors/asset/intern/asset_handle.cc b/source/blender/editors/asset/intern/asset_handle.cc index 4a6a7fda40b..979b180c82d 100644 --- a/source/blender/editors/asset/intern/asset_handle.cc +++ b/source/blender/editors/asset/intern/asset_handle.cc @@ -9,13 +9,16 @@ #include "AS_asset_representation.h" #include "AS_asset_representation.hh" -#include "DNA_space_types.h" +#include "BLI_string.h" -#include "BLO_readfile.h" +#include "DNA_space_types.h" #include "ED_asset_handle.h" -#include "WM_api.h" +AssetRepresentation *ED_asset_handle_get_representation(const AssetHandle *asset) +{ + return asset->file_data->asset; +} const char *ED_asset_handle_get_name(const AssetHandle *asset) { @@ -53,36 +56,11 @@ void ED_asset_handle_get_full_library_path(const AssetHandle *asset_handle, { *r_full_lib_path = '\0'; - std::string asset_path = AS_asset_representation_full_path_get(asset_handle->file_data->asset); - if (asset_path.empty()) { + std::string library_path = AS_asset_representation_full_library_path_get( + asset_handle->file_data->asset); + if (library_path.empty()) { return; } - BLO_library_path_explode(asset_path.c_str(), r_full_lib_path, nullptr, nullptr); + BLI_strncpy(r_full_lib_path, library_path.c_str(), FILE_MAX); } - -namespace blender::ed::asset { - -ID *get_local_id_from_asset_or_append_and_reuse(Main &bmain, const AssetHandle asset) -{ - if (ID *local_id = ED_asset_handle_get_local_id(&asset)) { - return local_id; - } - - char blend_path[FILE_MAX_LIBEXTRA]; - ED_asset_handle_get_full_library_path(&asset, blend_path); - const char *id_name = ED_asset_handle_get_name(&asset); - - return WM_file_append_datablock(&bmain, - nullptr, - nullptr, - nullptr, - blend_path, - ED_asset_handle_get_id_type(&asset), - id_name, - BLO_LIBLINK_APPEND_RECURSIVE | - BLO_LIBLINK_APPEND_ASSET_DATA_CLEAR | - BLO_LIBLINK_APPEND_LOCAL_ID_REUSE); -} - -} // namespace blender::ed::asset diff --git a/source/blender/editors/asset/intern/asset_import.cc b/source/blender/editors/asset/intern/asset_import.cc new file mode 100644 index 00000000000..f78084dd38c --- /dev/null +++ b/source/blender/editors/asset/intern/asset_import.cc @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edasset + */ + +#include "AS_asset_representation.h" +#include "AS_asset_representation.hh" + +#include "BLO_readfile.h" + +#include "WM_api.h" + +#include "ED_asset_import.h" + +using namespace blender; + +ID *ED_asset_get_local_id_from_asset_or_append_and_reuse(Main *bmain, + const AssetRepresentation *asset_c_ptr, + ID_Type idtype) +{ + const asset_system::AssetRepresentation &asset = + *reinterpret_cast(asset_c_ptr); + + if (ID *local_id = asset.local_id()) { + return local_id; + } + + std::string blend_path = asset.get_identifier().full_library_path(); + if (blend_path.empty()) { + return nullptr; + } + + return WM_file_append_datablock(bmain, + nullptr, + nullptr, + nullptr, + blend_path.c_str(), + idtype, + asset.get_name().c_str(), + BLO_LIBLINK_APPEND_RECURSIVE | + BLO_LIBLINK_APPEND_ASSET_DATA_CLEAR | + BLO_LIBLINK_APPEND_LOCAL_ID_REUSE); +} diff --git a/source/blender/editors/asset/intern/asset_list.cc b/source/blender/editors/asset/intern/asset_list.cc index 78859a9206d..c7df300ed24 100644 --- a/source/blender/editors/asset/intern/asset_list.cc +++ b/source/blender/editors/asset/intern/asset_list.cc @@ -112,6 +112,8 @@ class AssetList : NonCopyable { void ensurePreviewsJob(const bContext *C); void clear(bContext *C); + AssetHandle asset_get_by_index(int index) const; + bool needsRefetch() const; bool isLoaded() const; asset_system::AssetLibrary *asset_library() const; @@ -139,7 +141,7 @@ void AssetList::setup() filelist_setlibrary(files, &library_ref_); filelist_setfilter_options( files, - false, + true, true, true, /* Just always hide parent, prefer to not add an extra user option for this. */ FILE_TYPE_BLENDERLIB, @@ -246,6 +248,11 @@ void AssetList::clear(bContext *C) WM_main_add_notifier(NC_ASSET | ND_ASSET_LIST, nullptr); } +AssetHandle AssetList::asset_get_by_index(int index) const +{ + return {filelist_file(filelist_, index)}; +} + /** * \return True if the asset-list needs a UI redraw. */ @@ -472,6 +479,13 @@ asset_system::AssetLibrary *ED_assetlist_library_get_once_available( return list->asset_library(); } +AssetHandle ED_assetlist_asset_get_by_index(const AssetLibraryReference *library_reference, + int asset_index) +{ + const AssetList *list = AssetListStorage::lookup_list(*library_reference); + return list->asset_get_by_index(asset_index); +} + ImBuf *ED_assetlist_asset_image_get(const AssetHandle *asset_handle) { ImBuf *imbuf = filelist_file_getimage(asset_handle->file_data); diff --git a/source/blender/editors/asset/intern/asset_temp_id_consumer.cc b/source/blender/editors/asset/intern/asset_temp_id_consumer.cc index 076a332c472..d8525d4150f 100644 --- a/source/blender/editors/asset/intern/asset_temp_id_consumer.cc +++ b/source/blender/editors/asset/intern/asset_temp_id_consumer.cc @@ -8,9 +8,15 @@ */ #include +#include + +#include "AS_asset_representation.h" +#include "AS_asset_representation.hh" #include "DNA_space_types.h" +#include "ED_asset.h" + #include "BKE_report.h" #include "BLI_utility_mixins.hh" @@ -19,17 +25,16 @@ #include "MEM_guardedalloc.h" -#include "ED_asset_handle.h" #include "ED_asset_temp_id_consumer.h" using namespace blender; class AssetTemporaryIDConsumer : NonCopyable, NonMovable { - const AssetHandle &handle_; + const AssetRepresentation *asset_; TempLibraryContext *temp_lib_context_ = nullptr; public: - AssetTemporaryIDConsumer(const AssetHandle &handle) : handle_(handle) + AssetTemporaryIDConsumer(const AssetRepresentation *asset) : asset_(asset) { } ~AssetTemporaryIDConsumer() @@ -41,20 +46,20 @@ class AssetTemporaryIDConsumer : NonCopyable, NonMovable { ID *get_local_id() { - return ED_asset_handle_get_local_id(&handle_); + return AS_asset_representation_local_id_get(asset_); } ID *import_id(ID_Type id_type, Main &bmain, ReportList &reports) { - const char *asset_name = ED_asset_handle_get_name(&handle_); - char blend_file_path[FILE_MAX_LIBEXTRA]; - ED_asset_handle_get_full_library_path(&handle_, blend_file_path); + const char *asset_name = AS_asset_representation_name_get(asset_); + std::string blend_file_path = AS_asset_representation_full_library_path_get(asset_); temp_lib_context_ = BLO_library_temp_load_id( - &bmain, blend_file_path, id_type, asset_name, &reports); + &bmain, blend_file_path.c_str(), id_type, asset_name, &reports); if (temp_lib_context_ == nullptr || temp_lib_context_->temp_id == nullptr) { - BKE_reportf(&reports, RPT_ERROR, "Unable to load %s from %s", asset_name, blend_file_path); + BKE_reportf( + &reports, RPT_ERROR, "Unable to load %s from %s", asset_name, blend_file_path.c_str()); return nullptr; } @@ -70,7 +75,7 @@ AssetTempIDConsumer *ED_asset_temp_id_consumer_create(const AssetHandle *handle) } BLI_assert(handle->file_data->asset != nullptr); return reinterpret_cast( - MEM_new(__func__, *handle)); + MEM_new(__func__, ED_asset_handle_get_representation(handle))); } void ED_asset_temp_id_consumer_free(AssetTempIDConsumer **consumer) diff --git a/source/blender/editors/include/ED_asset.h b/source/blender/editors/include/ED_asset.h index d7e7880e114..c5c835c6171 100644 --- a/source/blender/editors/include/ED_asset.h +++ b/source/blender/editors/include/ED_asset.h @@ -25,6 +25,7 @@ void ED_operatortypes_asset(void); #include "../asset/ED_asset_catalog.h" #include "../asset/ED_asset_filter.h" #include "../asset/ED_asset_handle.h" +#include "../asset/ED_asset_import.h" #include "../asset/ED_asset_library.h" #include "../asset/ED_asset_list.h" #include "../asset/ED_asset_mark_clear.h" diff --git a/source/blender/editors/include/UI_interface.hh b/source/blender/editors/include/UI_interface.hh index fc03b0218c0..147b48b454e 100644 --- a/source/blender/editors/include/UI_interface.hh +++ b/source/blender/editors/include/UI_interface.hh @@ -8,6 +8,7 @@ #include +#include "BLI_function_ref.hh" #include "BLI_string_ref.hh" #include "BLI_vector.hh" @@ -17,8 +18,10 @@ namespace blender::nodes::geo_eval_log { struct GeometryAttributeInfo; } +struct PointerRNA; struct StructRNA; struct uiBlock; +struct uiList; struct uiSearchItems; namespace blender::ui { @@ -53,6 +56,60 @@ void attribute_search_add_items(StringRefNull str, } // namespace blender::ui +enum eUIListFilterResult { + /** Never show this item, even when filter results are inverted (#UILST_FLT_EXCLUDE). */ + UI_LIST_ITEM_NEVER_SHOW, + /** Show this item, unless filter results are inverted (#UILST_FLT_EXCLUDE). */ + UI_LIST_ITEM_FILTER_MATCHES, + /** Don't show this item, unless filter results are inverted (#UILST_FLT_EXCLUDE). */ + UI_LIST_ITEM_FILTER_MISMATCHES, +}; + +/** + * Function object for UI list item filtering that does the default name comparison with '*' + * wildcards. Create an instance of this once and pass it to #UI_list_filter_and_sort_items(), do + * NOT create an instance for every item, this would be costly. + */ +class uiListNameFilter { + /* Storage with an inline buffer for smaller strings (small buffer optimization). */ + struct { + char filter_buff[32]; + char *filter_dyn = nullptr; + } storage_; + char *filter_ = nullptr; + + public: + uiListNameFilter(uiList &list); + ~uiListNameFilter(); + + eUIListFilterResult operator()(const PointerRNA &itemptr, + blender::StringRefNull name, + int index); +}; + +using uiListItemFilterFn = blender::FunctionRef; +using uiListItemGetNameFn = + blender::FunctionRef; + +/** + * Filter list items using \a item_filter_fn and sort the result. This respects the normal UI list + * filter settings like alphabetical sorting (#UILST_FLT_SORT_ALPHA), and result inverting + * (#UILST_FLT_EXCLUDE). + * + * Call this from a #uiListType::filter_items callback with any #item_filter_fn. #uiListNameFilter + * can be used to apply the default name based filtering. + * + * \param get_name_fn: In some cases the name cannot be retrieved via RNA. This function can be set + * to provide the name still. + */ +void UI_list_filter_and_sort_items(uiList *ui_list, + const struct bContext *C, + uiListItemFilterFn item_filter_fn, + PointerRNA *dataptr, + const char *propname, + uiListItemGetNameFn get_name_fn = nullptr); + /** * Override this for all available view types. */ diff --git a/source/blender/editors/interface/interface_template_asset_view.cc b/source/blender/editors/interface/interface_template_asset_view.cc index 55b2e780a9e..71bd66ff651 100644 --- a/source/blender/editors/interface/interface_template_asset_view.cc +++ b/source/blender/editors/interface/interface_template_asset_view.cc @@ -24,6 +24,7 @@ #include "RNA_prototypes.h" #include "UI_interface.h" +#include "UI_interface.hh" #include "WM_api.h" #include "WM_types.h" @@ -32,6 +33,7 @@ struct AssetViewListData { AssetLibraryReference asset_library_ref; + AssetFilterSettings filter_settings; bScreen *screen; bool show_names; }; @@ -45,8 +47,6 @@ static void asset_view_item_but_drag_set(uiBut *but, AssetHandle *asset_handle) } char blend_path[FILE_MAX_LIBEXTRA]; - /* Context can be null here, it's only needed for a File Browser specific hack that should go - * away before too long. */ ED_asset_handle_get_full_library_path(asset_handle, blend_path); const eAssetImportMethod import_method = @@ -68,19 +68,23 @@ static void asset_view_draw_item(uiList *ui_list, const bContext * /*C*/, uiLayout *layout, PointerRNA * /*dataptr*/, - PointerRNA *itemptr, + PointerRNA * /*itemptr*/, int /*icon*/, PointerRNA * /*active_dataptr*/, const char * /*active_propname*/, - int /*index*/, + int index, int /*flt_flag*/) { AssetViewListData *list_data = (AssetViewListData *)ui_list->dyn_data->customdata; - BLI_assert(RNA_struct_is_a(itemptr->type, &RNA_AssetHandle)); - AssetHandle *asset_handle = (AssetHandle *)itemptr->data; + AssetHandle asset_handle = ED_assetlist_asset_get_by_index(&list_data->asset_library_ref, index); - uiLayoutSetContextPointer(layout, "asset_handle", itemptr); + PointerRNA file_ptr; + RNA_pointer_create(&list_data->screen->id, + &RNA_FileSelectEntry, + const_cast(asset_handle.file_data), + &file_ptr); + uiLayoutSetContextPointer(layout, "active_file", &file_ptr); uiBlock *block = uiLayoutGetBlock(layout); const bool show_names = list_data->show_names; @@ -90,8 +94,8 @@ static void asset_view_draw_item(uiList *ui_list, uiBut *but = uiDefIconTextBut(block, UI_BTYPE_PREVIEW_TILE, 0, - ED_asset_handle_get_preview_icon_id(asset_handle), - show_names ? ED_asset_handle_get_name(asset_handle) : "", + ED_asset_handle_get_preview_icon_id(&asset_handle), + show_names ? ED_asset_handle_get_name(&asset_handle) : "", 0, 0, size_x, @@ -103,14 +107,43 @@ static void asset_view_draw_item(uiList *ui_list, 0, ""); ui_def_but_icon(but, - ED_asset_handle_get_preview_icon_id(asset_handle), + ED_asset_handle_get_preview_icon_id(&asset_handle), /* NOLINTNEXTLINE: bugprone-suspicious-enum-usage */ UI_HAS_ICON | UI_BUT_ICON_PREVIEW); if (!ui_list->dyn_data->custom_drag_optype) { - asset_view_item_but_drag_set(but, asset_handle); + asset_view_item_but_drag_set(but, &asset_handle); } } +static void asset_view_filter_items(uiList *ui_list, + const bContext *C, + PointerRNA *dataptr, + const char *propname) +{ + AssetViewListData *list_data = (AssetViewListData *)ui_list->dyn_data->customdata; + AssetFilterSettings &filter_settings = list_data->filter_settings; + + uiListNameFilter name_filter(*ui_list); + + UI_list_filter_and_sort_items( + ui_list, + C, + [&name_filter, list_data, &filter_settings]( + const PointerRNA &itemptr, blender::StringRefNull name, int index) { + AssetHandle asset = ED_assetlist_asset_get_by_index(&list_data->asset_library_ref, index); + if (!ED_asset_filter_matches_asset(&filter_settings, &asset)) { + return UI_LIST_ITEM_NEVER_SHOW; + } + return name_filter(itemptr, name, index); + }, + dataptr, + propname, + [list_data](const PointerRNA & /*itemptr*/, int index) -> std::string { + AssetHandle asset = ED_assetlist_asset_get_by_index(&list_data->asset_library_ref, index); + return ED_asset_handle_get_name(&asset); + }); +} + static void asset_view_listener(uiList *ui_list, wmRegionListenerParams *params) { AssetViewListData *list_data = (AssetViewListData *)ui_list->dyn_data->customdata; @@ -136,16 +169,15 @@ uiListType *UI_UL_asset_view() BLI_strncpy(list_type->idname, "UI_UL_asset_view", sizeof(list_type->idname)); list_type->draw_item = asset_view_draw_item; + list_type->filter_items = asset_view_filter_items; list_type->listener = asset_view_listener; return list_type; } -static void asset_view_template_refresh_asset_collection( - const AssetLibraryReference &asset_library_ref, - const AssetFilterSettings &filter_settings, - PointerRNA &assets_dataptr, - const char *assets_propname) +static void populate_asset_collection(const AssetLibraryReference &asset_library_ref, + PointerRNA &assets_dataptr, + const char *assets_propname) { PropertyRNA *assets_prop = RNA_struct_find_property(&assets_dataptr, assets_propname); if (!assets_prop) { @@ -164,17 +196,15 @@ static void asset_view_template_refresh_asset_collection( RNA_property_collection_clear(&assets_dataptr, assets_prop); - ED_assetlist_iterate(asset_library_ref, [&](AssetHandle asset) { - if (!ED_asset_filter_matches_asset(&filter_settings, &asset)) { - /* Don't do anything else, but return true to continue iterating. */ - return true; - } + ED_assetlist_iterate(asset_library_ref, [&](AssetHandle /*asset*/) { + /* XXX creating a dummy #RNA_AssetHandle collection item. It's #file_data will be null. This is + * because the #FileDirEntry may be freed while iterating, there's a cache for them with a + * maximum size. Further code will query as needed it using the collection index. */ PointerRNA itemptr, fileptr; RNA_property_collection_add(&assets_dataptr, assets_prop, &itemptr); - RNA_pointer_create( - nullptr, &RNA_FileSelectEntry, const_cast(asset.file_data), &fileptr); + RNA_pointer_create(nullptr, &RNA_FileSelectEntry, nullptr, &fileptr); RNA_pointer_set(&itemptr, "file_data", fileptr); return true; @@ -221,12 +251,12 @@ void uiTemplateAssetView(uiLayout *layout, ED_assetlist_ensure_previews_job(&asset_library_ref, C); const int tot_items = ED_assetlist_size(&asset_library_ref); - asset_view_template_refresh_asset_collection( - asset_library_ref, *filter_settings, *assets_dataptr, assets_propname); + populate_asset_collection(asset_library_ref, *assets_dataptr, assets_propname); AssetViewListData *list_data = (AssetViewListData *)MEM_mallocN(sizeof(*list_data), "AssetViewListData"); list_data->asset_library_ref = asset_library_ref; + list_data->filter_settings = *filter_settings; list_data->screen = CTX_wm_screen(C); list_data->show_names = (display_flags & UI_TEMPLATE_ASSET_DRAW_NO_NAMES) == 0; diff --git a/source/blender/editors/interface/interface_template_list.cc b/source/blender/editors/interface/interface_template_list.cc index d5803e603f1..28ffee73ca3 100644 --- a/source/blender/editors/interface/interface_template_list.cc +++ b/source/blender/editors/interface/interface_template_list.cc @@ -8,9 +8,11 @@ #include #include "BLI_fnmatch.h" +#include "BLI_function_ref.hh" #include "BLI_listbase.h" #include "BLI_math_base.h" #include "BLI_string.h" +#include "BLI_string_ref.hh" #include "BLI_utildefines.h" #include "BKE_screen.h" @@ -26,12 +28,15 @@ #include "RNA_prototypes.h" #include "UI_interface.h" +#include "UI_interface.hh" #include "UI_view2d.h" #include "WM_api.h" #include "interface_intern.hh" +using namespace blender; + /** * The validated data that was passed to #uiTemplateList (typically through Python). * Populated through #ui_template_list_data_retrieve(). @@ -148,6 +153,45 @@ static void uilist_draw_filter_default(struct uiList *ui_list, } } +uiListNameFilter::uiListNameFilter(uiList &list) +{ + const char *filter_raw = list.filter_byname; + + if (filter_raw[0]) { + const size_t slen = strlen(filter_raw); + + /* Implicitly add heading/trailing wildcards if needed. */ + if (slen + 3 <= sizeof(storage_.filter_buff)) { + filter_ = storage_.filter_buff; + } + else { + filter_ = storage_.filter_dyn = static_cast( + MEM_mallocN((slen + 3) * sizeof(char), "filter_dyn")); + } + BLI_strncpy_ensure_pad(filter_, filter_raw, '*', slen + 3); + } +} + +uiListNameFilter::~uiListNameFilter() +{ + MEM_SAFE_FREE(storage_.filter_dyn); +} + +eUIListFilterResult uiListNameFilter::operator()(const PointerRNA & /* itemptr */, + StringRefNull name, + int /* index */) +{ + if (!filter_) { + return UI_LIST_ITEM_FILTER_MATCHES; + } + + /* Case-insensitive! */ + if (fnmatch(filter_, name.c_str(), FNM_CASEFOLD) == 0) { + return UI_LIST_ITEM_FILTER_MATCHES; + } + return UI_LIST_ITEM_FILTER_MISMATCHES; +} + struct StringCmp { char name[MAX_IDPROP_NAME]; int org_idx; @@ -159,16 +203,16 @@ static int cmpstringp(const void *p1, const void *p2) return BLI_strcasecmp(((StringCmp *)p1)->name, ((StringCmp *)p2)->name); } -static void uilist_filter_items_default(struct uiList *ui_list, - const struct bContext * /*C*/, - struct PointerRNA *dataptr, - const char *propname) +void UI_list_filter_and_sort_items(uiList *ui_list, + const bContext * /*C*/, + uiListItemFilterFn item_filter_fn, + PointerRNA *dataptr, + const char *propname, + uiListItemGetNameFn get_name_fn) { uiListDyn *dyn_data = ui_list->dyn_data; PropertyRNA *prop = RNA_struct_find_property(dataptr, propname); - const char *filter_raw = ui_list->filter_byname; - char *filter = (char *)filter_raw, filter_buff[32], *filter_dyn = nullptr; const bool filter_exclude = (ui_list->filter_flag & UILST_FLT_EXCLUDE) != 0; const bool order_by_name = (ui_list->filter_sort_flag & UILST_FLT_SORT_MASK) == UILST_FLT_SORT_ALPHA; @@ -176,41 +220,26 @@ static void uilist_filter_items_default(struct uiList *ui_list, dyn_data->items_shown = dyn_data->items_len = len; - if (len && (order_by_name || filter_raw[0])) { + if (len && (order_by_name || item_filter_fn)) { StringCmp *names = nullptr; int order_idx = 0, i = 0; if (order_by_name) { names = static_cast(MEM_callocN(sizeof(StringCmp) * len, "StringCmp")); } - if (filter_raw[0]) { - const size_t slen = strlen(filter_raw); + if (item_filter_fn) { dyn_data->items_filter_flags = static_cast( MEM_callocN(sizeof(int) * len, "items_filter_flags")); dyn_data->items_shown = 0; - - /* Implicitly add heading/trailing wildcards if needed. */ - if (slen + 3 <= sizeof(filter_buff)) { - filter = filter_buff; - } - else { - filter = filter_dyn = static_cast( - MEM_mallocN((slen + 3) * sizeof(char), "filter_dyn")); - } - BLI_strncpy_ensure_pad(filter, filter_raw, '*', slen + 3); } RNA_PROP_BEGIN (dataptr, itemptr, prop) { bool do_order = false; char *namebuf; - if (RNA_struct_is_a(itemptr.type, &RNA_AssetHandle)) { - /* XXX The AssetHandle design is hacky and meant to be temporary. It can't have a proper - * name property, so for now this hardcoded exception is needed. */ - AssetHandle *asset_handle = (AssetHandle *)itemptr.data; - const char *asset_name = ED_asset_handle_get_name(asset_handle); - namebuf = BLI_strdup(asset_name); + if (get_name_fn) { + namebuf = BLI_strdup(get_name_fn(itemptr, i).c_str()); } else { namebuf = RNA_struct_name_get_alloc(&itemptr, nullptr, 0, nullptr); @@ -218,9 +247,13 @@ static void uilist_filter_items_default(struct uiList *ui_list, const char *name = namebuf ? namebuf : ""; - if (filter[0]) { - /* Case-insensitive! */ - if (fnmatch(filter, name, FNM_CASEFOLD) == 0) { + if (item_filter_fn) { + const eUIListFilterResult filter_result = item_filter_fn(itemptr, name, i); + + if (filter_result == UI_LIST_ITEM_NEVER_SHOW) { + /* Pass. */ + } + else if (filter_result == UI_LIST_ITEM_FILTER_MATCHES) { dyn_data->items_filter_flags[i] = UILST_FLT_ITEM; if (!filter_exclude) { dyn_data->items_shown++; @@ -266,15 +299,30 @@ static void uilist_filter_items_default(struct uiList *ui_list, } } - if (filter_dyn) { - MEM_freeN(filter_dyn); - } if (names) { MEM_freeN(names); } } } +/** + * Default UI List filtering: Filter by name. + */ +static void uilist_filter_items_default(struct uiList *ui_list, + const struct bContext *C, + struct PointerRNA *dataptr, + const char *propname) +{ + if (ui_list->filter_byname[0]) { + uiListNameFilter name_filter(*ui_list); + UI_list_filter_and_sort_items(ui_list, C, name_filter, dataptr, propname); + } + /* Optimization: Skip filtering entirely when there is no filter string set. */ + else { + UI_list_filter_and_sort_items(ui_list, C, nullptr, dataptr, propname); + } +} + static void uilist_free_dyn_data(uiList *ui_list) { uiListDyn *dyn_data = ui_list->dyn_data; diff --git a/source/blender/editors/space_node/add_menu_assets.cc b/source/blender/editors/space_node/add_menu_assets.cc index 060e8914194..710eac2be0f 100644 --- a/source/blender/editors/space_node/add_menu_assets.cc +++ b/source/blender/editors/space_node/add_menu_assets.cc @@ -3,6 +3,7 @@ #include "AS_asset_catalog.hh" #include "AS_asset_catalog_tree.hh" #include "AS_asset_library.hh" +#include "AS_asset_representation.h" #include "BLI_multi_value_map.hh" @@ -46,7 +47,7 @@ static void node_add_menu_assets_listen_fn(const wmRegionListenerParams *params) struct LibraryAsset { AssetLibraryReference library_ref; - AssetHandle handle; + AssetRepresentation &asset; }; struct AssetItemTree { @@ -93,11 +94,11 @@ static AssetItemTree build_catalog_tree(const bContext &C, const bNodeTree *node return {}; } - ED_assetlist_iterate(all_library_ref, [&](AssetHandle asset) { - if (!ED_asset_filter_matches_asset(&type_filter, &asset)) { + ED_assetlist_iterate(all_library_ref, [&](AssetHandle asset_handle) { + if (!ED_asset_filter_matches_asset(&type_filter, &asset_handle)) { return true; } - const AssetMetaData &meta_data = *ED_asset_handle_get_metadata(&asset); + const AssetMetaData &meta_data = *ED_asset_handle_get_metadata(&asset_handle); const IDProperty *tree_type = BKE_asset_metadata_idprop_find(&meta_data, "type"); if (tree_type == nullptr || IDP_Int(tree_type) != node_tree->type) { return true; @@ -111,7 +112,8 @@ static AssetItemTree build_catalog_tree(const bContext &C, const bNodeTree *node if (catalog == nullptr) { return true; } - assets_per_path.add(catalog->path, LibraryAsset{all_library_ref, asset}); + AssetRepresentation *asset = ED_asset_handle_get_representation(&asset_handle); + assets_per_path.add(catalog->path, LibraryAsset{all_library_ref, *asset}); return true; }); @@ -178,16 +180,17 @@ static void node_add_catalog_assets_draw(const bContext *C, Menu *menu) for (const LibraryAsset &item : asset_items) { uiLayout *col = uiLayoutColumn(layout, false); - PointerRNA file{ - &screen.id, &RNA_FileSelectEntry, const_cast(item.handle.file_data)}; - uiLayoutSetContextPointer(col, "active_file", &file); + + PointerRNA asset_ptr{NULL, &RNA_AssetRepresentation, &item.asset}; + uiLayoutSetContextPointer(col, "asset", &asset_ptr); PointerRNA library_ptr{&screen.id, &RNA_AssetLibraryReference, const_cast(&item.library_ref)}; uiLayoutSetContextPointer(col, "asset_library_ref", &library_ptr); - uiItemO(col, ED_asset_handle_get_name(&item.handle), ICON_NONE, "NODE_OT_add_group_asset"); + uiItemO( + col, AS_asset_representation_name_get(&item.asset), ICON_NONE, "NODE_OT_add_group_asset"); } catalog_item->foreach_child([&](asset_system::AssetCatalogTreeItem &child_item) { diff --git a/source/blender/editors/space_node/add_node_search.cc b/source/blender/editors/space_node/add_node_search.cc index 16505620e6a..55f60758ac3 100644 --- a/source/blender/editors/space_node/add_node_search.cc +++ b/source/blender/editors/space_node/add_node_search.cc @@ -39,7 +39,7 @@ struct AddNodeItem { std::string ui_name; std::string identifier; std::string description; - std::optional asset; + const AssetRepresentation *asset; std::function after_add_fn; int weight = 0; }; @@ -67,24 +67,25 @@ static void add_node_search_listen_fn(const wmRegionListenerParams *params, void } static void search_items_for_asset_metadata(const bNodeTree &node_tree, - const AssetHandle asset, + const AssetHandle asset_handle, Vector &search_items) { - const AssetMetaData &asset_data = *ED_asset_handle_get_metadata(&asset); + const AssetMetaData &asset_data = *ED_asset_handle_get_metadata(&asset_handle); const IDProperty *tree_type = BKE_asset_metadata_idprop_find(&asset_data, "type"); if (tree_type == nullptr || IDP_Int(tree_type) != node_tree.type) { return; } + const AssetRepresentation *asset = ED_asset_handle_get_representation(&asset_handle); AddNodeItem item{}; - item.ui_name = ED_asset_handle_get_name(&asset); + item.ui_name = ED_asset_handle_get_name(&asset_handle); item.identifier = node_tree.typeinfo->group_idname; item.description = asset_data.description == nullptr ? "" : asset_data.description; item.asset = asset; item.after_add_fn = [asset](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, asset); + node.id = ED_asset_get_local_id_from_asset_or_append_and_reuse(&bmain, asset, ID_NT); id_us_plus(node.id); BKE_ntree_update_tag_node_property(&node_tree, &node); DEG_relations_tag_update(&bmain); diff --git a/source/blender/editors/space_node/link_drag_search.cc b/source/blender/editors/space_node/link_drag_search.cc index b0244f599b6..b7270907c9d 100644 --- a/source/blender/editors/space_node/link_drag_search.cc +++ b/source/blender/editors/space_node/link_drag_search.cc @@ -143,10 +143,10 @@ static void add_existing_group_input_fn(nodes::LinkSearchOpParams ¶ms, */ static void search_link_ops_for_asset_metadata(const bNodeTree &node_tree, const bNodeSocket &socket, - const AssetHandle asset, + const AssetHandle asset_handle, Vector &search_link_ops) { - const AssetMetaData &asset_data = *ED_asset_handle_get_metadata(&asset); + const AssetMetaData &asset_data = *ED_asset_handle_get_metadata(&asset_handle); const IDProperty *tree_type = BKE_asset_metadata_idprop_find(&asset_data, "type"); if (tree_type == nullptr || IDP_Int(tree_type) != node_tree.type) { return; @@ -182,7 +182,8 @@ static void search_link_ops_for_asset_metadata(const bNodeTree &node_tree, continue; } - const StringRef asset_name = ED_asset_handle_get_name(&asset); + AssetRepresentation *asset = ED_asset_handle_get_representation(&asset_handle); + const StringRef asset_name = ED_asset_handle_get_name(&asset_handle); const StringRef socket_name = socket_property->name; search_link_ops.append( @@ -193,7 +194,7 @@ static void search_link_ops_for_asset_metadata(const bNodeTree &node_tree, bNode &node = params.add_node(params.node_tree.typeinfo->group_idname); node.flag &= ~NODE_OPTIONS; - node.id = asset::get_local_id_from_asset_or_append_and_reuse(bmain, asset); + node.id = ED_asset_get_local_id_from_asset_or_append_and_reuse(&bmain, asset, ID_NT); id_us_plus(node.id); BKE_ntree_update_tag_node_property(¶ms.node_tree, &node); DEG_relations_tag_update(&bmain); diff --git a/source/blender/editors/space_node/node_add.cc b/source/blender/editors/space_node/node_add.cc index 473ae4a6f83..c175d994ec3 100644 --- a/source/blender/editors/space_node/node_add.cc +++ b/source/blender/editors/space_node/node_add.cc @@ -7,6 +7,8 @@ #include +#include "AS_asset_representation.h" + #include "MEM_guardedalloc.h" #include "DNA_collection_types.h" @@ -378,14 +380,16 @@ void NODE_OT_add_group(wmOperatorType *ot) /** \name Add Node Group Asset Operator * \{ */ -static bool add_node_group_asset(const bContext &C, const AssetHandle asset, ReportList &reports) +static bool add_node_group_asset(const bContext &C, + const AssetRepresentation *asset, + ReportList &reports) { Main &bmain = *CTX_data_main(&C); SpaceNode &snode = *CTX_wm_space_node(&C); bNodeTree &edit_tree = *snode.edittree; bNodeTree *node_group = reinterpret_cast( - asset::get_local_id_from_asset_or_append_and_reuse(bmain, asset)); + ED_asset_get_local_id_from_asset_or_append_and_reuse(&bmain, asset, ID_NT)); if (!node_group) { return false; } @@ -427,9 +431,8 @@ static int node_add_group_asset_invoke(bContext *C, wmOperator *op, const wmEven if (!library_ref) { return OPERATOR_CANCELLED; } - bool is_valid; - const AssetHandle handle = CTX_wm_asset_handle(C, &is_valid); - if (!is_valid) { + const AssetRepresentation *asset = CTX_wm_asset(C); + if (!asset) { return OPERATOR_CANCELLED; } @@ -442,7 +445,7 @@ static int node_add_group_asset_invoke(bContext *C, wmOperator *op, const wmEven snode.runtime->cursor /= UI_DPI_FAC; - if (!add_node_group_asset(*C, handle, *op->reports)) { + if (!add_node_group_asset(*C, asset, *op->reports)) { return OPERATOR_CANCELLED; } @@ -460,12 +463,11 @@ static char *node_add_group_asset_get_description(struct bContext *C, struct wmOperatorType * /*op*/, struct PointerRNA * /*values*/) { - bool is_valid; - const AssetHandle handle = CTX_wm_asset_handle(C, &is_valid); - if (!is_valid) { + const AssetRepresentation *asset = CTX_wm_asset(C); + if (!asset) { return nullptr; } - const AssetMetaData &asset_data = *ED_asset_handle_get_metadata(&handle); + const AssetMetaData &asset_data = *AS_asset_representation_metadata_get(asset); if (!asset_data.description) { return nullptr; } diff --git a/source/blender/makesdna/DNA_asset_types.h b/source/blender/makesdna/DNA_asset_types.h index 5ed625211e5..0857d92cff7 100644 --- a/source/blender/makesdna/DNA_asset_types.h +++ b/source/blender/makesdna/DNA_asset_types.h @@ -140,6 +140,10 @@ typedef struct AssetLibraryReference { * Not part of the core design, we should try to get rid of it. Only needed to wrap FileDirEntry * into a type with PropertyGroup as base, so we can have an RNA collection of #AssetHandle's to * pass to the UI. + * + * \warning Never store this! When using #ED_assetlist_iterate(), only access it within the + * iterator function. The contained file data can be freed since the file cache has a + * maximum number of items. */ # # diff --git a/source/blender/makesrna/intern/rna_asset.c b/source/blender/makesrna/intern/rna_asset.c index 4a48562b03e..b88322e7c25 100644 --- a/source/blender/makesrna/intern/rna_asset.c +++ b/source/blender/makesrna/intern/rna_asset.c @@ -578,6 +578,17 @@ static void rna_def_asset_handle(BlenderRNA *brna) rna_def_asset_handle_api(srna); } +static void rna_def_asset_representation(BlenderRNA *brna) +{ + StructRNA *srna; + + srna = RNA_def_struct(brna, "AssetRepresentation", NULL); + RNA_def_struct_ui_text(srna, + "Asset Representation", + "Information about an entity that makes it possible for the asset system " + "to deal with the entity as asset"); +} + static void rna_def_asset_catalog_path(BlenderRNA *brna) { StructRNA *srna = RNA_def_struct(brna, "AssetCatalogPath", NULL); @@ -610,6 +621,7 @@ void RNA_def_asset(BlenderRNA *brna) rna_def_asset_data(brna); rna_def_asset_library_reference(brna); rna_def_asset_handle(brna); + rna_def_asset_representation(brna); rna_def_asset_catalog_path(brna); RNA_define_animate_sdna(true);