diff --git a/source/blender/asset_system/intern/asset_library_service.cc b/source/blender/asset_system/intern/asset_library_service.cc index 1ce1fdd0dc8..7d53ebf8140 100644 --- a/source/blender/asset_system/intern/asset_library_service.cc +++ b/source/blender/asset_system/intern/asset_library_service.cc @@ -221,6 +221,10 @@ std::string AssetLibraryService::root_path_from_library_ref( return essentials_directory_path(); } + if (ELEM(library_reference.type, ASSET_LIBRARY_ESSENTIALS)) { + return essentials_directory_path(); + } + bUserAssetLibrary *custom_library = find_custom_asset_library_from_library_ref( library_reference); if (!custom_library || !custom_library->path[0]) { diff --git a/source/blender/blenkernel/BKE_screen.h b/source/blender/blenkernel/BKE_screen.h index a86953f35cc..dca1252e270 100644 --- a/source/blender/blenkernel/BKE_screen.h +++ b/source/blender/blenkernel/BKE_screen.h @@ -128,6 +128,9 @@ typedef struct SpaceType { /* region type definitions */ ListBase regiontypes; + /* Asset shelf type definitions */ + ListBase asset_shelf_types; /* AssetShelfType */ + /* read and write... */ /** Default key-maps to add. */ @@ -396,6 +399,26 @@ typedef struct Menu { struct uiLayout *layout; /* runtime for drawing */ } Menu; +/* asset shelf types */ + +typedef struct AssetShelfType { + struct AssetShelfType *next, *prev; + + char idname[BKE_ST_MAXNAME]; /* unique name */ + + int space_type; + + /* Determine if the asset shelf should be visible or not. */ + bool (*poll)(const struct bContext *C, const struct AssetShelfType *shelf_type); + + /* Determine if an individual asset should be visible or not. May be a temporary design, + * visibility should be first and foremost controlled by asset traits. */ + bool (*asset_poll)(const struct AssetShelfType *shelf_type, const struct AssetHandle *asset); + + /* RNA integration */ + ExtensionRNA rna_ext; +} AssetShelfType; + /* Space-types. */ struct SpaceType *BKE_spacetype_from_id(int spaceid); diff --git a/source/blender/blenloader/intern/versioning_300.cc b/source/blender/blenloader/intern/versioning_300.cc index 62751661645..fe3d92bea39 100644 --- a/source/blender/blenloader/intern/versioning_300.cc +++ b/source/blender/blenloader/intern/versioning_300.cc @@ -4190,5 +4190,44 @@ void blo_do_versions_300(FileData *fd, Library * /*lib*/, Main *bmain) */ { /* Keep this block, even when empty. */ + LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) { + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { + if (sl->spacetype == SPACE_VIEW3D) { + ListBase *regionbase = (sl == area->spacedata.first) ? &area->regionbase : + &sl->regionbase; + { + ARegion *new_asset_shelf_footer = do_versions_add_region_if_not_found( + regionbase, + RGN_TYPE_ASSET_SHELF_FOOTER, + "asset shelf footer for view3d (versioning)", + RGN_TYPE_UI); + if (new_asset_shelf_footer != nullptr) { + new_asset_shelf_footer->alignment = RGN_ALIGN_BOTTOM; + new_asset_shelf_footer->flag |= RGN_FLAG_HIDDEN; + } + } + { + ARegion *new_asset_shelf = do_versions_add_region_if_not_found( + regionbase, + RGN_TYPE_ASSET_SHELF, + "asset shelf for view3d (versioning)", + RGN_TYPE_ASSET_SHELF_FOOTER); + if (new_asset_shelf != nullptr) { + new_asset_shelf->alignment = RGN_ALIGN_BOTTOM | RGN_SPLIT_PREV; + new_asset_shelf->flag |= RGN_FLAG_DYNAMIC_SIZE; + } + } + } + } + } + } + + /* Should we really use the "All" library by default? Consider loading time and memory usage. + */ + LISTBASE_FOREACH (WorkSpace *, workspace, &bmain->workspaces) { + workspace->asset_library_ref.type = ASSET_LIBRARY_ALL; + workspace->asset_library_ref.custom_library_index = -1; + } } } diff --git a/source/blender/blenloader/intern/versioning_defaults.cc b/source/blender/blenloader/intern/versioning_defaults.cc index abdda14bb72..b911315affd 100644 --- a/source/blender/blenloader/intern/versioning_defaults.cc +++ b/source/blender/blenloader/intern/versioning_defaults.cc @@ -260,6 +260,9 @@ void BLO_update_defaults_workspace(WorkSpace *workspace, const char *app_templat BKE_workspace_tool_remove(workspace, static_cast(workspace->tools.first)); } + workspace->asset_library_ref.type = ASSET_LIBRARY_ALL; + workspace->asset_library_ref.custom_library_index = -1; + /* For 2D animation template. */ if (STREQ(workspace->id.name + 2, "Drawing")) { workspace->object_mode = OB_MODE_PAINT_GPENCIL; diff --git a/source/blender/editors/asset/CMakeLists.txt b/source/blender/editors/asset/CMakeLists.txt index 9d5f10eddd2..73a7fe24fd3 100644 --- a/source/blender/editors/asset/CMakeLists.txt +++ b/source/blender/editors/asset/CMakeLists.txt @@ -13,6 +13,8 @@ set(INC ../../windowmanager ../../../../intern/clog ../../../../intern/guardedalloc + # dna_type_offsets.h + ${CMAKE_CURRENT_BINARY_DIR}/../../makesdna/intern # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna ) @@ -31,6 +33,7 @@ set(SRC intern/asset_list.cc intern/asset_mark_clear.cc intern/asset_ops.cc + intern/asset_shelf.cc intern/asset_temp_id_consumer.cc intern/asset_type.cc @@ -44,6 +47,7 @@ set(SRC ED_asset_list.h ED_asset_list.hh ED_asset_mark_clear.h + ED_asset_shelf.h ED_asset_temp_id_consumer.h ED_asset_type.h intern/asset_library_reference.hh diff --git a/source/blender/editors/asset/ED_asset_list.h b/source/blender/editors/asset/ED_asset_list.h index 19528c5dde6..71d7900aa32 100644 --- a/source/blender/editors/asset/ED_asset_list.h +++ b/source/blender/editors/asset/ED_asset_list.h @@ -58,8 +58,7 @@ struct ImBuf *ED_assetlist_asset_image_get(const AssetHandle *asset_handle); /** * \return True if the region needs a UI redraw. */ -bool ED_assetlist_listen(const struct AssetLibraryReference *library_reference, - const struct wmNotifier *notifier); +bool ED_assetlist_listen(const struct wmNotifier *notifier); /** * \return The number of assets stored in the asset list for \a library_reference, or -1 if there * is no list fetched for it. diff --git a/source/blender/editors/asset/ED_asset_shelf.h b/source/blender/editors/asset/ED_asset_shelf.h new file mode 100644 index 00000000000..11b1f4c2808 --- /dev/null +++ b/source/blender/editors/asset/ED_asset_shelf.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edasset + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +struct ARegionType; +struct AssetShelfSettings; +struct bContext; +struct bContextDataResult; +struct BlendDataReader; +struct BlendWriter; +struct wmWindowManager; + +/* -------------------------------------------------------------------- */ +/* Asset Shelf Regions */ + +/** Only needed for #RGN_TYPE_ASSET_SHELF (not #RGN_TYPE_ASSET_SHELF_FOOTER). */ +void ED_asset_shelf_region_listen(const struct wmRegionListenerParams *params); +void ED_asset_shelf_region_draw(const bContext *C, struct ARegion *region); +void ED_asset_shelf_region_register(ARegionType *region_type, + const char *idname, + const int space_type); + +void ED_asset_shelf_footer_region_init(struct wmWindowManager *wm, struct ARegion *region); +void ED_asset_shelf_footer_region(const struct bContext *C, struct ARegion *region); +void ED_asset_shelf_footer_region_listen(const struct wmRegionListenerParams *params); +void ED_asset_shelf_footer_register(struct ARegionType *region_type, + const char *idname, + const int space_type); + +/* -------------------------------------------------------------------- */ +/* Asset Shelf Settings */ + +/** + * Deep-copies \a shelf_settings into newly allocated memory. Must be freed using #MEM_freeN() or + * #MEM_delete(). + */ +AssetShelfSettings *ED_asset_shelf_settings_duplicate(const AssetShelfSettings *shelf_settings); +/** + * Frees the contained data, not \a shelf_settings itself. + */ +void ED_asset_shelf_settings_free(AssetShelfSettings *shelf_settings); + +void ED_asset_shelf_settings_blend_write(struct BlendWriter *writer, + const struct AssetShelfSettings *storage); +void ED_asset_shelf_settings_blend_read_data(struct BlendDataReader *reader, + struct AssetShelfSettings **storage); + +/* -------------------------------------------------------------------- */ + +/** + * Creates an `"asset_shelf_settings"` context member, pointing to \a shelf_settings. + */ +int ED_asset_shelf_context(const struct bContext *C, + const char *member, + struct bContextDataResult *result, + struct AssetShelfSettings *shelf_settings); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/editors/asset/intern/asset_filter.cc b/source/blender/editors/asset/intern/asset_filter.cc index 3bcf42ff206..4b8c34090bd 100644 --- a/source/blender/editors/asset/intern/asset_filter.cc +++ b/source/blender/editors/asset/intern/asset_filter.cc @@ -18,7 +18,7 @@ bool ED_asset_filter_matches_asset(const AssetFilterSettings *filter, const Asse ID_Type asset_type = ED_asset_handle_get_id_type(asset); uint64_t asset_id_filter = BKE_idtype_idcode_to_idfilter(asset_type); - if ((filter->id_types & asset_id_filter) == 0) { + if (filter->id_types && (filter->id_types & asset_id_filter) == 0) { return false; } /* Not very efficient (O(n^2)), could be improved quite a bit. */ diff --git a/source/blender/editors/asset/intern/asset_list.cc b/source/blender/editors/asset/intern/asset_list.cc index c7df300ed24..41d0ca3f4e1 100644 --- a/source/blender/editors/asset/intern/asset_list.cc +++ b/source/blender/editors/asset/intern/asset_list.cc @@ -118,7 +118,7 @@ class AssetList : NonCopyable { bool isLoaded() const; asset_system::AssetLibrary *asset_library() const; void iterate(AssetListIterFn fn) const; - bool listen(const wmNotifier ¬ifier) const; + static bool listen(const wmNotifier ¬ifier); int size() const; void tagMainDataDirty() const; void remapID(ID *id_old, ID *id_new) const; @@ -256,7 +256,7 @@ AssetHandle AssetList::asset_get_by_index(int index) const /** * \return True if the asset-list needs a UI redraw. */ -bool AssetList::listen(const wmNotifier ¬ifier) const +bool AssetList::listen(const wmNotifier ¬ifier) { switch (notifier.category) { case NC_ID: { @@ -496,14 +496,9 @@ ImBuf *ED_assetlist_asset_image_get(const AssetHandle *asset_handle) return filelist_geticon_image_ex(asset_handle->file_data); } -bool ED_assetlist_listen(const AssetLibraryReference *library_reference, - const wmNotifier *notifier) +bool ED_assetlist_listen(const wmNotifier *notifier) { - AssetList *list = AssetListStorage::lookup_list(*library_reference); - if (list) { - return list->listen(*notifier); - } - return false; + return AssetList::listen(*notifier); } int ED_assetlist_size(const AssetLibraryReference *library_reference) diff --git a/source/blender/editors/asset/intern/asset_shelf.cc b/source/blender/editors/asset/intern/asset_shelf.cc new file mode 100644 index 00000000000..249d55c172a --- /dev/null +++ b/source/blender/editors/asset/intern/asset_shelf.cc @@ -0,0 +1,563 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edasset + */ + +#include "AS_asset_catalog.hh" +#include "AS_asset_catalog_tree.hh" +#include "AS_asset_library.hh" + +#include "BKE_context.h" +#include "BKE_screen.h" + +#include "BLO_read_write.h" + +#include "BLT_translation.h" + +#include "DNA_screen_types.h" + +#include "ED_asset_list.h" +#include "ED_asset_list.hh" +#include "ED_screen.h" + +#include "RNA_prototypes.h" + +#include "UI_interface.h" +#include "UI_interface.hh" +#include "UI_resources.h" +#include "UI_tree_view.hh" + +#include "WM_api.h" + +#include "ED_asset_shelf.h" + +using namespace blender; + +static void asset_shelf_send_redraw_notifier(bContext &C) +{ + WM_event_add_notifier(&C, NC_SPACE | ND_SPACE_ASSET_SHELF, nullptr); +} + +/* -------------------------------------------------------------------- */ +/** \name Asset Shelf Regions + * \{ */ + +static void asset_shelf_region_listen(const wmRegionListenerParams *params) +{ + ARegion *region = params->region; + const wmNotifier *wmn = params->notifier; + + switch (wmn->category) { + case NC_SPACE: + if (wmn->data == ND_SPACE_ASSET_SHELF) { + ED_region_tag_redraw(region); + } + break; + case NC_SCENE: + /* Asset shelf polls typically check the mode. */ + if (ELEM(wmn->data, ND_MODE)) { + ED_region_tag_redraw(region); + } + break; + } +} + +void ED_asset_shelf_region_listen(const wmRegionListenerParams *params) +{ + if (ED_assetlist_listen(params->notifier)) { + ED_region_tag_redraw_no_rebuild(params->region); + } + /* If the asset list didn't catch the notifier, let the region itself listen. */ + else { + asset_shelf_region_listen(params); + } +} + +/** + * Check if there is any asset shelf type returning true in it's poll. If not, no asset shelf + * region should be displayed. + */ +static bool asset_shelf_region_header_type_poll(const bContext *C, HeaderType * /*header_type*/) +{ + const SpaceLink *space_link = CTX_wm_space_data(C); + const SpaceType *space_type = BKE_spacetype_from_id(space_link->spacetype); + + /* Is there any asset shelf type registered that returns true for it's poll? */ + LISTBASE_FOREACH (AssetShelfType *, shelf_type, &space_type->asset_shelf_types) { + if (shelf_type->poll && shelf_type->poll(C, shelf_type)) { + return true; + } + } + + return false; +} + +void ED_asset_shelf_region_draw(const bContext *C, ARegion *region) +{ + ED_region_header(C, region); +} + +static void asset_shelf_region_draw(const bContext *C, Header *header) +{ + uiLayout *layout = header->layout; + AssetFilterSettings dummy_filter_settings{0}; + + uiTemplateAssetShelf(layout, C, &dummy_filter_settings); +} + +void ED_asset_shelf_region_register(ARegionType *region_type, + const char *idname, + const int space_type) +{ + HeaderType *ht = MEM_cnew(__func__); + strcpy(ht->idname, idname); + ht->space_type = space_type; + ht->region_type = RGN_TYPE_ASSET_SHELF_FOOTER; + ht->draw = asset_shelf_region_draw; + ht->poll = asset_shelf_region_header_type_poll; + BLI_addtail(®ion_type->headertypes, ht); +} + +void ED_asset_shelf_footer_region_listen(const wmRegionListenerParams *params) +{ + asset_shelf_region_listen(params); +} + +void ED_asset_shelf_footer_region_init(wmWindowManager * /*wm*/, ARegion *region) +{ + ED_region_header_init(region); +} + +void ED_asset_shelf_footer_region(const bContext *C, ARegion *region) +{ + ED_region_header(C, region); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Asset Shelf Settings + * \{ */ + +AssetShelfSettings *ED_asset_shelf_settings_duplicate(const AssetShelfSettings *shelf_settings) +{ + if (!shelf_settings) { + return nullptr; + } + + static_assert( + std::is_trivial_v, + "AssetShelfSettings needs to be trivial to allow freeing with MEM_freeN() (API promise)"); + AssetShelfSettings *new_settings = MEM_new(__func__, *shelf_settings); + + LISTBASE_FOREACH (LinkData *, catalog_path_item, &shelf_settings->enabled_catalog_paths) { + LinkData *new_path_item = static_cast(MEM_dupallocN(catalog_path_item)); + new_path_item->data = BLI_strdup((char *)catalog_path_item->data); + BLI_addtail(&new_settings->enabled_catalog_paths, new_path_item); + } + + return new_settings; +} + +static void asset_shelf_settings_clear_enabled_catalogs(AssetShelfSettings &shelf_settings) +{ + LISTBASE_FOREACH_MUTABLE (LinkData *, catalog_path_item, &shelf_settings.enabled_catalog_paths) { + MEM_freeN(catalog_path_item->data); + BLI_freelinkN(&shelf_settings.enabled_catalog_paths, catalog_path_item); + } + BLI_assert(BLI_listbase_is_empty(&shelf_settings.enabled_catalog_paths)); +} + +static void asset_shelf_settings_set_active_catalog(AssetShelfSettings &shelf_settings, + const asset_system::AssetCatalogPath &path) +{ + MEM_delete(shelf_settings.active_catalog_path); + shelf_settings.active_catalog_path = BLI_strdupn(path.c_str(), path.length()); +} + +static bool asset_shelf_settings_is_active_catalog(const AssetShelfSettings &shelf_settings, + const asset_system::AssetCatalogPath &path) +{ + return shelf_settings.active_catalog_path && shelf_settings.active_catalog_path == path.str(); +} + +void ED_asset_shelf_settings_free(AssetShelfSettings *shelf_settings) +{ + asset_shelf_settings_clear_enabled_catalogs(*shelf_settings); + MEM_delete(shelf_settings->active_catalog_path); +} + +void ED_asset_shelf_settings_blend_write(BlendWriter *writer, + const AssetShelfSettings *shelf_settings) +{ + if (!shelf_settings) { + return; + } + + BLO_write_struct(writer, AssetShelfSettings, shelf_settings); + + LISTBASE_FOREACH (LinkData *, catalog_path_item, &shelf_settings->enabled_catalog_paths) { + BLO_write_struct(writer, LinkData, catalog_path_item); + BLO_write_string(writer, (const char *)catalog_path_item->data); + } +} + +void ED_asset_shelf_settings_blend_read_data(BlendDataReader *reader, + AssetShelfSettings **shelf_settings) +{ + if (!*shelf_settings) { + return; + } + + BLO_read_data_address(reader, shelf_settings); + + BLO_read_list(reader, &(*shelf_settings)->enabled_catalog_paths); + LISTBASE_FOREACH (LinkData *, catalog_path_item, &(*shelf_settings)->enabled_catalog_paths) { + BLO_read_data_address(reader, &catalog_path_item->data); + } +} + +static bool asset_shelf_settings_is_catalog_path_enabled( + const AssetShelfSettings &shelf_settings, const asset_system::AssetCatalogPath &path) +{ + LISTBASE_FOREACH (LinkData *, catalog_path_item, &shelf_settings.enabled_catalog_paths) { + if (StringRef((const char *)catalog_path_item->data) == path.str()) { + return true; + } + } + return false; +} + +static void asset_shelf_settings_set_catalog_path_enabled( + AssetShelfSettings &shelf_settings, const asset_system::AssetCatalogPath &path) +{ + char *path_copy = BLI_strdupn(path.c_str(), path.length()); + BLI_addtail(&shelf_settings.enabled_catalog_paths, BLI_genericNodeN(path_copy)); +} + +static void asset_shelf_settings_foreach_enabled_catalog_path( + const AssetShelfSettings &shelf_settings, + FunctionRef fn) +{ + LISTBASE_FOREACH (LinkData *, catalog_path_item, &shelf_settings.enabled_catalog_paths) { + fn(asset_system::AssetCatalogPath((char *)catalog_path_item->data)); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Asset Shelf Context + * \{ */ + +int ED_asset_shelf_context(const bContext *C, + const char *member, + bContextDataResult *result, + AssetShelfSettings *shelf_settings) +{ + static const char *context_dir[] = { + "asset_shelf_settings", + "active_file", /* XXX yuk... */ + nullptr, + }; + + if (CTX_data_dir(member)) { + CTX_data_dir_set(result, context_dir); + return CTX_RESULT_OK; + } + + bScreen *screen = CTX_wm_screen(C); + + if (CTX_data_equals(member, "asset_shelf_settings")) { + CTX_data_pointer_set(result, &screen->id, &RNA_AssetShelfSettings, shelf_settings); + + return CTX_RESULT_OK; + } + + /* XXX hack. Get the asset from the hovered button, but needs to be the file... */ + if (CTX_data_equals(member, "active_file")) { + const uiBut *but = UI_context_active_but_get(C); + if (!but) { + return CTX_RESULT_NO_DATA; + } + + const bContextStore *but_context = UI_but_context_get(but); + if (!but_context) { + return CTX_RESULT_NO_DATA; + } + + const PointerRNA *file_ptr = CTX_store_ptr_lookup( + but_context, "active_file", &RNA_FileSelectEntry); + if (!file_ptr) { + return CTX_RESULT_NO_DATA; + } + + CTX_data_pointer_set_ptr(result, file_ptr); + return CTX_RESULT_OK; + } + + return CTX_RESULT_MEMBER_NOT_FOUND; +} + +static AssetShelfSettings *get_asset_shelf_settings_from_context(const bContext *C) +{ + PointerRNA shelf_settings_ptr = CTX_data_pointer_get_type( + C, "asset_shelf_settings", &RNA_AssetShelfSettings); + return static_cast(shelf_settings_ptr.data); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Asset Catalog Selector UI + * + * Popup containing a tree-view to select which catalogs to display in the asset shelf footer. + * \{ */ + +class AssetCatalogSelectorTree : public ui::AbstractTreeView { + asset_system::AssetLibrary &library_; + asset_system::AssetCatalogTree *catalog_tree_; + AssetShelfSettings &shelf_settings_; + + public: + class Item; + + AssetCatalogSelectorTree(asset_system::AssetLibrary &library, AssetShelfSettings &shelf_settings) + : library_(library), shelf_settings_(shelf_settings) + { + asset_system::AssetCatalogService *catalog_service = library_.catalog_service.get(); + catalog_tree_ = catalog_service->get_catalog_tree(); + } + + void build_tree() override + { + if (!catalog_tree_) { + return; + } + + catalog_tree_->foreach_root_item([this](asset_system::AssetCatalogTreeItem &catalog_item) { + build_catalog_items_recursive(*this, catalog_item); + }); + } + + Item &build_catalog_items_recursive(ui::TreeViewOrItem &parent_view_item, + asset_system::AssetCatalogTreeItem &catalog_item) const + { + Item &view_item = parent_view_item.add_tree_item(catalog_item, shelf_settings_); + + catalog_item.foreach_child([&view_item, this](asset_system::AssetCatalogTreeItem &child) { + build_catalog_items_recursive(view_item, child); + }); + + return view_item; + } + + void update_shelf_settings_from_enabled_catalogs(); + + class Item : public ui::BasicTreeViewItem { + asset_system::AssetCatalogTreeItem catalog_item_; + /* Is the catalog path enabled in this redraw? Set on construction, updated by the UI (which + * gets a pointer to it). The UI needs it as char. */ + char catalog_path_enabled_ = false; + + public: + Item(asset_system::AssetCatalogTreeItem &catalog_item, AssetShelfSettings &shelf_settings) + : ui::BasicTreeViewItem(catalog_item.get_name()), + catalog_item_(catalog_item), + catalog_path_enabled_(asset_shelf_settings_is_catalog_path_enabled( + shelf_settings, catalog_item.catalog_path())) + { + } + + bool is_catalog_path_enabled() const + { + return catalog_path_enabled_ != 0; + } + + asset_system::AssetCatalogPath catalog_path() const + { + return catalog_item_.catalog_path(); + } + + void build_row(uiLayout &row) override + { + AssetCatalogSelectorTree &tree = dynamic_cast(get_tree_view()); + uiBlock *block = uiLayoutGetBlock(&row); + + uiLayoutSetEmboss(&row, UI_EMBOSS); + + if (!is_collapsible()) { + uiItemL(&row, nullptr, ICON_BLANK1); + } + + uiBut *but = uiDefButC(block, + UI_BTYPE_CHECKBOX, + 0, + catalog_item_.get_name().c_str(), + 0, + 0, + UI_UNIT_X * 10, + UI_UNIT_Y, + (char *)&catalog_path_enabled_, + 0, + 0, + 0, + 0, + TIP_("Toggle catalog visibility in the asset shelf")); + UI_but_func_set(but, [&tree](bContext &C) { + tree.update_shelf_settings_from_enabled_catalogs(); + asset_shelf_send_redraw_notifier(C); + }); + UI_but_flag_disable(but, UI_BUT_UNDO); + } + }; +}; + +void AssetCatalogSelectorTree::update_shelf_settings_from_enabled_catalogs() +{ + asset_shelf_settings_clear_enabled_catalogs(shelf_settings_); + foreach_item([this](ui::AbstractTreeViewItem &view_item) { + const auto &selector_tree_item = dynamic_cast(view_item); + if (selector_tree_item.is_catalog_path_enabled()) { + asset_shelf_settings_set_catalog_path_enabled(shelf_settings_, + selector_tree_item.catalog_path()); + } + }); +} + +static uiBlock *asset_shelf_catalog_selector_block_draw(bContext *C, + ARegion *region, + void * /*arg1*/) +{ + const AssetLibraryReference *library_ref = CTX_wm_asset_library_ref(C); + asset_system::AssetLibrary *library = ED_assetlist_library_get_once_available(*library_ref); + AssetShelfSettings *shelf_settings = get_asset_shelf_settings_from_context(C); + + uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS); + UI_block_flag_enable(block, + UI_BLOCK_KEEP_OPEN | UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPUP_CAN_REFRESH); + + uiLayout *layout = UI_block_layout(block, + UI_LAYOUT_VERTICAL, + UI_LAYOUT_PANEL, + 0, + 0, + UI_UNIT_X * 12, + UI_UNIT_Y, + 0, + UI_style_get()); + + uiItemL(layout, "Enable Catalogs", ICON_NONE); + uiItemS(layout); + + uiLayoutSetEmboss(layout, UI_EMBOSS_NONE); + if (library && shelf_settings) { + ui::AbstractTreeView *tree_view = UI_block_add_view( + *block, + "asset catalog tree view", + std::make_unique(*library, *shelf_settings)); + + ui::TreeViewBuilder builder(*block); + builder.build_tree_view(*tree_view); + } + + UI_block_bounds_set_normal(block, 0.3f * U.widget_unit); + UI_block_direction_set(block, UI_DIR_UP); + + return block; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Catalog toggle buttons + * \{ */ + +static void add_catalog_toggle_buttons(AssetShelfSettings &shelf_settings, uiLayout &layout) +{ + uiBlock *block = uiLayoutGetBlock(&layout); + const uiStyle *style = UI_style_get_dpi(); + + asset_shelf_settings_foreach_enabled_catalog_path( + shelf_settings, [&shelf_settings, block, style](const asset_system::AssetCatalogPath &path) { + const char *name = path.name().c_str(); + const int string_width = UI_fontstyle_string_width(&style->widget, name); + const int pad_x = UI_UNIT_X * 0.3f; + const int but_width = std::min(string_width + 2 * pad_x, UI_UNIT_X * 8); + + uiBut *but = uiDefBut( + block, + UI_BTYPE_TAB, + 0, + name, + 0, + 0, + but_width, + UI_UNIT_Y, + nullptr, + 0, + 0, + 0, + 0, + "Enable catalog, making contained assets visible in the asset shelf"); + + UI_but_drawflag_enable(but, UI_BUT_ALIGN_TOP); + UI_but_func_set(but, [&shelf_settings, path](bContext &C) { + asset_shelf_settings_set_active_catalog(shelf_settings, path); + asset_shelf_send_redraw_notifier(C); + }); + UI_but_func_pushed_state_set(but, [&shelf_settings, path](const uiBut &) -> bool { + return asset_shelf_settings_is_active_catalog(shelf_settings, path); + }); + }); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Asset Shelf Footer + * + * Implemented as HeaderType for #RGN_TYPE_ASSET_SHELF_FOOTER. + * \{ */ + +static void asset_shelf_footer_draw(const bContext *C, Header *header) +{ + uiLayout *layout = header->layout; + uiBlock *block = uiLayoutGetBlock(layout); + const AssetLibraryReference *library_ref = CTX_wm_asset_library_ref(C); + + ED_assetlist_storage_fetch(library_ref, C); + uiDefIconBlockBut(block, + asset_shelf_catalog_selector_block_draw, + nullptr, + 0, + ICON_RIGHTARROW, + 0, + 0, + UI_UNIT_X * 1.5f, + UI_UNIT_Y, + TIP_("Select catalogs to display")); + + uiItemS(layout); + + AssetShelfSettings *shelf_settings = get_asset_shelf_settings_from_context(C); + if (shelf_settings) { + add_catalog_toggle_buttons(*shelf_settings, *layout); + } +} + +void ED_asset_shelf_footer_register(ARegionType *region_type, + const char *idname, + const int space_type) +{ + HeaderType *ht = MEM_cnew(__func__); + strcpy(ht->idname, idname); + ht->space_type = space_type; + ht->region_type = RGN_TYPE_ASSET_SHELF_FOOTER; + ht->draw = asset_shelf_footer_draw; + ht->poll = asset_shelf_region_header_type_poll; + BLI_addtail(®ion_type->headertypes, ht); +} + +/** \} */ diff --git a/source/blender/editors/include/ED_screen.h b/source/blender/editors/include/ED_screen.h index 6dfaab663f6..2ad8e68c05d 100644 --- a/source/blender/editors/include/ED_screen.h +++ b/source/blender/editors/include/ED_screen.h @@ -701,6 +701,7 @@ enum { ED_KEYMAP_FOOTER = (1 << 9), ED_KEYMAP_GPENCIL = (1 << 10), ED_KEYMAP_NAVBAR = (1 << 11), + ED_KEYMAP_ASSET_SHELF = (1 << 12), }; /** #SCREEN_OT_space_context_cycle direction. */ diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index 0ad39f8c05f..e558d29e082 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -168,6 +168,7 @@ enum { UI_BLOCK_SEARCH_ONLY = 1 << 25, /** Hack for quick setup (splash screen) to draw text centered. */ UI_BLOCK_QUICK_SETUP = 1 << 26, + UI_BLOCK_POPUP_CAN_REFRESH = 1 << 27, }; /** #uiPopupBlockHandle.menuretval */ @@ -1755,8 +1756,6 @@ void UI_but_focus_on_enter_event(struct wmWindow *win, uiBut *but); void UI_but_func_hold_set(uiBut *but, uiButHandleHoldFunc func, void *argN); -void UI_but_func_pushed_state_set(uiBut *but, uiButPushedStateFunc func, const void *arg); - struct PointerRNA *UI_but_extra_operator_icon_add(uiBut *but, const char *opname, wmOperatorCallContext opcontext, @@ -2179,6 +2178,7 @@ int uiLayoutGetAlignment(uiLayout *layout); bool uiLayoutGetFixedSize(uiLayout *layout); bool uiLayoutGetKeepAspect(uiLayout *layout); int uiLayoutGetWidth(uiLayout *layout); +int uiLayoutGetRootHeight(uiLayout *layout); float uiLayoutGetScaleX(uiLayout *layout); float uiLayoutGetScaleY(uiLayout *layout); float uiLayoutGetUnitsX(uiLayout *layout); @@ -2616,6 +2616,9 @@ void uiTemplateAssetView(struct uiLayout *layout, struct PointerRNA *r_activate_op_properties, const char *drag_opname, struct PointerRNA *r_drag_op_properties); +void uiTemplateAssetShelf(uiLayout *layout, + const struct bContext *C, + const struct AssetFilterSettings *filter_settings); /** * \return: A RNA pointer for the operator properties. diff --git a/source/blender/editors/include/UI_interface.hh b/source/blender/editors/include/UI_interface.hh index 147b48b454e..d555a835c69 100644 --- a/source/blender/editors/include/UI_interface.hh +++ b/source/blender/editors/include/UI_interface.hh @@ -18,10 +18,10 @@ namespace blender::nodes::geo_eval_log { struct GeometryAttributeInfo; } -struct PointerRNA; +struct bContext; struct StructRNA; struct uiBlock; -struct uiList; +struct uiBut; struct uiSearchItems; namespace blender::ui { @@ -110,6 +110,9 @@ void UI_list_filter_and_sort_items(uiList *ui_list, const char *propname, uiListItemGetNameFn get_name_fn = nullptr); +void UI_but_func_set(uiBut *but, std::function func); +void UI_but_func_pushed_state_set(uiBut *but, std::function func); + /** * Override this for all available view types. */ diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt index 5ecf4c59ed8..3d6b6cb51fe 100644 --- a/source/blender/editors/interface/CMakeLists.txt +++ b/source/blender/editors/interface/CMakeLists.txt @@ -64,6 +64,7 @@ set(SRC interface_region_tooltip.cc interface_regions.cc interface_style.cc + interface_template_asset_shelf.cc interface_template_asset_view.cc interface_template_attribute_search.cc interface_template_list.cc diff --git a/source/blender/editors/interface/interface.cc b/source/blender/editors/interface/interface.cc index 6f593c74fb9..75e81dca070 100644 --- a/source/blender/editors/interface/interface.cc +++ b/source/blender/editors/interface/interface.cc @@ -51,6 +51,7 @@ #include "BLT_translation.h" #include "UI_interface.h" +#include "UI_interface.hh" #include "UI_interface_icons.h" #include "UI_view2d.h" @@ -749,6 +750,10 @@ static bool ui_but_equals_old(const uiBut *but, const uiBut *oldbut) if (but->func != oldbut->func) { return false; } + if (but->apply_func.target() != + oldbut->apply_func.target()) { + return false; + } if (but->funcN != oldbut->funcN) { return false; } @@ -2181,7 +2186,7 @@ int ui_but_is_pushed_ex(uiBut *but, double *value) { int is_push = 0; if (but->pushed_state_func) { - return but->pushed_state_func(but, but->pushed_state_arg); + return but->pushed_state_func(*but); } if (but->bit) { @@ -6028,6 +6033,11 @@ void UI_but_func_set(uiBut *but, uiButHandleFunc func, void *arg1, void *arg2) but->func_arg2 = arg2; } +void UI_but_func_set(uiBut *but, std::function func) +{ + but->apply_func = func; +} + void UI_but_funcN_set(uiBut *but, uiButHandleNFunc funcN, void *argN, void *arg2) { if (but->func_argN) { @@ -6060,10 +6070,9 @@ void UI_but_func_tooltip_set(uiBut *but, uiButToolTipFunc func, void *arg, uiFre but->tip_arg_free = free_arg; } -void UI_but_func_pushed_state_set(uiBut *but, uiButPushedStateFunc func, const void *arg) +void UI_but_func_pushed_state_set(uiBut *but, std::function func) { but->pushed_state_func = func; - but->pushed_state_arg = arg; ui_but_update(but); } diff --git a/source/blender/editors/interface/interface_handlers.cc b/source/blender/editors/interface/interface_handlers.cc index 05ccda5649c..ac36c041c6d 100644 --- a/source/blender/editors/interface/interface_handlers.cc +++ b/source/blender/editors/interface/interface_handlers.cc @@ -459,45 +459,47 @@ struct uiHandleButtonData { }; struct uiAfterFunc { - uiAfterFunc *next, *prev; + uiAfterFunc *next = nullptr, *prev = nullptr; - uiButHandleFunc func; - void *func_arg1; - void *func_arg2; + uiButHandleFunc func = nullptr; + void *func_arg1 = nullptr; + void *func_arg2 = nullptr; + /** C++ version of #func above, without need for void pointer arguments. */ + std::function apply_func; - uiButHandleNFunc funcN; - void *func_argN; + uiButHandleNFunc funcN = nullptr; + void *func_argN = nullptr; - uiButHandleRenameFunc rename_func; - void *rename_arg1; - void *rename_orig; + uiButHandleRenameFunc rename_func = nullptr; + void *rename_arg1 = nullptr; + void *rename_orig = nullptr; - uiBlockHandleFunc handle_func; - void *handle_func_arg; - int retval; + uiBlockHandleFunc handle_func = nullptr; + void *handle_func_arg = nullptr; + int retval = 0; - uiMenuHandleFunc butm_func; - void *butm_func_arg; - int a2; + uiMenuHandleFunc butm_func = nullptr; + void *butm_func_arg = nullptr; + int a2 = 0; - wmOperator *popup_op; - wmOperatorType *optype; + wmOperator *popup_op = nullptr; + wmOperatorType *optype = nullptr; wmOperatorCallContext opcontext; - PointerRNA *opptr; + PointerRNA *opptr = nullptr; - PointerRNA rnapoin; - PropertyRNA *rnaprop; + PointerRNA rnapoin = {}; + PropertyRNA *rnaprop = nullptr; - void *search_arg; - uiFreeArgFunc search_arg_free_fn; + void *search_arg = nullptr; + uiFreeArgFunc search_arg_free_fn = nullptr; - uiBlockInteraction_CallbackData custom_interaction_callbacks; - uiBlockInteraction_Handle *custom_interaction_handle; + uiBlockInteraction_CallbackData custom_interaction_callbacks = {}; + uiBlockInteraction_Handle *custom_interaction_handle = nullptr; - bContextStore *context; + bContextStore *context = nullptr; - char undostr[BKE_UNDO_STR_MAX]; - char drawstr[UI_MAX_DRAW_STR]; + char undostr[BKE_UNDO_STR_MAX] = ""; + char drawstr[UI_MAX_DRAW_STR] = ""; }; static void button_activate_init(bContext *C, @@ -745,7 +747,7 @@ static ListBase UIAfterFuncs = {nullptr, nullptr}; static uiAfterFunc *ui_afterfunc_new() { - uiAfterFunc *after = MEM_cnew(__func__); + uiAfterFunc *after = MEM_new(__func__); BLI_addtail(&UIAfterFuncs, after); @@ -802,8 +804,9 @@ static void popup_check(bContext *C, wmOperator *op) */ static bool ui_afterfunc_check(const uiBlock *block, const uiBut *but) { - return (but->func || but->funcN || but->rename_func || but->optype || but->rnaprop || - block->handle_func || (but->type == UI_BTYPE_BUT_MENU && block->butm_func) || + return (but->func || but->apply_func || but->funcN || but->rename_func || but->optype || + but->rnaprop || block->handle_func || + (but->type == UI_BTYPE_BUT_MENU && block->butm_func) || (block->handle && block->handle->popup_op)); } @@ -828,10 +831,11 @@ static void ui_apply_but_func(bContext *C, uiBut *but) else { after->func = but->func; } - after->func_arg1 = but->func_arg1; after->func_arg2 = but->func_arg2; + after->apply_func = but->apply_func; + after->funcN = but->funcN; after->func_argN = (but->func_argN) ? MEM_dupallocN(but->func_argN) : nullptr; @@ -1000,7 +1004,8 @@ static void ui_apply_but_funcs_after(bContext *C) LISTBASE_FOREACH_MUTABLE (uiAfterFunc *, afterf, &funcs) { uiAfterFunc after = *afterf; /* Copy to avoid memory leak on exit(). */ - BLI_freelinkN(&funcs, afterf); + BLI_remlink(&funcs, afterf); + MEM_delete(afterf); if (after.context) { CTX_store_set(C, after.context); @@ -1042,6 +1047,9 @@ static void ui_apply_but_funcs_after(bContext *C) if (after.func) { after.func(C, after.func_arg1, after.func_arg2); } + if (after.apply_func) { + after.apply_func(*C); + } if (after.funcN) { after.funcN(C, after.func_argN, after.func_arg2); } @@ -2089,7 +2097,8 @@ static bool ui_but_drag_init(bContext *C, RGN_TYPE_NAV_BAR, RGN_TYPE_HEADER, RGN_TYPE_TOOL_HEADER, - RGN_TYPE_FOOTER)) { + RGN_TYPE_FOOTER, + RGN_TYPE_ASSET_SHELF_FOOTER)) { const int region_alignment = RGN_ALIGN_ENUM_FROM_MASK(data->region->alignment); int lock_axis = -1; diff --git a/source/blender/editors/interface/interface_intern.hh b/source/blender/editors/interface/interface_intern.hh index 830cc977d49..714fcf12019 100644 --- a/source/blender/editors/interface/interface_intern.hh +++ b/source/blender/editors/interface/interface_intern.hh @@ -7,6 +7,8 @@ #pragma once +#include + #include "BLI_compiler_attrs.h" #include "BLI_rect.h" #include "BLI_vector.hh" @@ -190,6 +192,9 @@ struct uiBut { uiButHandleFunc func = nullptr; void *func_arg1 = nullptr; void *func_arg2 = nullptr; + /** C++ version of #func above. Allows storing arbitrary data in a type safe way, no void + * pointer arguments.*/ + std::function apply_func; uiButHandleNFunc funcN = nullptr; void *func_argN = nullptr; @@ -266,8 +271,7 @@ struct uiBut { double *editval = nullptr; float *editvec = nullptr; - uiButPushedStateFunc pushed_state_func = nullptr; - const void *pushed_state_arg = nullptr; + std::function pushed_state_func; /** Little indicator (e.g., counter) displayed on top of some icons. */ IconTextOverlay icon_overlay_text = {}; diff --git a/source/blender/editors/interface/interface_layout.cc b/source/blender/editors/interface/interface_layout.cc index 1cab840f100..468660584b7 100644 --- a/source/blender/editors/interface/interface_layout.cc +++ b/source/blender/editors/interface/interface_layout.cc @@ -4087,7 +4087,7 @@ static void ui_litem_layout_box(uiLayout *litem) const uiStyle *style = litem->root->style; int boxspace = style->boxspace; - if (litem->root->type == UI_LAYOUT_HEADER) { + if (litem->root->type == UI_LAYOUT_HEADER && false) { boxspace = 0; } @@ -5114,6 +5114,11 @@ int uiLayoutGetWidth(uiLayout *layout) return layout->w; } +int uiLayoutGetRootHeight(uiLayout *layout) +{ + return layout->root->layout->h; +} + float uiLayoutGetScaleX(uiLayout *layout) { return layout->scale[0]; diff --git a/source/blender/editors/interface/interface_region_popup.cc b/source/blender/editors/interface/interface_region_popup.cc index edc6d94c250..6e85c8a6b6a 100644 --- a/source/blender/editors/interface/interface_region_popup.cc +++ b/source/blender/editors/interface/interface_region_popup.cc @@ -593,6 +593,11 @@ uiBlock *ui_popup_block_refresh(bContext *C, block->flag |= UI_BLOCK_LOOP; UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); + /* TODO does this flag need to be checked in more cases? */ + if (block->flag & UI_BLOCK_POPUP_CAN_REFRESH) { + handle->can_refresh = true; + } + /* defer this until blocks are translated (below) */ block->oldblock = nullptr; diff --git a/source/blender/editors/interface/interface_template_asset_shelf.cc b/source/blender/editors/interface/interface_template_asset_shelf.cc new file mode 100644 index 00000000000..03fab3e9c21 --- /dev/null +++ b/source/blender/editors/interface/interface_template_asset_shelf.cc @@ -0,0 +1,188 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edinterface + */ + +#include "AS_asset_library.hh" + +#include "BKE_context.h" +#include "BKE_screen.h" + +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "ED_asset.h" + +#include "RNA_access.h" + +#include "UI_interface.h" +#include "UI_resources.h" +#include "interface_intern.hh" + +#include "RNA_prototypes.h" + +using namespace blender; + +/* TODO copy of #asset_view_item_but_drag_set(). */ +static void asset_tile_but_drag_set(uiBut &but, AssetHandle &asset_handle) +{ + ID *id = ED_asset_handle_get_local_id(&asset_handle); + if (id != nullptr) { + UI_but_drag_set_id(&but, id); + return; + } + + 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); + + if (blend_path[0]) { + ImBuf *imbuf = ED_assetlist_asset_image_get(&asset_handle); + UI_but_drag_set_asset(&but, + &asset_handle, + BLI_strdup(blend_path), + FILE_ASSET_IMPORT_APPEND, + ED_asset_handle_get_preview_icon_id(&asset_handle), + imbuf, + 1.0f); + } +} + +static void asset_tile_draw(uiLayout &layout, + AssetHandle &asset_handle, + const int width, + const int height, + const bool show_names) +{ + PointerRNA file_ptr; + RNA_pointer_create( + nullptr, + &RNA_FileSelectEntry, + /* XXX passing file pointer here, should be asset handle or asset representation. */ + const_cast(asset_handle.file_data), + &file_ptr); + + uiLayoutSetContextPointer(&layout, "active_file", &file_ptr); + + uiBlock *block = uiLayoutGetBlock(&layout); + const StringRefNull name = ED_asset_handle_get_name(&asset_handle); + + uiBut *but = uiDefIconTextBut(block, + UI_BTYPE_PREVIEW_TILE, + 0, + ED_asset_handle_get_preview_icon_id(&asset_handle), + show_names ? name.c_str() : "", + 0, + 0, + width, + height, + nullptr, + 0, + 0, + 0, + 0, + name.c_str()); + ui_def_but_icon(but, + ED_asset_handle_get_preview_icon_id(&asset_handle), + /* NOLINTNEXTLINE: bugprone-suspicious-enum-usage */ + UI_HAS_ICON | UI_BUT_ICON_PREVIEW); + asset_tile_but_drag_set(*but, asset_handle); +} + +static std::optional catalog_filter_from_shelf_settings( + const AssetShelfSettings *shelf_settings, const asset_system::AssetLibrary *library) +{ + if (!shelf_settings || !shelf_settings->active_catalog_path) { + return {}; + } + + asset_system ::AssetCatalog *active_catalog = library->catalog_service->find_catalog_by_path( + shelf_settings->active_catalog_path); + if (!active_catalog) { + return {}; + } + + return library->catalog_service->create_catalog_filter(active_catalog->catalog_id); +} + +/* TODO calling a (.py defined) callback for every asset isn't exactly great. Should be a temporary + * solution until there is proper filtering by asset traits. */ +/** + * Returns true if the asset should be visible. That is, if any of the visible asset shelves has no + * poll function (all assets should be displayed), or its #AssetShelfType.asset_poll function + * returns true. + */ +static bool asset_shelf_asset_poll(const SpaceType &space_type, + const bContext &C, + const AssetHandle &asset) +{ + LISTBASE_FOREACH (AssetShelfType *, shelf_type, &space_type.asset_shelf_types) { + if (!shelf_type->poll || !shelf_type->poll(&C, shelf_type)) { + continue; + } + if (!shelf_type->asset_poll || shelf_type->asset_poll(shelf_type, &asset)) { + return true; + } + } + + return false; +} + +void uiTemplateAssetShelf(uiLayout *layout, + const bContext *C, + const AssetFilterSettings *filter_settings) +{ + const AssetLibraryReference *library_ref = CTX_wm_asset_library_ref(C); + const PointerRNA shelf_settings_ptr = CTX_data_pointer_get_type( + C, "asset_shelf_settings", &RNA_AssetShelfSettings); + const AssetShelfSettings *shelf_settings = static_cast( + shelf_settings_ptr.data); + + Vector asset_polls; + + ED_assetlist_storage_fetch(library_ref, C); + ED_assetlist_ensure_previews_job(library_ref, C); + + const asset_system::AssetLibrary *library = ED_assetlist_library_get_once_available( + *library_ref); + if (!library) { + return; + } + + std::optional catalog_filter = + catalog_filter_from_shelf_settings(shelf_settings, library); + + uiLayoutSetScaleX(layout, 1.0f); + uiLayoutSetScaleY(layout, 1.0f); + + const bool show_names = true; + const int height = uiLayoutGetRootHeight(layout) - UI_style_get_dpi()->boxspace * 2; + /* Width is derived from the height. It's the height without the space for the name (if there is + * any). */ + const int width = height - (show_names ? 0 : UI_UNIT_Y); + + uiLayout *box = uiLayoutBox(layout); + uiLayout *row = uiLayoutRow(box, false); + const SpaceLink *space_link = CTX_wm_space_data(C); + const SpaceType *space_type = BKE_spacetype_from_id(space_link->spacetype); + + ED_assetlist_iterate(*library_ref, [&](AssetHandle asset) { + if (!asset_shelf_asset_poll(*space_type, *C, asset)) { + return true; + } + if (!ED_asset_filter_matches_asset(filter_settings, &asset)) { + /* Don't do anything else, but return true to continue iterating. */ + return true; + } + /* Filter by active catalog. */ + const AssetMetaData *asset_data = ED_asset_handle_get_metadata(&asset); + if (catalog_filter && !catalog_filter->contains(asset_data->catalog_id)) { + return true; + } + + asset_tile_draw(*row, asset, width, height, show_names); + return true; + }); +} diff --git a/source/blender/editors/interface/interface_template_asset_view.cc b/source/blender/editors/interface/interface_template_asset_view.cc index 71bd66ff651..91ac4721552 100644 --- a/source/blender/editors/interface/interface_template_asset_view.cc +++ b/source/blender/editors/interface/interface_template_asset_view.cc @@ -146,7 +146,6 @@ static void asset_view_filter_items(uiList *ui_list, static void asset_view_listener(uiList *ui_list, wmRegionListenerParams *params) { - AssetViewListData *list_data = (AssetViewListData *)ui_list->dyn_data->customdata; const wmNotifier *notifier = params->notifier; switch (notifier->category) { @@ -158,7 +157,7 @@ static void asset_view_listener(uiList *ui_list, wmRegionListenerParams *params) } } - if (ED_assetlist_listen(&list_data->asset_library_ref, params->notifier)) { + if (ED_assetlist_listen(params->notifier)) { ED_region_tag_redraw(params->region); } } diff --git a/source/blender/editors/interface/resources.cc b/source/blender/editors/interface/resources.cc index 6a2e2ff9073..0199519ca14 100644 --- a/source/blender/editors/interface/resources.cc +++ b/source/blender/editors/interface/resources.cc @@ -153,7 +153,10 @@ const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid) else if (g_theme_state.regionid == RGN_TYPE_CHANNELS) { cp = ts->list; } - else if (ELEM(g_theme_state.regionid, RGN_TYPE_HEADER, RGN_TYPE_FOOTER)) { + else if (ELEM(g_theme_state.regionid, + RGN_TYPE_HEADER, + RGN_TYPE_FOOTER, + RGN_TYPE_ASSET_SHELF_FOOTER)) { cp = ts->header; } else if (g_theme_state.regionid == RGN_TYPE_NAV_BAR) { @@ -187,7 +190,10 @@ const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid) else if (g_theme_state.regionid == RGN_TYPE_CHANNELS) { cp = ts->list_text; } - else if (ELEM(g_theme_state.regionid, RGN_TYPE_HEADER, RGN_TYPE_FOOTER)) { + else if (ELEM(g_theme_state.regionid, + RGN_TYPE_HEADER, + RGN_TYPE_FOOTER, + RGN_TYPE_ASSET_SHELF_FOOTER)) { cp = ts->header_text; } else { @@ -201,7 +207,10 @@ const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid) else if (g_theme_state.regionid == RGN_TYPE_CHANNELS) { cp = ts->list_text_hi; } - else if (ELEM(g_theme_state.regionid, RGN_TYPE_HEADER, RGN_TYPE_FOOTER)) { + else if (ELEM(g_theme_state.regionid, + RGN_TYPE_HEADER, + RGN_TYPE_FOOTER, + RGN_TYPE_ASSET_SHELF_FOOTER)) { cp = ts->header_text_hi; } else { @@ -215,7 +224,10 @@ const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid) else if (g_theme_state.regionid == RGN_TYPE_CHANNELS) { cp = ts->list_title; } - else if (ELEM(g_theme_state.regionid, RGN_TYPE_HEADER, RGN_TYPE_FOOTER)) { + else if (ELEM(g_theme_state.regionid, + RGN_TYPE_HEADER, + RGN_TYPE_FOOTER, + RGN_TYPE_ASSET_SHELF_FOOTER)) { cp = ts->header_title; } else { diff --git a/source/blender/editors/interface/views/tree_view.cc b/source/blender/editors/interface/views/tree_view.cc index f3acfe75246..cd16fa9166c 100644 --- a/source/blender/editors/interface/views/tree_view.cc +++ b/source/blender/editors/interface/views/tree_view.cc @@ -167,7 +167,7 @@ void AbstractTreeViewItem::collapse_chevron_click_fn(struct bContext *C, * lookup the hovered item via context here. */ const wmWindow *win = CTX_wm_window(C); - const ARegion *region = CTX_wm_region(C); + const ARegion *region = CTX_wm_menu(C) ? CTX_wm_menu(C) : CTX_wm_region(C); uiViewItemHandle *hovered_item_handle = UI_region_views_find_item_at(region, win->eventstate->xy); diff --git a/source/blender/editors/screen/CMakeLists.txt b/source/blender/editors/screen/CMakeLists.txt index 77ea72357a8..caa4a93cd76 100644 --- a/source/blender/editors/screen/CMakeLists.txt +++ b/source/blender/editors/screen/CMakeLists.txt @@ -2,6 +2,7 @@ set(INC ../include + ../../asset_system ../../blenfont ../../blenkernel ../../blenlib diff --git a/source/blender/editors/screen/area.cc b/source/blender/editors/screen/area.cc index bfbea031fdd..83bd8fe74c7 100644 --- a/source/blender/editors/screen/area.cc +++ b/source/blender/editors/screen/area.cc @@ -33,6 +33,7 @@ #include "WM_toolsystem.h" #include "WM_types.h" +#include "ED_asset.h" #include "ED_buttons.h" #include "ED_screen.h" #include "ED_screen_types.h" @@ -931,9 +932,14 @@ static void fullscreen_azone_init(ScrArea *area, ARegion *region) #define AZONEPAD_ICON (0.45f * U.widget_unit) static void region_azone_edge(AZone *az, ARegion *region) { - /* If region is overlapped (transparent background), move #AZone to content. - * Note this is an arbitrary amount that matches nicely with numbers elsewhere. */ - int overlap_padding = (region->overlap) ? int(0.4f * U.widget_unit) : 0; + /* If there is no visible region background, users typically expect the #AZone to be closer to + * the content, so move it a bit. Headers-like regions are usually thin and there's not much + * padding around them, so don't touch the #AZone there (also avoids mouse hover conflicts with + * actual contents). + * Note that this is an arbitrary amount that matches nicely with numbers elsewhere. */ + const int overlap_padding = (region->overlap && !RGN_TYPE_IS_HEADER_ANY(region->regiontype)) ? + int(0.4f * U.widget_unit) : + 0; switch (az->edge) { case AE_TOP_TO_BOTTOMRIGHT: @@ -1243,7 +1249,9 @@ bool ED_region_is_overlap(int spacetype, int regiontype) RGN_TYPE_UI, RGN_TYPE_TOOL_PROPS, RGN_TYPE_FOOTER, - RGN_TYPE_TOOL_HEADER)) { + RGN_TYPE_TOOL_HEADER, + RGN_TYPE_ASSET_SHELF, + RGN_TYPE_ASSET_SHELF_FOOTER)) { return true; } } @@ -1318,6 +1326,9 @@ static void region_rect_recursive( else if (region->regiontype == RGN_TYPE_FOOTER) { prefsizey = ED_area_footersize(); } + else if (region->regiontype == RGN_TYPE_ASSET_SHELF_FOOTER) { + prefsizey = ED_area_footersize(); + } else if (ED_area_is_global(area)) { prefsizey = ED_region_global_size_y(); } @@ -1728,6 +1739,11 @@ static void ed_default_handlers( wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "Region Context Menu", 0, 0); WM_event_add_keymap_handler(®ion->handlers, keymap); } + if (flag & ED_KEYMAP_ASSET_SHELF) { + /* standard keymap for asset shelf regions */ + wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "Asset Shelf", 0, 0); + WM_event_add_keymap_handler(®ion->handlers, keymap); + } /* Keep last because of LMB/RMB handling, see: #57527. */ if (flag & ED_KEYMAP_GPENCIL) { @@ -3289,12 +3305,15 @@ void ED_region_header_layout(const bContext *C, ARegion *region) bool region_layout_based = region->flag & RGN_FLAG_DYNAMIC_SIZE; /* Height of buttons and scaling needed to achieve it. */ - const int buttony = min_ii(UI_UNIT_Y, region->winy - 2 * UI_SCALE_FAC); + const bool is_fixed_header_height = region->type->prefsizey == HEADERY; + const int buttony = is_fixed_header_height ? UI_UNIT_Y : + region->winy - 2 * UI_SCALE_FAC - UI_HEADER_OFFSET; const float buttony_scale = buttony / float(UI_UNIT_Y); /* Vertically center buttons. */ int xco = UI_HEADER_OFFSET; - int yco = buttony + (region->winy - buttony) / 2; + int yco = is_fixed_header_height ? buttony + (region->winy - buttony) / 2 : + buttony + UI_HEADER_OFFSET / 2; int maxco = xco; /* XXX workaround for 1 px alignment issue. Not sure what causes it... @@ -3399,7 +3418,6 @@ void ED_region_header_init(ARegion *region) { UI_view2d_region_reinit(®ion->v2d, V2D_COMMONVIEW_HEADER, region->winx, region->winy); } - int ED_area_headersize(void) { /* Accommodate widget and padding. */ diff --git a/source/blender/editors/screen/screen_edit.c b/source/blender/editors/screen/screen_edit.c index e0969b444eb..8b85aad3239 100644 --- a/source/blender/editors/screen/screen_edit.c +++ b/source/blender/editors/screen/screen_edit.c @@ -1435,7 +1435,9 @@ static bScreen *screen_state_to_nonnormal(bContext *C, RGN_TYPE_FOOTER, RGN_TYPE_TOOLS, RGN_TYPE_NAV_BAR, - RGN_TYPE_EXECUTE)) { + RGN_TYPE_EXECUTE, + RGN_TYPE_ASSET_SHELF, + RGN_TYPE_ASSET_SHELF_FOOTER)) { region->flag |= RGN_FLAG_HIDDEN; } } diff --git a/source/blender/editors/screen/screen_ops.c b/source/blender/editors/screen/screen_ops.c index ca1f190001b..7146f2b5de8 100644 --- a/source/blender/editors/screen/screen_ops.c +++ b/source/blender/editors/screen/screen_ops.c @@ -2625,15 +2625,19 @@ static int area_max_regionsize(ScrArea *area, ARegion *scale_region, AZEdge edge dist -= region->winx; } else if (scale_region->alignment == RGN_ALIGN_TOP && - (region->alignment == RGN_ALIGN_BOTTOM || - ELEM( - region->regiontype, RGN_TYPE_HEADER, RGN_TYPE_TOOL_HEADER, RGN_TYPE_FOOTER))) { + (region->alignment == RGN_ALIGN_BOTTOM || ELEM(region->regiontype, + RGN_TYPE_HEADER, + RGN_TYPE_TOOL_HEADER, + RGN_TYPE_FOOTER, + RGN_TYPE_ASSET_SHELF_FOOTER))) { dist -= region->winy; } else if (scale_region->alignment == RGN_ALIGN_BOTTOM && - (region->alignment == RGN_ALIGN_TOP || - ELEM( - region->regiontype, RGN_TYPE_HEADER, RGN_TYPE_TOOL_HEADER, RGN_TYPE_FOOTER))) { + (region->alignment == RGN_ALIGN_TOP || ELEM(region->regiontype, + RGN_TYPE_HEADER, + RGN_TYPE_TOOL_HEADER, + RGN_TYPE_FOOTER, + RGN_TYPE_ASSET_SHELF_FOOTER))) { dist -= region->winy; } } diff --git a/source/blender/editors/space_file/filelist.cc b/source/blender/editors/space_file/filelist.cc index f27238f4fe3..1ad82ec6378 100644 --- a/source/blender/editors/space_file/filelist.cc +++ b/source/blender/editors/space_file/filelist.cc @@ -864,6 +864,7 @@ static bool is_filtered_lib_type(FileListInternEntry *file, if (file->typeflag & FILE_TYPE_BLENDERLIB) { return is_filtered_id_file_type(file, file->blentype, file->name, filter); } + return is_filtered_file_type(file, filter); } diff --git a/source/blender/editors/space_view3d/CMakeLists.txt b/source/blender/editors/space_view3d/CMakeLists.txt index 691bba4c02d..c940b188c27 100644 --- a/source/blender/editors/space_view3d/CMakeLists.txt +++ b/source/blender/editors/space_view3d/CMakeLists.txt @@ -2,6 +2,7 @@ set(INC ../include + ../asset ../../blenfont ../../blenkernel ../../blenlib diff --git a/source/blender/editors/space_view3d/space_view3d.cc b/source/blender/editors/space_view3d/space_view3d.cc index 5f5ba3aab2d..e1b8ccb682c 100644 --- a/source/blender/editors/space_view3d/space_view3d.cc +++ b/source/blender/editors/space_view3d/space_view3d.cc @@ -48,6 +48,7 @@ #include "BKE_viewer_path.h" #include "BKE_workspace.h" +#include "ED_asset_shelf.h" #include "ED_object.h" #include "ED_outliner.h" #include "ED_render.h" @@ -290,6 +291,21 @@ static SpaceLink *view3d_create(const ScrArea * /*area*/, const Scene *scene) region->alignment = RGN_ALIGN_RIGHT; region->flag = RGN_FLAG_HIDDEN; + /* asset shelf footer */ + region = MEM_cnew("asset shelf footer for view3d"); + BLI_addtail(&v3d->regionbase, region); + region->regiontype = RGN_TYPE_ASSET_SHELF_FOOTER; + region->alignment = RGN_ALIGN_BOTTOM; + region->flag = RGN_FLAG_HIDDEN; + + /* asset shelf */ + region = MEM_cnew("asset shelf for view3d"); + + BLI_addtail(&v3d->regionbase, region); + region->regiontype = RGN_TYPE_ASSET_SHELF; + region->alignment = RGN_ALIGN_BOTTOM | RGN_SPLIT_PREV; + region->flag |= RGN_FLAG_DYNAMIC_SIZE; + /* main region */ region = MEM_cnew("main region for view3d"); @@ -327,11 +343,21 @@ static void view3d_free(SpaceLink *sl) } BKE_viewer_path_clear(&vd->viewer_path); + + if (vd->asset_shelf) { + ED_asset_shelf_settings_free(vd->asset_shelf); + MEM_SAFE_FREE(vd->asset_shelf); + } } /* spacetype; init callback */ -static void view3d_init(wmWindowManager * /*wm*/, ScrArea * /*area*/) +static void view3d_init(wmWindowManager * /*wm*/, ScrArea *area) { + BLI_assert(area->spacetype == SPACE_VIEW3D); + View3D *v3d = static_cast(area->spacedata.first); + if (!v3d->asset_shelf) { + v3d->asset_shelf = MEM_cnew("AssetShelfSettings"); + } } static void view3d_exit(wmWindowManager * /*wm*/, ScrArea *area) @@ -367,6 +393,8 @@ static SpaceLink *view3d_duplicate(SpaceLink *sl) BKE_viewer_path_copy(&v3dn->viewer_path, &v3do->viewer_path); + v3dn->asset_shelf = ED_asset_shelf_settings_duplicate(v3do->asset_shelf); + /* copy or clear inside new stuff */ return (SpaceLink *)v3dn; @@ -2003,6 +2031,14 @@ static int view3d_context(const bContext *C, const char *member, bContextDataRes return CTX_RESULT_MEMBER_NOT_FOUND; } +static int view3d_asset_shelf_context(const bContext *C, + const char *member, + bContextDataResult *result) +{ + View3D *v3d = CTX_wm_view3d(C); + return ED_asset_shelf_context(C, member, result, v3d->asset_shelf); +} + static void view3d_id_remap_v3d_ob_centers(View3D *v3d, const struct IDRemapper *mappings) { if (BKE_id_remapper_apply(mappings, (ID **)&v3d->ob_center, ID_REMAP_APPLY_DEFAULT) == @@ -2078,6 +2114,8 @@ static void view3d_blend_read_data(BlendDataReader *reader, SpaceLink *sl) BKE_screen_view3d_do_versions_250(v3d, &sl->regionbase); BKE_viewer_path_blend_read_data(reader, &v3d->viewer_path); + + ED_asset_shelf_settings_blend_read_data(reader, &v3d->asset_shelf); } static void view3d_blend_read_lib(BlendLibReader *reader, ID *parent_id, SpaceLink *sl) @@ -2106,6 +2144,8 @@ static void view3d_blend_write(BlendWriter *writer, SpaceLink *sl) BKE_screen_view3d_shading_blend_write(writer, &v3d->shading); BKE_viewer_path_blend_write(writer, &v3d->viewer_path); + + ED_asset_shelf_settings_blend_write(writer, v3d->asset_shelf); } void ED_spacetype_view3d() @@ -2197,6 +2237,31 @@ void ED_spacetype_view3d() art->draw = view3d_header_region_draw; BLI_addhead(&st->regiontypes, art); + /* regions: asset shelf */ + art = MEM_cnew("spacetype view3d asset shelf region"); + art->regionid = RGN_TYPE_ASSET_SHELF; + art->prefsizey = HEADERY * 3.5f; + art->keymapflag = ED_KEYMAP_UI | ED_KEYMAP_ASSET_SHELF | ED_KEYMAP_VIEW2D | ED_KEYMAP_FRAMES | + ED_KEYMAP_HEADER; + art->listener = ED_asset_shelf_region_listen; + art->context = view3d_asset_shelf_context; + art->init = view3d_header_region_init; + art->draw = ED_asset_shelf_region_draw; + BLI_addhead(&st->regiontypes, art); + ED_asset_shelf_region_register(art, "VIEW3D_HT_asset_shelf_main", SPACE_VIEW3D); + + /* regions: asset shelf footer */ + art = MEM_cnew("spacetype view3d asset shelf footer region"); + art->regionid = RGN_TYPE_ASSET_SHELF_FOOTER; + art->prefsizey = HEADERY; + art->keymapflag = ED_KEYMAP_UI | ED_KEYMAP_ASSET_SHELF | ED_KEYMAP_VIEW2D | ED_KEYMAP_FOOTER; + art->init = ED_asset_shelf_footer_region_init; + art->draw = ED_asset_shelf_footer_region; + art->listener = ED_asset_shelf_footer_region_listen; + art->context = view3d_asset_shelf_context; + BLI_addhead(&st->regiontypes, art); + ED_asset_shelf_footer_register(art, "VIEW3D_HT_asset_shelf_footer", SPACE_VIEW3D); + /* regions: hud */ art = ED_area_type_hud(st->spaceid); BLI_addhead(&st->regiontypes, art); diff --git a/source/blender/makesdna/DNA_asset_defaults.h b/source/blender/makesdna/DNA_asset_defaults.h index 63239f23e4e..14134ee4395 100644 --- a/source/blender/makesdna/DNA_asset_defaults.h +++ b/source/blender/makesdna/DNA_asset_defaults.h @@ -20,7 +20,7 @@ #define _DNA_DEFAULT_AssetLibraryReference \ { \ - .type = ASSET_LIBRARY_LOCAL, \ + .type = ASSET_LIBRARY_ALL, \ /* Not needed really (should be ignored for anything but #ASSET_LIBRARY_CUSTOM), but helps debugging. */ \ .custom_library_index = -1, \ } diff --git a/source/blender/makesdna/DNA_screen_types.h b/source/blender/makesdna/DNA_screen_types.h index cc7360ae6a4..0a67357ee38 100644 --- a/source/blender/makesdna/DNA_screen_types.h +++ b/source/blender/makesdna/DNA_screen_types.h @@ -312,6 +312,13 @@ typedef struct uiList { /* some list UI data need to be saved in file */ uiListDyn *dyn_data; } uiList; +typedef struct AssetShelf { + struct AssetShelf *next, *prev; + + /** Runtime. */ + struct AssetShelfType *type; +} AssetShelf; + typedef struct TransformOrientation { struct TransformOrientation *next, *prev; /** MAX_NAME. */ @@ -661,8 +668,10 @@ typedef enum eRegion_Type { /* Region type used exclusively by internal code and add-ons to register draw callbacks to the XR * context (surface, mirror view). Does not represent any real region. */ RGN_TYPE_XR = 13, + RGN_TYPE_ASSET_SHELF = 14, + RGN_TYPE_ASSET_SHELF_FOOTER = 15, -#define RGN_TYPE_NUM (RGN_TYPE_XR + 1) +#define RGN_TYPE_NUM (RGN_TYPE_ASSET_SHELF_FOOTER + 1) } eRegion_Type; /* use for function args */ @@ -673,8 +682,8 @@ typedef enum eRegion_Type { /* Check for any kind of header region. */ #define RGN_TYPE_IS_HEADER_ANY(regiontype) \ - (((1 << (regiontype)) & \ - ((1 << RGN_TYPE_HEADER) | 1 << (RGN_TYPE_TOOL_HEADER) | (1 << RGN_TYPE_FOOTER))) != 0) + (((1 << (regiontype)) & ((1 << RGN_TYPE_HEADER) | 1 << (RGN_TYPE_TOOL_HEADER) | \ + (1 << RGN_TYPE_FOOTER) | (1 << RGN_TYPE_ASSET_SHELF_FOOTER))) != 0) /** #ARegion.alignment */ enum { @@ -748,6 +757,12 @@ enum { RGN_DRAW_EDITOR_OVERLAYS = 32, }; +typedef struct AssetShelfSettings { + /* TODO make this per mode? (or use a custom identifier?) */ + ListBase enabled_catalog_paths; /* #LinkData */ + const char *active_catalog_path; +} AssetShelfSettings; + #ifdef __cplusplus } #endif diff --git a/source/blender/makesdna/DNA_view3d_types.h b/source/blender/makesdna/DNA_view3d_types.h index ff82cd2183f..bd67fb13644 100644 --- a/source/blender/makesdna/DNA_view3d_types.h +++ b/source/blender/makesdna/DNA_view3d_types.h @@ -360,6 +360,8 @@ typedef struct View3D { /** Path to the viewer node that is currently previewed. This is retrieved from the workspace. */ ViewerPath viewer_path; + struct AssetShelfSettings *asset_shelf; + /** Runtime evaluation data (keep last). */ View3D_Runtime runtime; } View3D; diff --git a/source/blender/makesrna/intern/rna_screen.c b/source/blender/makesrna/intern/rna_screen.c index 4d837a374d1..11c3dac9897 100644 --- a/source/blender/makesrna/intern/rna_screen.c +++ b/source/blender/makesrna/intern/rna_screen.c @@ -26,6 +26,8 @@ const EnumPropertyItem rna_enum_region_type_items[] = { {RGN_TYPE_UI, "UI", 0, "UI", ""}, {RGN_TYPE_TOOLS, "TOOLS", 0, "Tools", ""}, {RGN_TYPE_TOOL_PROPS, "TOOL_PROPS", 0, "Tool Properties", ""}, + {RGN_TYPE_ASSET_SHELF, "ASSET_SHELF", 0, "Asset Shelf", ""}, + {RGN_TYPE_ASSET_SHELF_FOOTER, "ASSET_SHELF_FOOTER", 0, "Asset Shelf Footer", ""}, {RGN_TYPE_PREVIEW, "PREVIEW", 0, "Preview", ""}, {RGN_TYPE_HUD, "HUD", 0, "Floating Region", ""}, {RGN_TYPE_NAV_BAR, "NAVIGATION_BAR", 0, "Navigation Bar", ""}, diff --git a/source/blender/makesrna/intern/rna_ui.c b/source/blender/makesrna/intern/rna_ui.c index 935e442952e..50d5a91c727 100644 --- a/source/blender/makesrna/intern/rna_ui.c +++ b/source/blender/makesrna/intern/rna_ui.c @@ -1068,6 +1068,156 @@ static StructRNA *rna_Menu_refine(PointerRNA *mtr) return (menu->type && menu->type->rna_ext.srna) ? menu->type->rna_ext.srna : &RNA_Menu; } +/* Asset Shelf */ + +static bool asset_shelf_asset_poll(const AssetShelfType *shelf_type, const AssetHandle *asset) +{ + extern FunctionRNA rna_AssetShelf_asset_poll___func; + + PointerRNA ptr; + RNA_pointer_create(NULL, shelf_type->rna_ext.srna, NULL, &ptr); /* dummy */ + FunctionRNA *func = &rna_AssetShelf_asset_poll___func; + + ParameterList list; + RNA_parameter_list_create(&list, &ptr, func); + RNA_parameter_set_lookup(&list, "asset_handle", &asset); + shelf_type->rna_ext.call(NULL, &ptr, func, &list); + + void *ret; + RNA_parameter_get_lookup(&list, "visible", &ret); + /* Get the value before freeing. */ + const bool is_visible = *(bool *)ret; + + RNA_parameter_list_free(&list); + + return is_visible; +} + +static bool asset_shelf_poll(const bContext *C, const AssetShelfType *shelf_type) +{ + extern FunctionRNA rna_AssetShelf_poll_func; + + PointerRNA ptr; + RNA_pointer_create(NULL, shelf_type->rna_ext.srna, NULL, &ptr); /* dummy */ + FunctionRNA *func = &rna_AssetShelf_poll_func; /* RNA_struct_find_function(&ptr, "poll"); */ + + ParameterList list; + RNA_parameter_list_create(&list, &ptr, func); + RNA_parameter_set_lookup(&list, "context", &C); + shelf_type->rna_ext.call((bContext *)C, &ptr, func, &list); + + void *ret; + RNA_parameter_get_lookup(&list, "visible", &ret); + /* Get the value before freeing. */ + const bool is_visible = *(bool *)ret; + + RNA_parameter_list_free(&list); + + return is_visible; +} + +static void rna_AssetShelf_unregister(Main *UNUSED(bmain), StructRNA *type) +{ + AssetShelfType *shelf_type = RNA_struct_blender_type_get(type); + + if (!shelf_type) { + return; + } + + SpaceType *space_type = BKE_spacetype_from_id(shelf_type->space_type); + if (!space_type) { + return; + } + + RNA_struct_free_extension(type, &shelf_type->rna_ext); + RNA_struct_free(&BLENDER_RNA, type); + + BLI_freelinkN(&space_type->asset_shelf_types, shelf_type); + + /* update while blender is running */ + WM_main_add_notifier(NC_WINDOW, NULL); +} + +static StructRNA *rna_AssetShelf_register(Main *bmain, + ReportList *reports, + void *data, + const char *identifier, + StructValidateFunc validate, + StructCallbackFunc call, + StructFreeFunc free) +{ + AssetShelfType dummy_shelf_type = {NULL}; + AssetShelf dummy_shelf = {NULL}; + PointerRNA dummy_shelf_type_ptr; + + /* setup dummy shelf & shelf type to store static properties in */ + dummy_shelf.type = &dummy_shelf_type; + RNA_pointer_create(NULL, &RNA_AssetShelf, &dummy_shelf, &dummy_shelf_type_ptr); + + int have_function[2]; + + /* validate the python class */ + if (validate(&dummy_shelf_type_ptr, data, have_function) != 0) { + return NULL; + } + + if (strlen(identifier) >= sizeof(dummy_shelf_type.idname)) { + BKE_reportf(reports, + RPT_ERROR, + "Registering asset shelf class: '%s' is too long, maximum length is %d", + identifier, + (int)sizeof(dummy_shelf_type.idname)); + return NULL; + } + + SpaceType *space_type = BKE_spacetype_from_id(dummy_shelf_type.space_type); + if (!space_type) { + return NULL; + } + + /* Check if we have registered this asset shelf type before, and remove it. */ + LISTBASE_FOREACH (AssetShelfType *, iter_shelf_type, &space_type->asset_shelf_types) { + if (STREQ(iter_shelf_type->idname, dummy_shelf_type.idname)) { + if (iter_shelf_type->rna_ext.srna) { + rna_AssetShelf_unregister(bmain, iter_shelf_type->rna_ext.srna); + } + break; + } + } + if (!RNA_struct_available_or_report(reports, dummy_shelf_type.idname)) { + return NULL; + } + if (!RNA_struct_bl_idname_ok_or_report(reports, dummy_shelf_type.idname, "_AST_")) { + return NULL; + } + + /* Create the new shelf type. */ + AssetShelfType *shelf_type = MEM_mallocN(sizeof(*shelf_type), __func__); + memcpy(shelf_type, &dummy_shelf_type, sizeof(*shelf_type)); + + shelf_type->rna_ext.srna = RNA_def_struct_ptr(&BLENDER_RNA, shelf_type->idname, &RNA_AssetShelf); + shelf_type->rna_ext.data = data; + shelf_type->rna_ext.call = call; + shelf_type->rna_ext.free = free; + RNA_struct_blender_type_set(shelf_type->rna_ext.srna, shelf_type); + + shelf_type->poll = have_function[0] ? asset_shelf_poll : NULL; + shelf_type->asset_poll = have_function[1] ? asset_shelf_asset_poll : NULL; + + BLI_addtail(&space_type->asset_shelf_types, shelf_type); + + /* update while blender is running */ + WM_main_add_notifier(NC_WINDOW, NULL); + + return shelf_type->rna_ext.srna; +} + +static StructRNA *rna_AssetShelf_refine(PointerRNA *shelf_ptr) +{ + AssetShelf *shelf = (AssetShelf *)shelf_ptr->data; + return (shelf->type && shelf->type->rna_ext.srna) ? shelf->type->rna_ext.srna : &RNA_AssetShelf; +} + static void rna_Panel_bl_description_set(PointerRNA *ptr, const char *value) { Panel *data = (Panel *)(ptr->data); @@ -1880,6 +2030,67 @@ static void rna_def_menu(BlenderRNA *brna) RNA_define_verify_sdna(1); } +static void rna_def_asset_shelf(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "AssetShelf", NULL); + RNA_def_struct_ui_text(srna, "Asset Shelf", "Regions for quick access to assets"); + RNA_def_struct_refine_func(srna, "rna_AssetShelf_refine"); + RNA_def_struct_register_funcs( + srna, "rna_AssetShelf_register", "rna_AssetShelf_unregister", NULL); + RNA_def_struct_translation_context(srna, BLT_I18NCONTEXT_DEFAULT_BPYRNA); + RNA_def_struct_flag(srna, STRUCT_PUBLIC_NAMESPACE_INHERIT); + + /* registration */ + + prop = RNA_def_property(srna, "bl_idname", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "type->idname"); + RNA_def_property_flag(prop, PROP_REGISTER); + RNA_def_property_ui_text(prop, + "ID Name", + "If this is set, the asset gets a custom ID, otherwise it takes the " + "name of the class used to define the menu (for example, if the " + "class name is \"OBJECT_AST_hello\", and bl_idname is not set by the " + "script, then bl_idname = \"OBJECT_AST_hello\")"); + + prop = RNA_def_property(srna, "bl_space_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "type->space_type"); + RNA_def_property_enum_items(prop, rna_enum_space_type_items); + RNA_def_property_flag(prop, PROP_REGISTER); + RNA_def_property_ui_text( + prop, "Space Type", "The space where the asset shelf is going to be used in"); + + PropertyRNA *parm; + FunctionRNA *func; + + func = RNA_def_function(srna, "poll", NULL); + RNA_def_function_ui_description( + func, "If this method returns a non-null output, then the asset shelf will be visible"); + RNA_def_function_flag(func, FUNC_NO_SELF | FUNC_REGISTER_OPTIONAL); + RNA_def_function_return(func, RNA_def_boolean(func, "visible", 1, "", "")); + parm = RNA_def_pointer(func, "context", "Context", "", ""); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + + func = RNA_def_function(srna, "asset_poll__", NULL); + RNA_def_function_ui_description( + func, + "TEMPORARY DESIGN; Expect compatibility breakage. Determine if an asset should be visible " + "in the asset shelf. If this method returns a non-null output, then the asset shelf will be " + "visible"); + RNA_def_function_flag(func, FUNC_NO_SELF | FUNC_REGISTER_OPTIONAL); + RNA_def_function_return(func, RNA_def_boolean(func, "visible", 1, "", "")); + parm = RNA_def_pointer(func, "asset_handle", "AssetHandle", "", ""); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); +} + +static void rna_def_asset_shelf_settings(BlenderRNA *brna) +{ + StructRNA *srna = RNA_def_struct(brna, "AssetShelfSettings", NULL); + RNA_def_struct_ui_text(srna, "Asset Shelf Settings", ""); +} + void RNA_def_ui(BlenderRNA *brna) { rna_def_ui_layout(brna); @@ -1887,6 +2098,8 @@ void RNA_def_ui(BlenderRNA *brna) rna_def_uilist(brna); rna_def_header(brna); rna_def_menu(brna); + rna_def_asset_shelf(brna); + rna_def_asset_shelf_settings(brna); } #endif /* RNA_RUNTIME */ diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h index 3e97abca3be..7113bfdc9b5 100644 --- a/source/blender/windowmanager/WM_types.h +++ b/source/blender/windowmanager/WM_types.h @@ -485,6 +485,7 @@ typedef struct wmNotifier { #define ND_SPACE_CLIP (20 << 16) #define ND_SPACE_FILE_PREVIEW (21 << 16) #define ND_SPACE_SPREADSHEET (22 << 16) +#define ND_SPACE_ASSET_SHELF (23 << 16) /* NC_ASSET */ /* Denotes that the AssetList is done reading some previews. NOT that the preview generation of diff --git a/source/blender/windowmanager/intern/wm_event_system.cc b/source/blender/windowmanager/intern/wm_event_system.cc index dcf73332410..42ace4c8b4f 100644 --- a/source/blender/windowmanager/intern/wm_event_system.cc +++ b/source/blender/windowmanager/intern/wm_event_system.cc @@ -6116,6 +6116,7 @@ void WM_window_cursor_keymap_status_refresh(bContext *C, wmWindow *win) RGN_TYPE_HEADER, RGN_TYPE_TOOL_HEADER, RGN_TYPE_FOOTER, + RGN_TYPE_ASSET_SHELF_FOOTER, RGN_TYPE_TEMPORARY, RGN_TYPE_HUD)) { return;