From 98142f5e35b488f6c900d283b21ee4d8bdde5207 Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Thu, 3 Aug 2023 16:54:39 +0200 Subject: [PATCH] UI: Asset Shelf Support (Experimental Feature) No user visible changes expected, except of new experimental feature option. ------------------------------------------------------------------------------ This introduces asset shelves as a new standard UI element for accessing assets. Based on the current context (like the active mode and/or tool), they can provide assets for specific workflows/tasks. As such they are more limited in functionality than the asset browser, but a lot more efficient for certain tasks. The asset shelf is developed as part of the brush assets project (see #101895), but is also meant to replace the current pose library UI. Support for asset shelves can quite easily be added to different editor types, the following commit will add support for the 3D View. If an editor type supports asset shelves, add-ons can chose to register an asset shelf type for an editor with just a few lines of Python. It should be possible to entirely remove `UILayout.asset_view_template()` once asset shelves are non-experimental. Some changes are to be expected still, see #107881. Task: #102879 Brush asset workflow blog post: https://code.blender.org/2022/12/brush-assets-workflow/ Initial technical documentation: https://developer.blender.org/docs/asset_system/user_interface/asset_shelf/ Pull Request: #104831 --- scripts/modules/bpy_types.py | 4 + scripts/startup/bl_operators/wm.py | 1 + scripts/startup/bl_ui/__init__.py | 1 + scripts/startup/bl_ui/asset_shelf.py | 39 + scripts/startup/bl_ui/space_userpref.py | 9 + source/blender/blenkernel/BKE_icons.h | 4 + source/blender/blenkernel/BKE_screen.h | 41 + source/blender/blenkernel/intern/icons.cc | 12 + source/blender/blenkernel/intern/screen.cc | 20 +- source/blender/editors/asset/CMakeLists.txt | 9 + .../blender/editors/asset/ED_asset_filter.hh | 8 +- .../blender/editors/asset/ED_asset_handle.h | 1 + source/blender/editors/asset/ED_asset_list.h | 2 + source/blender/editors/asset/ED_asset_shelf.h | 69 ++ .../editors/asset/intern/asset_filter.cc | 48 ++ .../editors/asset/intern/asset_handle.cc | 7 + .../editors/asset/intern/asset_list.cc | 13 + .../editors/asset/intern/asset_shelf.cc | 706 ++++++++++++++++++ .../editors/asset/intern/asset_shelf.hh | 75 ++ .../asset/intern/asset_shelf_asset_view.cc | 300 ++++++++ .../intern/asset_shelf_catalog_selector.cc | 216 ++++++ .../asset/intern/asset_shelf_regiondata.cc | 83 ++ .../asset/intern/asset_shelf_settings.cc | 144 ++++ source/blender/editors/include/ED_screen.h | 5 + .../blender/editors/include/UI_grid_view.hh | 3 + source/blender/editors/include/UI_interface.h | 12 +- source/blender/editors/interface/interface.cc | 12 +- .../editors/interface/interface_handlers.cc | 21 +- .../editors/interface/interface_intern.hh | 7 +- .../editors/interface/interface_widgets.cc | 61 +- source/blender/editors/interface/resources.cc | 24 +- .../interface/views/abstract_view_item.cc | 11 + .../editors/interface/views/grid_view.cc | 18 +- .../editors/interface/views/interface_view.cc | 5 + source/blender/editors/screen/CMakeLists.txt | 2 + source/blender/editors/screen/area.cc | 29 +- source/blender/editors/screen/screen_edit.cc | 4 +- source/blender/editors/screen/screen_ops.cc | 14 +- .../blender/editors/space_file/file_draw.cc | 2 +- source/blender/editors/space_file/filelist.cc | 8 + source/blender/editors/space_file/filelist.hh | 1 + source/blender/makesdna/DNA_screen_types.h | 75 +- source/blender/makesdna/DNA_userdef_types.h | 9 + source/blender/makesrna/intern/rna_screen.cc | 2 + source/blender/makesrna/intern/rna_space.cc | 20 + source/blender/makesrna/intern/rna_ui.cc | 279 +++++++ source/blender/makesrna/intern/rna_userdef.cc | 36 + source/blender/python/intern/bpy_rna.cc | 2 +- source/blender/windowmanager/WM_types.h | 2 + .../windowmanager/intern/wm_event_system.cc | 1 + .../blender/windowmanager/intern/wm_keymap.cc | 5 +- 51 files changed, 2429 insertions(+), 53 deletions(-) create mode 100644 scripts/startup/bl_ui/asset_shelf.py create mode 100644 source/blender/editors/asset/ED_asset_shelf.h create mode 100644 source/blender/editors/asset/intern/asset_shelf.cc create mode 100644 source/blender/editors/asset/intern/asset_shelf.hh create mode 100644 source/blender/editors/asset/intern/asset_shelf_asset_view.cc create mode 100644 source/blender/editors/asset/intern/asset_shelf_catalog_selector.cc create mode 100644 source/blender/editors/asset/intern/asset_shelf_regiondata.cc create mode 100644 source/blender/editors/asset/intern/asset_shelf_settings.cc diff --git a/scripts/modules/bpy_types.py b/scripts/modules/bpy_types.py index f1ed3780e67..3efd23cb0d7 100644 --- a/scripts/modules/bpy_types.py +++ b/scripts/modules/bpy_types.py @@ -1183,6 +1183,10 @@ class Menu(StructRNA, _GenericUI, metaclass=RNAMeta): layout.menu(cls.__name__, icon='COLLAPSEMENU') +class AssetShelf(StructRNA, metaclass=RNAMeta): + __slots__ = () + + class NodeTree(bpy_types.ID, metaclass=RNAMetaPropGroup): __slots__ = () diff --git a/scripts/startup/bl_operators/wm.py b/scripts/startup/bl_operators/wm.py index 63196117ef3..940095ca7cd 100644 --- a/scripts/startup/bl_operators/wm.py +++ b/scripts/startup/bl_operators/wm.py @@ -3319,6 +3319,7 @@ class WM_MT_region_toggle_pie(Menu): # no need to include both in this list. 'HEADER': "show_region_header", 'FOOTER': "show_region_footer", + 'ASSET_SHELF': "show_region_asset_shelf", 'CHANNELS': "show_region_channels", } # Map the `region.alignment` to the axis-aligned pie position. diff --git a/scripts/startup/bl_ui/__init__.py b/scripts/startup/bl_ui/__init__.py index f0dec2244d1..cdc7b03b6d2 100644 --- a/scripts/startup/bl_ui/__init__.py +++ b/scripts/startup/bl_ui/__init__.py @@ -11,6 +11,7 @@ if "bpy" in locals(): del reload _modules = [ + "asset_shelf", "node_add_menu", "node_add_menu_geometry", "properties_animviz", diff --git a/scripts/startup/bl_ui/asset_shelf.py b/scripts/startup/bl_ui/asset_shelf.py new file mode 100644 index 00000000000..88c5fbbf660 --- /dev/null +++ b/scripts/startup/bl_ui/asset_shelf.py @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: 2023 Blender Foundation +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import bpy +from bpy.types import ( + Panel, +) + + +class ASSETSHELF_PT_display(Panel): + bl_label = "Display Settings" + # Doesn't actually matter. Panel is instanced through popover only. + bl_space_type = 'VIEW_3D' + bl_region_type = 'HEADER' + + def draw(self, context): + layout = self.layout + + shelf = context.asset_shelf + + layout.prop(shelf, "show_names", text="Names") + layout.prop(shelf, "preview_size") + + @classmethod + def poll(cls, context): + return context.asset_shelf is not None + + +classes = ( + ASSETSHELF_PT_display, +) + + +if __name__ == "__main__": # only for live edit. + from bpy.utils import register_class + + for cls in classes: + register_class(cls) diff --git a/scripts/startup/bl_ui/space_userpref.py b/scripts/startup/bl_ui/space_userpref.py index d2ae299b108..3c8bc7f6f7b 100644 --- a/scripts/startup/bl_ui/space_userpref.py +++ b/scripts/startup/bl_ui/space_userpref.py @@ -1146,6 +1146,14 @@ class PreferenceThemeSpacePanel: }, } + @classmethod + def poll(cls, context): + # Special exception: Hide asset shelf theme settings depending on experimental "Asset Shelf" option. + if cls.datapath.endswith(".asset_shelf"): + prefs = context.preferences + return prefs.experimental.use_asset_shelf + return True + # TODO theme_area should be deprecated @staticmethod def _theme_generic(layout, themedata, theme_area): @@ -2394,6 +2402,7 @@ class USERPREF_PT_experimental_new_features(ExperimentalPanel, Panel): ({"property": "use_sculpt_tools_tilt"}, ("blender/blender/issues/82877", "#82877")), ({"property": "use_extended_asset_browser"}, ("blender/blender/projects/10", "Pipeline, Assets & IO Project Page")), + ({"property": "use_asset_shelf"}, ("blender/blender/issues/102879", "#102879")), ({"property": "use_override_templates"}, ("blender/blender/issues/73318", "Milestone 4")), ({"property": "use_new_volume_nodes"}, ("blender/blender/issues/103248", "#103248")), ({"property": "use_node_panels"}, ("blender/blender/issues/105248", "#105248")), diff --git a/source/blender/blenkernel/BKE_icons.h b/source/blender/blenkernel/BKE_icons.h index 3d1e7026d91..0301584e71d 100644 --- a/source/blender/blenkernel/BKE_icons.h +++ b/source/blender/blenkernel/BKE_icons.h @@ -20,6 +20,7 @@ extern "C" { #endif #include "BLI_compiler_attrs.h" +#include "BLI_sys_types.h" #include "DNA_ID_enums.h" @@ -110,6 +111,9 @@ struct ImBuf *BKE_icon_imbuf_get_buffer(int icon_id) ATTR_WARN_UNUSED_RESULT; */ struct Icon *BKE_icon_get(int icon_id); +bool BKE_icon_is_preview(int icon_id); +bool BKE_icon_is_image(int icon_id); + /** * Set icon for id if not already defined. * Used for inserting the internal icons. diff --git a/source/blender/blenkernel/BKE_screen.h b/source/blender/blenkernel/BKE_screen.h index 7b82203df18..44ea58803e9 100644 --- a/source/blender/blenkernel/BKE_screen.h +++ b/source/blender/blenkernel/BKE_screen.h @@ -129,6 +129,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. */ @@ -413,6 +416,44 @@ typedef struct Menu { struct uiLayout *layout; /* runtime for drawing */ } Menu; +/* asset shelf types */ + +/* #AssetShelfType.flag */ +typedef enum AssetShelfTypeFlag { + /** Do not trigger asset dragging on drag events. Drag events can be overridden with custom + * keymap items then. */ + ASSET_SHELF_TYPE_FLAG_NO_ASSET_DRAG = (1 << 0), + + ASSET_SHELF_TYPE_FLAG_MAX +} AssetShelfTypeFlag; +ENUM_OPERATORS(AssetShelfTypeFlag, ASSET_SHELF_TYPE_FLAG_MAX); + +typedef struct AssetShelfType { + struct AssetShelfType *next, *prev; + + char idname[BKE_ST_MAXNAME]; /* unique name */ + + int space_type; + + AssetShelfTypeFlag flag; + + /** Determine if asset shelves of this type should be available in current context 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 first and foremost be controlled by asset traits. */ + bool (*asset_poll)(const struct AssetShelfType *shelf_type, const struct AssetHandle *asset); + + /** Asset shelves can define their own context menu via this layout definition callback. */ + void (*draw_context_menu)(const struct bContext *C, + const struct AssetShelfType *shelf_type, + const struct AssetHandle *asset, + struct uiLayout *layout); + + /* RNA integration */ + ExtensionRNA rna_ext; +} AssetShelfType; + /* Space-types. */ struct SpaceType *BKE_spacetype_from_id(int spaceid); diff --git a/source/blender/blenkernel/intern/icons.cc b/source/blender/blenkernel/intern/icons.cc index 31c975a8f52..054dc8233d5 100644 --- a/source/blender/blenkernel/intern/icons.cc +++ b/source/blender/blenkernel/intern/icons.cc @@ -860,6 +860,18 @@ Icon *BKE_icon_get(const int icon_id) return icon; } +bool BKE_icon_is_preview(const int icon_id) +{ + const Icon *icon = BKE_icon_get(icon_id); + return icon->obj_type == ICON_DATA_PREVIEW; +} + +bool BKE_icon_is_image(const int icon_id) +{ + const Icon *icon = BKE_icon_get(icon_id); + return icon->obj_type == ICON_DATA_IMBUF; +} + void BKE_icon_set(const int icon_id, Icon *icon) { void **val_p; diff --git a/source/blender/blenkernel/intern/screen.cc b/source/blender/blenkernel/intern/screen.cc index bc046d8e8f7..24436207a1a 100644 --- a/source/blender/blenkernel/intern/screen.cc +++ b/source/blender/blenkernel/intern/screen.cc @@ -52,6 +52,10 @@ #include "BLO_read_write.h" +/* TODO(@JulianEisel): For asset shelf region reading/writing. Region read/write should be done via + * a #ARegionType callback. */ +#include "../editors/asset/ED_asset_shelf.h" + #ifdef WITH_PYTHON # include "BPY_extern.h" #endif @@ -445,6 +449,7 @@ static void spacetype_free(SpaceType *st) } BLI_freelistN(&st->regiontypes); + BLI_freelistN(&st->asset_shelf_types); } void BKE_spacetypes_free() @@ -1204,6 +1209,11 @@ static void write_region(BlendWriter *writer, ARegion *region, int spacetype) return; } + if (region->regiontype == RGN_TYPE_ASSET_SHELF) { + ED_asset_shelf_region_blend_write(writer, region); + return; + } + switch (spacetype) { case SPACE_VIEW3D: if (region->regiontype == RGN_TYPE_WINDOW) { @@ -1337,9 +1347,10 @@ static void direct_link_region(BlendDataReader *reader, ARegion *region, int spa region->regiondata = nullptr; } else { - BLO_read_data_address(reader, ®ion->regiondata); - if (region->regiondata) { - if (spacetype == SPACE_VIEW3D) { + if (spacetype == SPACE_VIEW3D) { + if (region->regiontype == RGN_TYPE_WINDOW) { + BLO_read_data_address(reader, ®ion->regiondata); + RegionView3D *rv3d = static_cast(region->regiondata); BLO_read_data_address(reader, &rv3d->localvd); @@ -1353,6 +1364,9 @@ static void direct_link_region(BlendDataReader *reader, ARegion *region, int spa rv3d->runtime_viewlock = 0; } } + if (region->regiontype == RGN_TYPE_ASSET_SHELF) { + ED_asset_shelf_region_blend_read_data(reader, region); + } } region->v2d.sms = nullptr; diff --git a/source/blender/editors/asset/CMakeLists.txt b/source/blender/editors/asset/CMakeLists.txt index cc6f21e5b15..e995415ecda 100644 --- a/source/blender/editors/asset/CMakeLists.txt +++ b/source/blender/editors/asset/CMakeLists.txt @@ -12,6 +12,8 @@ set(INC ../../makesrna ../../windowmanager ../../../../intern/clog + # dna_type_offsets.h + ${CMAKE_CURRENT_BINARY_DIR}/../../makesdna/intern # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna ) @@ -30,6 +32,11 @@ set(SRC intern/asset_list.cc intern/asset_mark_clear.cc intern/asset_ops.cc + intern/asset_shelf.cc + intern/asset_shelf_asset_view.cc + intern/asset_shelf_catalog_selector.cc + intern/asset_shelf_regiondata.cc + intern/asset_shelf_settings.cc intern/asset_temp_id_consumer.cc intern/asset_type.cc @@ -43,9 +50,11 @@ 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 + intern/asset_shelf.hh ) set(LIB diff --git a/source/blender/editors/asset/ED_asset_filter.hh b/source/blender/editors/asset/ED_asset_filter.hh index 809fa6d86a1..1c65068eed8 100644 --- a/source/blender/editors/asset/ED_asset_filter.hh +++ b/source/blender/editors/asset/ED_asset_filter.hh @@ -17,12 +17,14 @@ #include "AS_asset_catalog_tree.hh" struct AssetFilterSettings; +struct AssetHandle; struct AssetLibraryReference; struct bContext; namespace blender::asset_system { +class AssetLibrary; class AssetRepresentation; -} +} // namespace blender::asset_system /** * Compare \a asset against the settings of \a filter. @@ -47,6 +49,10 @@ struct AssetItemTree { assets_per_path; }; +asset_system::AssetCatalogTree build_filtered_catalog_tree( + const asset_system::AssetLibrary &library, + const AssetLibraryReference &library_ref, + blender::FunctionRef is_asset_visible_fn); AssetItemTree build_filtered_all_catalog_tree( const AssetLibraryReference &library_ref, const bContext &C, diff --git a/source/blender/editors/asset/ED_asset_handle.h b/source/blender/editors/asset/ED_asset_handle.h index 64a50bfdb35..e33113c5199 100644 --- a/source/blender/editors/asset/ED_asset_handle.h +++ b/source/blender/editors/asset/ED_asset_handle.h @@ -37,6 +37,7 @@ struct AssetHandle; AssetRepresentationHandle *ED_asset_handle_get_representation(const struct AssetHandle *asset); ID_Type ED_asset_handle_get_id_type(const struct AssetHandle *asset); int ED_asset_handle_get_preview_icon_id(const struct AssetHandle *asset); +int ED_asset_handle_get_preview_or_type_icon_id(const struct AssetHandle *asset); void ED_asset_handle_get_full_library_path( const struct AssetHandle *asset, /* `1024` for #FILE_MAX, diff --git a/source/blender/editors/asset/ED_asset_list.h b/source/blender/editors/asset/ED_asset_list.h index 2e53b60a6c5..1a1d0104bb5 100644 --- a/source/blender/editors/asset/ED_asset_list.h +++ b/source/blender/editors/asset/ED_asset_list.h @@ -66,6 +66,8 @@ blender::asset_system::AssetRepresentation *ED_assetlist_asset_get_by_index( const AssetLibraryReference &library_reference, int asset_index); #endif +bool ED_assetlist_asset_image_is_loading(const AssetLibraryReference *library_reference, + const AssetHandle *asset_handle); struct ImBuf *ED_assetlist_asset_image_get(const AssetHandle *asset_handle); /** 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..7de2cbc18ff --- /dev/null +++ b/source/blender/editors/asset/ED_asset_shelf.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edasset + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +struct ARegion; +struct ARegionType; +struct AssetShelfSettings; +struct bContext; +struct bContextDataResult; +struct BlendDataReader; +struct BlendWriter; +struct wmWindowManager; +struct RegionPollParams; + +/* -------------------------------------------------------------------- */ +/** \name Asset Shelf Regions + * + * Naming conventions: + * - #ED_asset_shelf_regions_xxx(): Applies to both regions (#RGN_TYPE_ASSET_SHELF and + * #RGN_TYPE_ASSET_SHELF_HEADER). + * - #ED_asset_shelf_region_xxx(): Applies to the main shelf region (#RGN_TYPE_ASSET_SHELF). + * - #ED_asset_shelf_header_region_xxx(): Applies to the shelf header region + * (#RGN_TYPE_ASSET_SHELF_HEADER). + * + * \{ */ + +bool ED_asset_shelf_regions_poll(const struct RegionPollParams *params); + +/** Only needed for #RGN_TYPE_ASSET_SHELF (not #RGN_TYPE_ASSET_SHELF_HEADER). */ +void *ED_asset_shelf_region_duplicate(void *regiondata); +void ED_asset_shelf_region_free(struct ARegion *region); +void ED_asset_shelf_region_init(struct wmWindowManager *wm, struct ARegion *region); +int ED_asset_shelf_region_snap(const struct ARegion *region, int size, int axis); +void ED_asset_shelf_region_listen(const struct wmRegionListenerParams *params); +void ED_asset_shelf_region_layout(const bContext *C, struct ARegion *region); +void ED_asset_shelf_region_draw(const bContext *C, struct ARegion *region); +void ED_asset_shelf_region_blend_read_data(BlendDataReader *reader, struct ARegion *region); +void ED_asset_shelf_region_blend_write(BlendWriter *writer, struct ARegion *region); +int ED_asset_shelf_region_prefsizey(void); + +void ED_asset_shelf_header_region_init(struct wmWindowManager *wm, struct ARegion *region); +void ED_asset_shelf_header_region(const struct bContext *C, struct ARegion *region); +void ED_asset_shelf_header_region_listen(const struct wmRegionListenerParams *params); +int ED_asset_shelf_header_region_size(void); +void ED_asset_shelf_header_regiontype_register(struct ARegionType *region_type, + const int space_type); + +/** \} */ + +/* -------------------------------------------------------------------- */ + +int ED_asset_shelf_tile_width(const struct AssetShelfSettings &settings); +int ED_asset_shelf_tile_height(const struct AssetShelfSettings &settings); + +int ED_asset_shelf_context(const struct bContext *C, + const char *member, + struct bContextDataResult *result); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/editors/asset/intern/asset_filter.cc b/source/blender/editors/asset/intern/asset_filter.cc index 42883177f23..ee39b480d31 100644 --- a/source/blender/editors/asset/intern/asset_filter.cc +++ b/source/blender/editors/asset/intern/asset_filter.cc @@ -19,6 +19,7 @@ #include "AS_asset_library.hh" #include "ED_asset_filter.hh" +#include "ED_asset_handle.h" #include "ED_asset_library.h" #include "ED_asset_list.h" #include "ED_asset_list.hh" @@ -51,6 +52,53 @@ bool ED_asset_filter_matches_asset(const AssetFilterSettings *filter, namespace blender::ed::asset { +asset_system::AssetCatalogTree build_filtered_catalog_tree( + const asset_system::AssetLibrary &library, + const AssetLibraryReference &library_ref, + const blender::FunctionRef is_asset_visible_fn) +{ + Set known_paths; + + /* Collect paths containing assets. */ + ED_assetlist_iterate(library_ref, [&](AssetHandle asset_handle) { + if (!is_asset_visible_fn(asset_handle)) { + return true; + } + + asset_system::AssetRepresentation &asset = *ED_asset_handle_get_representation(&asset_handle); + const AssetMetaData &meta_data = asset.get_metadata(); + if (BLI_uuid_is_nil(meta_data.catalog_id)) { + return true; + } + + const asset_system::AssetCatalog *catalog = library.catalog_service->find_catalog( + meta_data.catalog_id); + if (catalog == nullptr) { + return true; + } + known_paths.add(catalog->path.str()); + return true; + }); + + /* Build catalog tree. */ + asset_system::AssetCatalogTree filtered_tree; + asset_system::AssetCatalogTree &full_tree = *library.catalog_service->get_catalog_tree(); + full_tree.foreach_item([&](asset_system::AssetCatalogTreeItem &item) { + if (!known_paths.contains(item.catalog_path().str())) { + return; + } + + asset_system::AssetCatalog *catalog = library.catalog_service->find_catalog( + item.get_catalog_id()); + if (catalog == nullptr) { + return; + } + filtered_tree.insert_item(*catalog); + }); + + return filtered_tree; +} + AssetItemTree build_filtered_all_catalog_tree( const AssetLibraryReference &library_ref, const bContext &C, diff --git a/source/blender/editors/asset/intern/asset_handle.cc b/source/blender/editors/asset/intern/asset_handle.cc index 831068d3cc8..e5c92358bc6 100644 --- a/source/blender/editors/asset/intern/asset_handle.cc +++ b/source/blender/editors/asset/intern/asset_handle.cc @@ -18,6 +18,8 @@ #include "DNA_space_types.h" +#include "ED_fileselect.h" + #include "RNA_prototypes.h" #include "ED_asset_handle.h" @@ -38,6 +40,11 @@ int ED_asset_handle_get_preview_icon_id(const AssetHandle *asset) return asset->file_data->preview_icon_id; } +int ED_asset_handle_get_preview_or_type_icon_id(const AssetHandle *asset) +{ + return ED_file_icon(asset->file_data); +} + void ED_asset_handle_get_full_library_path(const AssetHandle *asset_handle, char r_full_lib_path[FILE_MAX]) { diff --git a/source/blender/editors/asset/intern/asset_list.cc b/source/blender/editors/asset/intern/asset_list.cc index 05c56e0e04f..038571ad1c2 100644 --- a/source/blender/editors/asset/intern/asset_list.cc +++ b/source/blender/editors/asset/intern/asset_list.cc @@ -122,6 +122,7 @@ class AssetList : NonCopyable { bool needsRefetch() const; bool isLoaded() const; + bool isAssetPreviewLoading(const AssetHandle &asset) const; asset_system::AssetLibrary *asset_library() const; void iterate(AssetListHandleIterFn fn) const; void iterate(AssetListIterFn fn) const; @@ -194,6 +195,11 @@ bool AssetList::isLoaded() const return filelist_is_ready(filelist_); } +bool AssetList::isAssetPreviewLoading(const AssetHandle &asset) const +{ + return filelist_file_is_preview_pending(filelist_, asset.file_data); +} + asset_system::AssetLibrary *AssetList::asset_library() const { return reinterpret_cast(filelist_asset_library(filelist_)); @@ -533,6 +539,13 @@ asset_system::AssetRepresentation *ED_assetlist_asset_get_by_index( return reinterpret_cast(asset_handle.file_data->asset); } +bool ED_assetlist_asset_image_is_loading(const AssetLibraryReference *library_reference, + const AssetHandle *asset_handle) +{ + const AssetList *list = AssetListStorage::lookup_list(*library_reference); + return list->isAssetPreviewLoading(*asset_handle); +} + 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_shelf.cc b/source/blender/editors/asset/intern/asset_shelf.cc new file mode 100644 index 00000000000..94a11b7a7ea --- /dev/null +++ b/source/blender/editors/asset/intern/asset_shelf.cc @@ -0,0 +1,706 @@ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edasset + * + * General asset shelf code, mostly region callbacks, drawing and context stuff. + */ + +#include + +#include "AS_asset_catalog_path.hh" + +#include "BKE_context.h" +#include "BKE_screen.h" + +#include "BLT_translation.h" + +#include "DNA_screen_types.h" + +#include "ED_asset_list.h" +#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 "UI_view2d.h" + +#include "WM_api.h" + +#include "ED_asset_shelf.h" +#include "asset_shelf.hh" + +using namespace blender; +using namespace blender::ed::asset; + +static int asset_shelf_default_tile_height(); + +namespace blender::ed::asset::shelf { + +void send_redraw_notifier(const bContext &C) +{ + WM_event_add_notifier(&C, NC_SPACE | ND_REGIONS_ASSET_SHELF, nullptr); +} + +} // namespace blender::ed::asset::shelf + +/* -------------------------------------------------------------------- */ +/** \name Shelf Type + * \{ */ + +static bool asset_shelf_type_poll(const bContext &C, AssetShelfType *shelf_type) +{ + if (!shelf_type) { + return false; + } + return !shelf_type->poll || shelf_type->poll(&C, shelf_type); +} + +static AssetShelfType *asset_shelf_type_ensure(const SpaceType &space_type, AssetShelf &shelf) +{ + if (shelf.type) { + return shelf.type; + } + + LISTBASE_FOREACH (AssetShelfType *, shelf_type, &space_type.asset_shelf_types) { + if (STREQ(shelf.idname, shelf_type->idname)) { + shelf.type = shelf_type; + return shelf_type; + } + } + + return nullptr; +} + +static AssetShelf *create_shelf_from_type(AssetShelfType &type) +{ + AssetShelf *shelf = MEM_new(__func__); + *shelf = dna::shallow_zero_initialize(); + shelf->settings.preview_size = shelf::DEFAULT_TILE_SIZE; + shelf->type = &type; + STRNCPY(shelf->idname, type.idname); + return shelf; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Active Shelf Management + * \{ */ + +/** + * Activating a shelf means assigning it to #RegionAssetShelf.active_shelf and (re-)inserting it at + * the beginning of the #RegionAssetShelf.shelves list. This implies that after calling this, \a + * shelf is guaranteed to be owned by the shelves list. + */ +static void activate_shelf(RegionAssetShelf &shelf_regiondata, AssetShelf &shelf) +{ + shelf_regiondata.active_shelf = &shelf; + BLI_assert(BLI_findindex(&shelf_regiondata.shelves, &shelf) > -1); + BLI_remlink(&shelf_regiondata.shelves, &shelf); + BLI_addhead(&shelf_regiondata.shelves, &shelf); +} + +/** + * Determine and set the currently active asset shelf, creating a new shelf if needed. + * + * The heuristic works as follows: + * 1) If the currently active shelf is still valid (poll succeeds), keep it active. + * 2) Otherwise, check for previously activated shelves in \a shelf_regiondata and activate the + * first valid one (first with a succeeding poll). + * 3) If none is valid, check all shelf-types available for \a space_type, create a new shelf for + * the first type that is valid (poll succeeds), and activate it. + * 4) If no shelf-type is valid, #RegionAssetShelf.active_shelf is set to null. + * + * When activating a shelf, it is moved to the beginning of the #RegionAssetShelf.shelves list, so + * that recently activated shelves are also the first ones to be reactivated. + * + * The returned shelf is guaranteed to have its #AssetShelf.type pointer set. + * + * \return A non-owning pointer to the now active shelf. Might be null if no shelf is valid in + * current context (all polls failed). + */ +static AssetShelf *update_active_shelf(const bContext &C, + const SpaceType &space_type, + RegionAssetShelf &shelf_regiondata) +{ + /* Note: Don't access #AssetShelf.type directly, use #asset_shelf_type_ensure(). */ + + /* Case 1: */ + if (shelf_regiondata.active_shelf && + asset_shelf_type_poll(C, + asset_shelf_type_ensure(space_type, *shelf_regiondata.active_shelf))) + { + /* Not a strong precondition, but if this is wrong something weird might be going on. */ + BLI_assert(shelf_regiondata.active_shelf == shelf_regiondata.shelves.first); + return shelf_regiondata.active_shelf; + } + + /* Case 2 (no active shelf or the poll of it isn't succeeding anymore. Poll all shelf types to + * determine a new active one): */ + LISTBASE_FOREACH (AssetShelf *, shelf, &shelf_regiondata.shelves) { + if (shelf == shelf_regiondata.active_shelf) { + continue; + } + + if (asset_shelf_type_poll(C, asset_shelf_type_ensure(space_type, *shelf))) { + /* Found a valid previously activated shelf, reactivate it. */ + activate_shelf(shelf_regiondata, *shelf); + return shelf; + } + } + + /* Case 3: */ + LISTBASE_FOREACH (AssetShelfType *, shelf_type, &space_type.asset_shelf_types) { + if (asset_shelf_type_poll(C, shelf_type)) { + AssetShelf *new_shelf = create_shelf_from_type(*shelf_type); + BLI_addhead(&shelf_regiondata.shelves, new_shelf); + /* Moves ownership to the regiondata. */ + activate_shelf(shelf_regiondata, *new_shelf); + return new_shelf; + } + } + + shelf_regiondata.active_shelf = nullptr; + return nullptr; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Asset Shelf Regions + * \{ */ + +void *ED_asset_shelf_region_duplicate(void *regiondata) +{ + const RegionAssetShelf *shelf_regiondata = static_cast(regiondata); + if (!shelf_regiondata) { + return nullptr; + } + + return shelf::regiondata_duplicate(shelf_regiondata); +} + +void ED_asset_shelf_region_free(ARegion *region) +{ + RegionAssetShelf *shelf_regiondata = RegionAssetShelf::get_from_asset_shelf_region(*region); + if (shelf_regiondata) { + shelf::regiondata_free(&shelf_regiondata); + } + region->regiondata = nullptr; +} + +/** + * Check if there is any asset shelf type in this space returning true in its poll. If not, no + * asset shelf region should be displayed. + */ +static bool asset_shelf_space_poll(const bContext *C, const SpaceLink *space_link) +{ + if (!U.experimental.use_asset_shelf) { + return false; + } + + 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 (asset_shelf_type_poll(*C, shelf_type)) { + return true; + } + } + + return false; +} + +bool ED_asset_shelf_regions_poll(const RegionPollParams *params) +{ + return asset_shelf_space_poll(params->context, + static_cast(params->area->spacedata.first)); +} + +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_REGIONS_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); + } +} + +void ED_asset_shelf_region_init(wmWindowManager *wm, ARegion *region) +{ + if (!region->regiondata) { + region->regiondata = MEM_cnew("RegionAssetShelf"); + } + RegionAssetShelf &shelf_regiondata = *RegionAssetShelf::get_from_asset_shelf_region(*region); + + /* Active shelf is only set on draw, so this may be null! */ + AssetShelf *active_shelf = shelf_regiondata.active_shelf; + + UI_view2d_region_reinit(®ion->v2d, V2D_COMMONVIEW_PANELS_UI, region->winx, region->winy); + + wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "View2D Buttons List", 0, 0); + WM_event_add_keymap_handler(®ion->handlers, keymap); + + region->v2d.scroll = V2D_SCROLL_RIGHT | V2D_SCROLL_VERTICAL_HIDE; + region->v2d.keepzoom |= V2D_LOCKZOOM_X | V2D_LOCKZOOM_Y; + region->v2d.keepofs |= V2D_KEEPOFS_Y; + region->v2d.keeptot |= V2D_KEEPTOT_STRICT; + + region->v2d.flag |= V2D_SNAP_TO_PAGESIZE_Y; + region->v2d.page_size_y = active_shelf ? ED_asset_shelf_tile_height(active_shelf->settings) : + asset_shelf_default_tile_height(); + + /* Ensure the view is snapped to a page still, especially for DPI changes. */ + UI_view2d_offset_y_snap_to_closest_page(®ion->v2d); +} + +static int main_region_padding_y() +{ + const uiStyle *style = UI_style_get_dpi(); + return style->buttonspacey / 2; +} + +static int main_region_padding_x() +{ + /* Use the same as the height, equal padding looks nice. */ + return main_region_padding_y(); +} + +int ED_asset_shelf_region_snap(const ARegion *region, const int size, const int axis) +{ + /* Only on Y axis. */ + if (axis != 1) { + return size; + } + + const RegionAssetShelf *shelf_regiondata = RegionAssetShelf::get_from_asset_shelf_region( + *region); + const AssetShelf *active_shelf = shelf_regiondata->active_shelf; + + /* Using scaled values only simplifies things. Simply divide the result by the scale again. */ + const int size_scaled = size * UI_SCALE_FAC; + + const float aspect = BLI_rctf_size_y(®ion->v2d.cur) / + (BLI_rcti_size_y(®ion->v2d.mask) + 1); + const float tile_height = (active_shelf ? ED_asset_shelf_tile_height(active_shelf->settings) : + asset_shelf_default_tile_height()) / + (IS_EQF(aspect, 0) ? 1.0f : aspect); + + const int region_padding = main_region_padding_y(); + + /* How many rows fit into the region (accounting for padding). */ + const int rows = std::max(1, int((size_scaled - 2 * region_padding) / tile_height)); + + const int new_size_scaled = (rows * tile_height + 2 * region_padding); + return new_size_scaled / UI_SCALE_FAC; +} + +int ED_asset_shelf_tile_width(const AssetShelfSettings &settings) +{ + return UI_preview_tile_size_x(settings.preview_size); +} + +int ED_asset_shelf_tile_height(const AssetShelfSettings &settings) +{ + return (settings.display_flag & ASSETSHELF_SHOW_NAMES) ? + UI_preview_tile_size_y(settings.preview_size) : + UI_preview_tile_size_y_no_label(settings.preview_size); +} + +static int asset_shelf_default_tile_height() +{ + return UI_preview_tile_size_x(shelf::DEFAULT_TILE_SIZE); +} + +int ED_asset_shelf_region_prefsizey() +{ + /* One row by default (plus padding). */ + return asset_shelf_default_tile_height() + 2 * main_region_padding_y(); +} + +/** + * Ensure the region height is snapped to the closest multiple of the row height. + */ +static void asset_shelf_region_snap_height_to_closest(ScrArea *area, + ARegion *region, + const AssetShelf &shelf) +{ + const int tile_height = ED_asset_shelf_tile_height(shelf.settings); + /* Increase the size by half a row, the snapping below shrinks it to a multiple of the row (plus + * paddings), effectively rounding it. */ + const int ceiled_size_y = region->sizey + ((tile_height / UI_SCALE_FAC) * 0.5); + const int new_size_y = ED_asset_shelf_region_snap(region, ceiled_size_y, 1); + + if (region->sizey != new_size_y) { + region->sizey = new_size_y; + area->flag |= AREA_FLAG_REGION_SIZE_UPDATE; + } +} + +/** + * The asset shelf always uses the "All" library for now. + */ +static const AssetLibraryReference &asset_shelf_library_ref() +{ + static AssetLibraryReference all_library_ref = {}; + all_library_ref.type = ASSET_LIBRARY_ALL; + all_library_ref.custom_library_index = -1; + return all_library_ref; +} + +void ED_asset_shelf_region_layout(const bContext *C, ARegion *region) +{ + const SpaceLink *space = CTX_wm_space_data(C); + const SpaceType *space_type = BKE_spacetype_from_id(space->spacetype); + + RegionAssetShelf *shelf_regiondata = RegionAssetShelf::get_from_asset_shelf_region(*region); + if (!shelf_regiondata) { + /* Regiondata should've been created by a previously called ED_asset_shelf_region_init(). */ + BLI_assert_unreachable(); + return; + } + + AssetShelf *active_shelf = update_active_shelf(*C, *space_type, *shelf_regiondata); + if (!active_shelf) { + return; + } + + const AssetLibraryReference &library_ref = asset_shelf_library_ref(); + + uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS); + + const uiStyle *style = UI_style_get_dpi(); + const int padding_y = main_region_padding_y(); + const int padding_x = main_region_padding_x(); + uiLayout *layout = UI_block_layout(block, + UI_LAYOUT_VERTICAL, + UI_LAYOUT_PANEL, + padding_x, + -padding_y, + region->winx - 2 * padding_x, + 0, + 0, + style); + + shelf::build_asset_view(*layout, library_ref, *active_shelf, *C, *region); + + int layout_height; + UI_block_layout_resolve(block, nullptr, &layout_height); + BLI_assert(layout_height <= 0); + UI_view2d_totRect_set(®ion->v2d, region->winx - 1, layout_height - padding_y); + UI_view2d_curRect_validate(®ion->v2d); + + asset_shelf_region_snap_height_to_closest(CTX_wm_area(C), region, *active_shelf); + + UI_block_end(C, block); +} + +void ED_asset_shelf_region_draw(const bContext *C, ARegion *region) +{ + ED_region_clear(C, region, TH_BACK); + + /* Set view2d view matrix for scrolling. */ + UI_view2d_view_ortho(®ion->v2d); + + /* View2D matrix might have changed due to dynamic sized regions. */ + UI_blocklist_update_window_matrix(C, ®ion->uiblocks); + + UI_blocklist_draw(C, ®ion->uiblocks); + + /* Restore view matrix. */ + UI_view2d_view_restore(C); + + UI_view2d_scrollers_draw(®ion->v2d, nullptr); +} + +void ED_asset_shelf_header_region_listen(const wmRegionListenerParams *params) +{ + asset_shelf_region_listen(params); +} + +void ED_asset_shelf_header_region_init(wmWindowManager * /*wm*/, ARegion *region) +{ + ED_region_header_init(region); +} + +void ED_asset_shelf_header_region(const bContext *C, ARegion *region) +{ + const SpaceLink *space = CTX_wm_space_data(C); + const SpaceType *space_type = BKE_spacetype_from_id(space->spacetype); + const ARegion *main_shelf_region = BKE_area_find_region_type(CTX_wm_area(C), + RGN_TYPE_ASSET_SHELF); + + RegionAssetShelf *shelf_regiondata = RegionAssetShelf::get_from_asset_shelf_region( + *main_shelf_region); + update_active_shelf(*C, *space_type, *shelf_regiondata); + + ED_region_header(C, region); +} + +int ED_asset_shelf_header_region_size() +{ + /* A little smaller than a regular header. */ + return ED_area_headersize() * 0.85f; +} + +void ED_asset_shelf_region_blend_read_data(BlendDataReader *reader, ARegion *region) +{ + RegionAssetShelf *shelf_regiondata = RegionAssetShelf::get_from_asset_shelf_region(*region); + if (!shelf_regiondata) { + return; + } + shelf::regiondata_blend_read_data(reader, &shelf_regiondata); + region->regiondata = shelf_regiondata; +} + +void ED_asset_shelf_region_blend_write(BlendWriter *writer, ARegion *region) +{ + RegionAssetShelf *shelf_regiondata = RegionAssetShelf::get_from_asset_shelf_region(*region); + if (!shelf_regiondata) { + return; + } + shelf::regiondata_blend_write(writer, shelf_regiondata); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Asset Shelf Context + * \{ */ + +int ED_asset_shelf_context(const bContext *C, const char *member, bContextDataResult *result) +{ + static const char *context_dir[] = { + "asset_shelf", + "asset_library_ref", + "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")) { + const ARegion *shelf_region = BKE_area_find_region_type(CTX_wm_area(C), RGN_TYPE_ASSET_SHELF); + if (!shelf_region) { + /* Called in wrong context, area doesn't have a shelf. */ + BLI_assert_unreachable(); + return CTX_RESULT_NO_DATA; + } + + if (shelf_region->flag & RGN_FLAG_POLL_FAILED) { + /* Don't return data when the region "doesn't exist" (poll failed). */ + return CTX_RESULT_NO_DATA; + } + + const RegionAssetShelf *shelf_regiondata = RegionAssetShelf::get_from_asset_shelf_region( + *shelf_region); + if (!shelf_regiondata) { + return CTX_RESULT_NO_DATA; + } + + CTX_data_pointer_set(result, &screen->id, &RNA_AssetShelf, shelf_regiondata->active_shelf); + + return CTX_RESULT_OK; + } + + if (CTX_data_equals(member, "asset_library_ref")) { + CTX_data_pointer_set(result, + &screen->id, + &RNA_AssetLibraryReference, + const_cast(&asset_shelf_library_ref())); + return CTX_RESULT_OK; + } + + /* XXX hack. Get the asset from the active item, but needs to be the file... */ + if (CTX_data_equals(member, "active_file")) { + const ARegion *region = CTX_wm_region(C); + const uiBut *but = UI_region_views_find_active_item_but(region); + 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; +} + +namespace blender::ed::asset::shelf { + +static PointerRNA active_shelf_ptr_from_context(const bContext *C) +{ + return CTX_data_pointer_get_type(C, "asset_shelf", &RNA_AssetShelf); +} + +AssetShelf *active_shelf_from_context(const bContext *C) +{ + PointerRNA shelf_settings_ptr = active_shelf_ptr_from_context(C); + return static_cast(shelf_settings_ptr.data); +} + +} // namespace blender::ed::asset::shelf + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Catalog toggle buttons + * \{ */ + +static uiBut *add_tab_button(uiBlock &block, StringRefNull name) +{ + const uiStyle *style = UI_style_get_dpi(); + const int string_width = UI_fontstyle_string_width(&style->widget, name.c_str()); + 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.c_str(), + 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_DOWN); + UI_but_flag_disable(but, UI_BUT_UNDO); + + return but; +} + +static void add_catalog_toggle_buttons(AssetShelfSettings &shelf_settings, uiLayout &layout) +{ + uiBlock *block = uiLayoutGetBlock(&layout); + + /* "All" tab. */ + { + uiBut *but = add_tab_button(*block, IFACE_("All")); + UI_but_func_set(but, [&shelf_settings](bContext &C) { + shelf::settings_set_all_catalog_active(shelf_settings); + shelf::send_redraw_notifier(C); + }); + UI_but_func_pushed_state_set(but, [&shelf_settings](const uiBut &) -> bool { + return shelf::settings_is_all_catalog_active(shelf_settings); + }); + } + + uiItemS(&layout); + + /* Regular catalog tabs. */ + shelf::settings_foreach_enabled_catalog_path( + shelf_settings, [&shelf_settings, block](const asset_system::AssetCatalogPath &path) { + uiBut *but = add_tab_button(*block, path.name()); + + UI_but_func_set(but, [&shelf_settings, path](bContext &C) { + shelf::settings_set_active_catalog(shelf_settings, path); + shelf::send_redraw_notifier(C); + }); + UI_but_func_pushed_state_set(but, [&shelf_settings, path](const uiBut &) -> bool { + return shelf::settings_is_active_catalog(shelf_settings, path); + }); + }); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Asset Shelf Header Region + * + * Implemented as HeaderType for #RGN_TYPE_ASSET_SHELF_HEADER. + * \{ */ + +static void asset_shelf_header_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); + + UI_block_emboss_set(block, UI_EMBOSS_NONE); + uiItemPopoverPanel(layout, C, "ASSETSHELF_PT_catalog_selector", "", ICON_COLLAPSEMENU); + UI_block_emboss_set(block, UI_EMBOSS); + + uiItemS(layout); + + PointerRNA shelf_ptr = shelf::active_shelf_ptr_from_context(C); + AssetShelf *shelf = static_cast(shelf_ptr.data); + if (shelf) { + add_catalog_toggle_buttons(shelf->settings, *layout); + } + + uiItemSpacer(layout); + + uiItemR(layout, &shelf_ptr, "search_filter", UI_ITEM_NONE, "", ICON_VIEWZOOM); + uiItemS(layout); + uiItemPopoverPanel(layout, C, "ASSETSHELF_PT_display", "", ICON_IMGDISPLAY); +} + +void ED_asset_shelf_header_regiontype_register(ARegionType *region_type, const int space_type) +{ + HeaderType *ht = MEM_cnew(__func__); + STRNCPY(ht->idname, "ASSETSHELF_HT_settings"); + ht->space_type = space_type; + ht->region_type = RGN_TYPE_ASSET_SHELF_HEADER; + ht->draw = asset_shelf_header_draw; + ht->poll = [](const bContext *C, HeaderType *) { + return asset_shelf_space_poll(C, CTX_wm_space_data(C)); + }; + + BLI_addtail(®ion_type->headertypes, ht); + + shelf::catalog_selector_panel_register(region_type); +} + +/** \} */ diff --git a/source/blender/editors/asset/intern/asset_shelf.hh b/source/blender/editors/asset/intern/asset_shelf.hh new file mode 100644 index 00000000000..4047379f54b --- /dev/null +++ b/source/blender/editors/asset/intern/asset_shelf.hh @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edasset + */ + +#pragma once + +#include "BLI_function_ref.hh" + +struct ARegion; +struct AssetLibraryReference; +struct AssetShelf; +struct AssetShelfSettings; +struct bContext; +struct BlendDataReader; +struct BlendWriter; +struct uiLayout; + +namespace blender::asset_system { +class AssetCatalogPath; +} + +namespace blender::ed::asset::shelf { + +constexpr short DEFAULT_TILE_SIZE = 64; + +void build_asset_view(uiLayout &layout, + const AssetLibraryReference &library_ref, + const AssetShelf &shelf, + const bContext &C, + ARegion ®ion); + +void catalog_selector_panel_register(struct ARegionType *region_type); + +AssetShelf *active_shelf_from_context(const bContext *C); + +void send_redraw_notifier(const bContext &C); + +/** + * Deep-copies \a shelf_regiondata into newly allocated memory. Must be freed using + * #regiondata_free(). + */ +struct RegionAssetShelf *regiondata_duplicate(const RegionAssetShelf *shelf_regiondata); +/** Frees the contained data and \a shelf_regiondata itself. */ +void regiondata_free(RegionAssetShelf **shelf_regiondata); +void regiondata_blend_write(struct BlendWriter *writer, + const struct RegionAssetShelf *shelf_regiondata); +void regiondata_blend_read_data(struct BlendDataReader *reader, + struct RegionAssetShelf **shelf_regiondata); + +/** + * Frees the contained data, not \a shelf_settings itself. + */ +void settings_free(AssetShelfSettings *settings); +void settings_blend_write(BlendWriter *writer, const AssetShelfSettings &settings); +void settings_blend_read_data(BlendDataReader *reader, AssetShelfSettings &settings); + +void settings_clear_enabled_catalogs(AssetShelfSettings &settings); +void settings_set_active_catalog(AssetShelfSettings &settings, + const asset_system::AssetCatalogPath &path); +void settings_set_all_catalog_active(AssetShelfSettings &settings); +bool settings_is_active_catalog(const AssetShelfSettings &settings, + const asset_system::AssetCatalogPath &path); +bool settings_is_all_catalog_active(const AssetShelfSettings &settings); +bool settings_is_catalog_path_enabled(const AssetShelfSettings &settings, + const asset_system::AssetCatalogPath &path); +void settings_set_catalog_path_enabled(AssetShelfSettings &settings, + const asset_system::AssetCatalogPath &path); + +void settings_foreach_enabled_catalog_path( + const AssetShelfSettings &settings, + FunctionRef fn); + +} // namespace blender::ed::asset::shelf diff --git a/source/blender/editors/asset/intern/asset_shelf_asset_view.cc b/source/blender/editors/asset/intern/asset_shelf_asset_view.cc new file mode 100644 index 00000000000..5966a5ddd12 --- /dev/null +++ b/source/blender/editors/asset/intern/asset_shelf_asset_view.cc @@ -0,0 +1,300 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edasset + * + * Grid-view showing all assets according to the giving shelf-type and settings. + */ + +#include "AS_asset_library.hh" +#include "AS_asset_representation.hh" + +#include "BKE_screen.h" + +#include "BLI_fnmatch.h" + +#include "DNA_asset_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "ED_asset_handle.h" +#include "ED_asset_list.h" +#include "ED_asset_list.hh" +#include "ED_asset_shelf.h" + +#include "UI_grid_view.hh" +#include "UI_interface.h" +#include "UI_interface.hh" +#include "UI_view2d.h" + +#include "RNA_access.h" +#include "RNA_prototypes.h" + +#include "WM_api.h" + +#include "asset_shelf.hh" + +namespace blender::ed::asset::shelf { + +class AssetView : public ui::AbstractGridView { + const AssetLibraryReference library_ref_; + const AssetShelf &shelf_; + /** Copy of the filter string from #AssetShelfSettings, with extra '*' added to the beginning and + * end of the string, for `fnmatch()` to work. */ + char search_string[sizeof(AssetShelfSettings::search_string) + 2] = ""; + std::optional catalog_filter_ = std::nullopt; + + friend class AssetViewItem; + friend class AssetDragController; + + public: + AssetView(const AssetLibraryReference &library_ref, const AssetShelf &shelf); + + void build_items() override; + bool begin_filtering(const bContext &C) const override; + + void set_catalog_filter(const std::optional &catalog_filter); +}; + +class AssetViewItem : public ui::PreviewGridItem { + AssetHandle asset_; + bool allow_asset_drag_ = true; + + public: + AssetViewItem(const AssetHandle &asset, + StringRef identifier, + StringRef label, + int preview_icon_id); + + void disable_asset_drag(); + void build_grid_tile(uiLayout &layout) const override; + void build_context_menu(bContext &C, uiLayout &column) const override; + bool is_filtered_visible() const override; + + std::unique_ptr create_drag_controller() const override; +}; + +class AssetDragController : public ui::AbstractViewItemDragController { + asset_system::AssetRepresentation &asset_; + + public: + AssetDragController(ui::AbstractGridView &view, asset_system::AssetRepresentation &asset); + + eWM_DragDataType get_drag_type() const override; + void *create_drag_data() const override; +}; + +AssetView::AssetView(const AssetLibraryReference &library_ref, const AssetShelf &shelf) + : library_ref_(library_ref), shelf_(shelf) +{ + if (shelf.settings.search_string[0]) { + BLI_strncpy_ensure_pad( + search_string, shelf.settings.search_string, '*', sizeof(search_string)); + } +} + +void AssetView::build_items() +{ + const asset_system::AssetLibrary *library = ED_assetlist_library_get_once_available( + library_ref_); + if (!library) { + return; + } + + ED_assetlist_iterate(library_ref_, [&](AssetHandle asset_handle) { + if (shelf_.type->asset_poll && !shelf_.type->asset_poll(shelf_.type, &asset_handle)) { + return true; + } + + const asset_system::AssetRepresentation *asset = ED_asset_handle_get_representation( + &asset_handle); + const AssetMetaData &asset_data = asset->get_metadata(); + + if (catalog_filter_ && !catalog_filter_->contains(asset_data.catalog_id)) { + /* Skip this asset. */ + return true; + } + + const bool show_names = (shelf_.settings.display_flag & ASSETSHELF_SHOW_NAMES); + + const StringRef identifier = asset->get_identifier().library_relative_identifier(); + const int preview_id = [this, &asset_handle]() -> int { + if (ED_assetlist_asset_image_is_loading(&library_ref_, &asset_handle)) { + return ICON_TEMP; + } + return ED_asset_handle_get_preview_or_type_icon_id(&asset_handle); + }(); + + AssetViewItem &item = add_item( + asset_handle, identifier, asset->get_name(), preview_id); + if (!show_names) { + item.hide_label(); + } + if (shelf_.type->flag & ASSET_SHELF_TYPE_FLAG_NO_ASSET_DRAG) { + item.disable_asset_drag(); + } + + return true; + }); +} + +bool AssetView::begin_filtering(const bContext &C) const +{ + const ScrArea *area = CTX_wm_area(&C); + LISTBASE_FOREACH (ARegion *, region, &area->regionbase) { + if (UI_textbutton_activate_rna(&C, region, &shelf_, "search_filter")) { + return true; + } + } + + return false; +} + +void AssetView::set_catalog_filter( + const std::optional &catalog_filter) +{ + if (catalog_filter) { + catalog_filter_.emplace(*catalog_filter); + } + else { + catalog_filter_ = std::nullopt; + } +} + +static std::optional catalog_filter_from_shelf_settings( + const AssetShelfSettings &shelf_settings, const asset_system::AssetLibrary &library) +{ + if (!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); +} + +/* ---------------------------------------------------------------------- */ + +AssetViewItem::AssetViewItem(const AssetHandle &asset, + StringRef identifier, + StringRef label, + int preview_icon_id) + : ui::PreviewGridItem(identifier, label, preview_icon_id), asset_(asset) +{ +} + +void AssetViewItem::disable_asset_drag() +{ + allow_asset_drag_ = false; +} + +void AssetViewItem::build_grid_tile(uiLayout &layout) const +{ + PointerRNA file_ptr; + RNA_pointer_create( + nullptr, + &RNA_FileSelectEntry, + /* XXX passing file pointer here, should be asset handle or asset representation. */ + const_cast(asset_.file_data), + &file_ptr); + + uiBlock *block = uiLayoutGetBlock(&layout); + UI_but_context_ptr_set( + block, reinterpret_cast(view_item_but_), "active_file", &file_ptr); + ui::PreviewGridItem::build_grid_tile(layout); +} + +void AssetViewItem::build_context_menu(bContext &C, uiLayout &column) const +{ + const AssetView &asset_view = dynamic_cast(get_view()); + const AssetShelfType &shelf_type = *asset_view.shelf_.type; + if (shelf_type.draw_context_menu) { + shelf_type.draw_context_menu(&C, &shelf_type, &asset_, &column); + } +} + +bool AssetViewItem::is_filtered_visible() const +{ + const AssetView &asset_view = dynamic_cast(get_view()); + if (asset_view.search_string[0] == '\0') { + return true; + } + + const StringRefNull asset_name = ED_asset_handle_get_representation(&asset_)->get_name(); + return fnmatch(asset_view.search_string, asset_name.c_str(), FNM_CASEFOLD) == 0; +} + +std::unique_ptr AssetViewItem::create_drag_controller() const +{ + if (!allow_asset_drag_) { + return nullptr; + } + asset_system::AssetRepresentation *asset = ED_asset_handle_get_representation(&asset_); + return std::make_unique(get_view(), *asset); +} + +/* ---------------------------------------------------------------------- */ + +void build_asset_view(uiLayout &layout, + const AssetLibraryReference &library_ref, + const AssetShelf &shelf, + const bContext &C, + ARegion ®ion) +{ + 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; + } + + const float tile_width = ED_asset_shelf_tile_width(shelf.settings); + const float tile_height = ED_asset_shelf_tile_height(shelf.settings); + BLI_assert(tile_width != 0); + BLI_assert(tile_height != 0); + + std::unique_ptr asset_view = std::make_unique(library_ref, shelf); + asset_view->set_catalog_filter(catalog_filter_from_shelf_settings(shelf.settings, *library)); + asset_view->set_tile_size(tile_width, tile_height); + + uiBlock *block = uiLayoutGetBlock(&layout); + ui::AbstractGridView *grid_view = UI_block_add_view( + *block, "asset shelf asset view", std::move(asset_view)); + + ui::GridViewBuilder builder(*block); + builder.build_grid_view(*grid_view, region.v2d, layout); +} + +/* ---------------------------------------------------------------------- */ +/* Dragging. */ + +AssetDragController::AssetDragController(ui::AbstractGridView &view, + asset_system::AssetRepresentation &asset) + : ui::AbstractViewItemDragController(view), asset_(asset) +{ +} + +eWM_DragDataType AssetDragController::get_drag_type() const +{ + return asset_.is_local_id() ? WM_DRAG_ID : WM_DRAG_ASSET; +} + +void *AssetDragController::create_drag_data() const +{ + ID *local_id = asset_.local_id(); + if (local_id) { + return static_cast(local_id); + } + + const eAssetImportMethod import_method = asset_.get_import_method().value_or( + ASSET_IMPORT_APPEND_REUSE); + + return WM_drag_create_asset_data(&asset_, import_method); +} + +} // namespace blender::ed::asset::shelf diff --git a/source/blender/editors/asset/intern/asset_shelf_catalog_selector.cc b/source/blender/editors/asset/intern/asset_shelf_catalog_selector.cc new file mode 100644 index 00000000000..a03317ce3e1 --- /dev/null +++ b/source/blender/editors/asset/intern/asset_shelf_catalog_selector.cc @@ -0,0 +1,216 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edasset + * + * Catalog tree-view to enable/disable catalogs in the asset shelf settings. + */ + +#include "AS_asset_catalog.hh" +#include "AS_asset_catalog_tree.hh" +#include "AS_asset_library.hh" + +#include "DNA_screen_types.h" + +#include "BKE_context.h" +#include "BKE_screen.h" + +#include "BLT_translation.h" + +#include "ED_asset_filter.hh" +#include "ED_asset_list.hh" + +#include "UI_interface.h" +#include "UI_interface.hh" +#include "UI_tree_view.hh" + +#include "WM_api.h" + +#include "asset_shelf.hh" + +namespace blender::ed::asset::shelf { + +class AssetCatalogSelectorTree : public ui::AbstractTreeView { + AssetShelf &shelf_; + AssetShelfSettings &shelf_settings_; + asset_system::AssetCatalogTree catalog_tree_; + + public: + class Item; + + AssetCatalogSelectorTree(asset_system::AssetLibrary &library, AssetShelf &shelf) + : shelf_(shelf), shelf_settings_(shelf_.settings) + { + catalog_tree_ = build_filtered_catalog_tree( + library, asset_system::all_library_reference(), [this](const AssetHandle asset_handle) { + return (!shelf_.type->asset_poll || shelf_.type->asset_poll(shelf_.type, &asset_handle)); + }); + } + + void build_tree() override + { + if (catalog_tree_.is_empty()) { + auto &item = add_tree_item(TIP_("No applicable assets found"), + ICON_INFO); + item.disable_interaction(); + return; + } + + catalog_tree_.foreach_root_item([this](asset_system::AssetCatalogTreeItem &catalog_item) { + Item &item = build_catalog_items_recursive(*this, catalog_item); + /* Uncollapse root items by default (user edits will override this just fine). */ + item.set_collapsed(false); + }); + } + + 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_( + settings_is_catalog_path_enabled(shelf_settings, catalog_item.catalog_path())) + { + disable_activatable(); + } + + bool is_catalog_path_enabled() const + { + return catalog_path_enabled_ != 0; + } + + bool has_enabled_in_subtree() + { + bool has_enabled = false; + + foreach_item_recursive( + [&has_enabled](const ui::AbstractTreeViewItem &abstract_item) { + const Item &item = dynamic_cast(abstract_item); + if (item.is_catalog_path_enabled()) { + has_enabled = true; + } + }, + IterOptions::SkipFiltered); + + return has_enabled; + } + + 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); + } + + uiLayout *subrow = uiLayoutRow(&row, false); + uiLayoutSetActive(subrow, catalog_path_enabled_); + uiItemL(subrow, catalog_item_.get_name().c_str(), ICON_NONE); + UI_block_layout_set_current(block, &row); + + uiBut *toggle_but = uiDefButC(block, + UI_BTYPE_CHECKBOX, + 0, + "", + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + (char *)&catalog_path_enabled_, + 0, + 0, + 0, + 0, + TIP_("Toggle catalog visibility in the asset shelf")); + UI_but_func_set(toggle_but, [&tree](bContext &C) { + tree.update_shelf_settings_from_enabled_catalogs(); + send_redraw_notifier(C); + }); + if (!is_catalog_path_enabled() && has_enabled_in_subtree()) { + UI_but_drawflag_enable(toggle_but, UI_BUT_INDETERMINATE); + } + UI_but_flag_disable(toggle_but, UI_BUT_UNDO); + } + }; +}; + +void AssetCatalogSelectorTree::update_shelf_settings_from_enabled_catalogs() +{ + 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()) { + settings_set_catalog_path_enabled(shelf_settings_, selector_tree_item.catalog_path()); + } + }); +} + +static void catalog_selector_panel_draw(const bContext *C, Panel *panel) +{ + const AssetLibraryReference *library_ref = CTX_wm_asset_library_ref(C); + asset_system::AssetLibrary *library = ED_assetlist_library_get_once_available(*library_ref); + AssetShelf *shelf = active_shelf_from_context(C); + + uiLayout *layout = panel->layout; + uiBlock *block = uiLayoutGetBlock(layout); + + uiItemL(layout, IFACE_("Catalogs"), ICON_NONE); + + if (!library || !shelf) { + return; + } + + ui::AbstractTreeView *tree_view = UI_block_add_view( + *block, + "asset catalog tree view", + std::make_unique(*library, *shelf)); + + ui::TreeViewBuilder::build_tree_view(*tree_view, *layout); +} + +void catalog_selector_panel_register(ARegionType *region_type) +{ + /* Uses global paneltype registry to allow usage as popover. So only register this once (may be + * called from multiple spaces). */ + if (WM_paneltype_find("ASSETSHELF_PT_catalog_selector", true)) { + return; + } + + PanelType *pt = MEM_cnew(__func__); + STRNCPY(pt->idname, "ASSETSHELF_PT_catalog_selector"); + STRNCPY(pt->label, N_("Catalog Selector")); + STRNCPY(pt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA); + pt->description = N_("Select catalogs to display in the asset shelf"); + pt->draw = catalog_selector_panel_draw; + BLI_addtail(®ion_type->paneltypes, pt); + WM_paneltype_add(pt); +} + +} // namespace blender::ed::asset::shelf diff --git a/source/blender/editors/asset/intern/asset_shelf_regiondata.cc b/source/blender/editors/asset/intern/asset_shelf_regiondata.cc new file mode 100644 index 00000000000..944743bd3ed --- /dev/null +++ b/source/blender/editors/asset/intern/asset_shelf_regiondata.cc @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edasset + */ + +#include "BLI_listbase.h" + +#include "BLO_read_write.h" + +#include "DNA_defs.h" +#include "DNA_screen_types.h" + +#include "asset_shelf.hh" + +RegionAssetShelf *RegionAssetShelf::get_from_asset_shelf_region(const ARegion ®ion) +{ + if (region.regiontype != RGN_TYPE_ASSET_SHELF) { + /* Should only be called on main asset shelf region. */ + BLI_assert_unreachable(); + return nullptr; + } + return static_cast(region.regiondata); +} + +namespace blender::ed::asset::shelf { + +RegionAssetShelf *regiondata_duplicate(const RegionAssetShelf *shelf_regiondata) +{ + static_assert(std::is_trivial_v, + "RegionAssetShelf needs to be trivial to allow freeing with MEM_freeN()"); + RegionAssetShelf *new_shelf_regiondata = MEM_new(__func__, *shelf_regiondata); + + BLI_listbase_clear(&new_shelf_regiondata->shelves); + LISTBASE_FOREACH (const AssetShelf *, shelf, &shelf_regiondata->shelves) { + AssetShelf *new_shelf = MEM_new("duplicate asset shelf", + blender::dna::shallow_copy(*shelf)); + new_shelf->settings = shelf->settings; + BLI_addtail(&new_shelf_regiondata->shelves, new_shelf); + if (shelf_regiondata->active_shelf == shelf) { + new_shelf_regiondata->active_shelf = new_shelf; + } + } + + return new_shelf_regiondata; +} + +void regiondata_free(RegionAssetShelf **shelf_regiondata) +{ + LISTBASE_FOREACH_MUTABLE (AssetShelf *, shelf, &(*shelf_regiondata)->shelves) { + MEM_delete(shelf); + } + MEM_SAFE_FREE(*shelf_regiondata); +} + +void regiondata_blend_write(BlendWriter *writer, const RegionAssetShelf *shelf_regiondata) +{ + BLO_write_struct(writer, RegionAssetShelf, shelf_regiondata); + LISTBASE_FOREACH (const AssetShelf *, shelf, &shelf_regiondata->shelves) { + BLO_write_struct(writer, AssetShelf, shelf); + settings_blend_write(writer, shelf->settings); + } +} + +void regiondata_blend_read_data(BlendDataReader *reader, RegionAssetShelf **shelf_regiondata) +{ + if (!*shelf_regiondata) { + return; + } + + BLO_read_data_address(reader, shelf_regiondata); + if ((*shelf_regiondata)->active_shelf) { + BLO_read_data_address(reader, &(*shelf_regiondata)->active_shelf); + } + + BLO_read_list(reader, &(*shelf_regiondata)->shelves); + LISTBASE_FOREACH (AssetShelf *, shelf, &(*shelf_regiondata)->shelves) { + shelf->type = nullptr; + settings_blend_read_data(reader, shelf->settings); + } +} + +} // namespace blender::ed::asset::shelf diff --git a/source/blender/editors/asset/intern/asset_shelf_settings.cc b/source/blender/editors/asset/intern/asset_shelf_settings.cc new file mode 100644 index 00000000000..26b6d4c7eba --- /dev/null +++ b/source/blender/editors/asset/intern/asset_shelf_settings.cc @@ -0,0 +1,144 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edasset + * + * Internal and external APIs for #AssetShelfSettings. + */ + +#include + +#include "AS_asset_catalog_path.hh" + +#include "DNA_screen_types.h" + +#include "BLO_read_write.h" + +#include "BLI_listbase.h" +#include "BLI_string.h" +#include "BLI_string_ref.hh" + +#include "asset_shelf.hh" + +using namespace blender; +using namespace blender::ed::asset; + +AssetShelfSettings::AssetShelfSettings() +{ + memset(this, 0, sizeof(*this)); +} + +AssetShelfSettings::AssetShelfSettings(const AssetShelfSettings &other) +{ + operator=(other); +} + +AssetShelfSettings &AssetShelfSettings::operator=(const AssetShelfSettings &other) +{ + /* Start with a shallow copy. */ + memcpy(this, &other, sizeof(AssetShelfSettings)); + + next = prev = nullptr; + + if (active_catalog_path) { + active_catalog_path = BLI_strdup(other.active_catalog_path); + } + BLI_listbase_clear(&enabled_catalog_paths); + + LISTBASE_FOREACH (LinkData *, catalog_path_item, &other.enabled_catalog_paths) { + LinkData *new_path_item = BLI_genericNodeN(BLI_strdup((char *)catalog_path_item->data)); + BLI_addtail(&enabled_catalog_paths, new_path_item); + } + return *this; +} + +AssetShelfSettings::~AssetShelfSettings() +{ + shelf::settings_clear_enabled_catalogs(*this); + MEM_delete(active_catalog_path); +} + +namespace blender::ed::asset::shelf { + +void settings_blend_write(BlendWriter *writer, const AssetShelfSettings &settings) +{ + BLO_write_struct(writer, AssetShelfSettings, &settings); + + LISTBASE_FOREACH (LinkData *, catalog_path_item, &settings.enabled_catalog_paths) { + BLO_write_struct(writer, LinkData, catalog_path_item); + BLO_write_string(writer, (const char *)catalog_path_item->data); + } + + BLO_write_string(writer, settings.active_catalog_path); +} + +void settings_blend_read_data(BlendDataReader *reader, AssetShelfSettings &settings) +{ + BLO_read_list(reader, &settings.enabled_catalog_paths); + LISTBASE_FOREACH (LinkData *, catalog_path_item, &settings.enabled_catalog_paths) { + BLO_read_data_address(reader, &catalog_path_item->data); + } + BLO_read_data_address(reader, &settings.active_catalog_path); +} + +void settings_clear_enabled_catalogs(AssetShelfSettings &settings) +{ + LISTBASE_FOREACH_MUTABLE (LinkData *, catalog_path_item, &settings.enabled_catalog_paths) { + MEM_freeN(catalog_path_item->data); + BLI_freelinkN(&settings.enabled_catalog_paths, catalog_path_item); + } + BLI_assert(BLI_listbase_is_empty(&settings.enabled_catalog_paths)); +} + +void settings_set_active_catalog(AssetShelfSettings &settings, + const asset_system::AssetCatalogPath &path) +{ + MEM_delete(settings.active_catalog_path); + settings.active_catalog_path = BLI_strdupn(path.c_str(), path.length()); +} + +void settings_set_all_catalog_active(AssetShelfSettings &settings) +{ + MEM_delete(settings.active_catalog_path); + settings.active_catalog_path = nullptr; +} + +bool settings_is_active_catalog(const AssetShelfSettings &settings, + const asset_system::AssetCatalogPath &path) +{ + return settings.active_catalog_path && settings.active_catalog_path == path.str(); +} + +bool settings_is_all_catalog_active(const AssetShelfSettings &settings) +{ + return !settings.active_catalog_path || !settings.active_catalog_path[0]; +} + +bool settings_is_catalog_path_enabled(const AssetShelfSettings &settings, + const asset_system::AssetCatalogPath &path) +{ + LISTBASE_FOREACH (LinkData *, catalog_path_item, &settings.enabled_catalog_paths) { + if (StringRef((const char *)catalog_path_item->data) == path.str()) { + return true; + } + } + return false; +} + +void settings_set_catalog_path_enabled(AssetShelfSettings &settings, + const asset_system::AssetCatalogPath &path) +{ + char *path_copy = BLI_strdupn(path.c_str(), path.length()); + BLI_addtail(&settings.enabled_catalog_paths, BLI_genericNodeN(path_copy)); +} + +void settings_foreach_enabled_catalog_path( + const AssetShelfSettings &settings, + FunctionRef fn) +{ + LISTBASE_FOREACH (LinkData *, catalog_path_item, &settings.enabled_catalog_paths) { + fn(asset_system::AssetCatalogPath((char *)catalog_path_item->data)); + } +} + +} // namespace blender::ed::asset::shelf diff --git a/source/blender/editors/include/ED_screen.h b/source/blender/editors/include/ED_screen.h index 5e2681f153f..11677c6affd 100644 --- a/source/blender/editors/include/ED_screen.h +++ b/source/blender/editors/include/ED_screen.h @@ -143,6 +143,10 @@ void ED_region_visibility_change_update_animated(struct bContext *C, struct ScrArea *area, struct ARegion *region); +void ED_region_clear(const struct bContext *C, + const struct ARegion *region, + int /*ThemeColorID*/ colorid); + void ED_region_info_draw(struct ARegion *region, const char *text, float fill_color[4], @@ -714,6 +718,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_grid_view.hh b/source/blender/editors/include/UI_grid_view.hh index 5c7131a459b..ffd46343a06 100644 --- a/source/blender/editors/include/UI_grid_view.hh +++ b/source/blender/editors/include/UI_grid_view.hh @@ -196,6 +196,7 @@ class PreviewGridItem : public AbstractGridViewItem { ActivateFn activate_fn_; /** See #set_is_active_fn() */ IsActiveFn is_active_fn_; + bool hide_label_ = false; public: std::string label{}; @@ -216,6 +217,8 @@ class PreviewGridItem : public AbstractGridViewItem { */ void set_is_active_fn(IsActiveFn fn); + void hide_label(); + private: std::optional should_be_active() const override; void on_activate(bContext &C) override; diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index 1084e616943..d31971cfe47 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -1814,12 +1814,12 @@ struct wmOperatorType *UI_but_extra_operator_icon_optype_get(struct uiButExtraOp struct PointerRNA *UI_but_extra_operator_icon_opptr_get(struct uiButExtraOpIcon *extra_icon); /** - * A decent size for a button (typically #UI_BTYPE_PREVIEW_TILE) to display a nicely readable - * preview with label in. + * Get the scaled size for a preview button (typically #UI_BTyPE_PREVIEW_TILE) based on \a + * size_px plus padding. */ -int UI_preview_tile_size_x(void); -int UI_preview_tile_size_y(void); -int UI_preview_tile_size_y_no_label(void); +int UI_preview_tile_size_x(const int size_px CPP_ARG_DEFAULT(96)); +int UI_preview_tile_size_y(const int size_px CPP_ARG_DEFAULT(96)); +int UI_preview_tile_size_y_no_label(const int size_px CPP_ARG_DEFAULT(96)); /* Autocomplete * @@ -3349,6 +3349,7 @@ void UI_view_item_context_menu_build(struct bContext *C, const uiViewItemHandle *item_handle, uiLayout *column); +bool UI_view_item_supports_drag(const uiViewItemHandle *item_); /** * Attempt to start dragging \a item_. This will not work if the view item doesn't * support dragging, i.e. if it won't create a drag-controller upon request. @@ -3367,6 +3368,7 @@ uiViewHandle *UI_region_view_find_at(const struct ARegion *region, const int xy[ uiViewItemHandle *UI_region_views_find_item_at(const struct ARegion *region, const int xy[2]) ATTR_NONNULL(); uiViewItemHandle *UI_region_views_find_active_item(const struct ARegion *region); +uiBut *UI_region_views_find_active_item_but(const struct ARegion *region); #ifdef __cplusplus } diff --git a/source/blender/editors/interface/interface.cc b/source/blender/editors/interface/interface.cc index 96dd7d3dfa8..7d826541a19 100644 --- a/source/blender/editors/interface/interface.cc +++ b/source/blender/editors/interface/interface.cc @@ -5011,26 +5011,26 @@ int UI_autocomplete_end(AutoComplete *autocpl, char *autoname) #define PREVIEW_TILE_PAD (0.15f * UI_UNIT_X) -int UI_preview_tile_size_x() +int UI_preview_tile_size_x(const int size_px) { const float pad = PREVIEW_TILE_PAD; - return round_fl_to_int((96.0f / 20.0f) * UI_UNIT_X + 2.0f * pad); + return round_fl_to_int((size_px / 20.0f) * UI_UNIT_X + 2.0f * pad); } -int UI_preview_tile_size_y() +int UI_preview_tile_size_y(const int size_px) { const uiStyle *style = UI_style_get(); const float font_height = style->widget.points * UI_SCALE_FAC; /* Add some extra padding to make things less tight vertically. */ const float pad = PREVIEW_TILE_PAD; - return round_fl_to_int(UI_preview_tile_size_y_no_label() + font_height + pad); + return round_fl_to_int(UI_preview_tile_size_y_no_label(size_px) + font_height + pad); } -int UI_preview_tile_size_y_no_label() +int UI_preview_tile_size_y_no_label(const int size_px) { const float pad = PREVIEW_TILE_PAD; - return round_fl_to_int((96.0f / 20.0f) * UI_UNIT_Y + 2.0f * pad); + return round_fl_to_int((size_px / 20.0f) * UI_UNIT_Y + 2.0f * pad); } #undef PREVIEW_TILE_PAD diff --git a/source/blender/editors/interface/interface_handlers.cc b/source/blender/editors/interface/interface_handlers.cc index 4f450b28db7..829d0e1dd0a 100644 --- a/source/blender/editors/interface/interface_handlers.cc +++ b/source/blender/editors/interface/interface_handlers.cc @@ -2126,7 +2126,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_HEADER)) { const int region_alignment = RGN_ALIGN_ENUM_FROM_MASK(data->region->alignment); int lock_axis = -1; @@ -4837,15 +4838,17 @@ static int ui_do_but_VIEW_ITEM(bContext *C, if (ui_but_extra_operator_icon_mouse_over_get(but, data->region, event)) { return WM_UI_HANDLER_BREAK; } - button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); - data->dragstartx = event->xy[0]; - data->dragstarty = event->xy[1]; + + if (UI_view_item_supports_drag(view_item_but->view_item)) { + button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); + data->dragstartx = event->xy[0]; + data->dragstarty = event->xy[1]; + } + else { + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + return WM_UI_HANDLER_CONTINUE; - - case KM_CLICK: - button_activate_state(C, but, BUTTON_STATE_EXIT); - return WM_UI_HANDLER_BREAK; - case KM_DBL_CLICK: data->cancel = true; UI_view_item_begin_rename(view_item_but->view_item); diff --git a/source/blender/editors/interface/interface_intern.hh b/source/blender/editors/interface/interface_intern.hh index 3da73d74ff7..5985452f204 100644 --- a/source/blender/editors/interface/interface_intern.hh +++ b/source/blender/editors/interface/interface_intern.hh @@ -1191,13 +1191,18 @@ void ui_draw_preview_item(const uiFontStyle *fstyle, /** * Version of #ui_draw_preview_item() that does not draw the menu background and item text based on * state. It just draws the preview and text directly. + * + * \param draw_as_icon: Instead of stretching the preview/icon to the available width/height, draw + * it at the standard icon size. Mono-icons will draw with \a text_col or the + * corresponding theme override for this type of icon. */ void ui_draw_preview_item_stateless(const uiFontStyle *fstyle, rcti *rect, const char *name, int iconid, const uchar text_col[4], - eFontStyle_Align text_align); + eFontStyle_Align text_align, + bool draw_as_icon = false); #define UI_TEXT_MARGIN_X 0.4f #define UI_POPUP_MARGIN (UI_SCALE_FAC * 12) diff --git a/source/blender/editors/interface/interface_widgets.cc b/source/blender/editors/interface/interface_widgets.cc index 9fbdafe8678..505027f921c 100644 --- a/source/blender/editors/interface/interface_widgets.cc +++ b/source/blender/editors/interface/interface_widgets.cc @@ -1344,6 +1344,32 @@ static void widget_draw_preview(BIFIconID icon, float alpha, const rcti *rect) } } +static void widget_draw_icon_centered(const BIFIconID icon, + const float aspect, + const float alpha, + const rcti *rect, + const uchar mono_color[4]) +{ + if (icon == ICON_NONE) { + return; + } + + const float size = ICON_DEFAULT_HEIGHT / (aspect * UI_INV_SCALE_FAC); + + if (size > 0) { + const int x = BLI_rcti_cent_x(rect) - size / 2; + const int y = BLI_rcti_cent_y(rect) - size / 2; + + const bTheme *btheme = UI_GetTheme(); + const float desaturate = 1.0 - btheme->tui.icon_saturation; + uchar color[4] = {mono_color[0], mono_color[1], mono_color[2], mono_color[3]}; + const bool has_theme = UI_icon_get_theme_color(int(icon), color); + + UI_icon_draw_ex( + x, y, icon, aspect * UI_INV_SCALE_FAC, alpha, desaturate, color, has_theme, nullptr); + } +} + static int ui_but_draw_menu_icon(const uiBut *but) { return (but->flag & UI_BUT_ICON_SUBMENU) && (but->emboss == UI_EMBOSS_PULLDOWN); @@ -4202,8 +4228,17 @@ static void widget_preview_tile(uiBut *but, widget_list_itembut(wcol, rect, state, roundboxalign, zoom); } - ui_draw_preview_item_stateless( - &UI_style_get()->widget, rect, but->drawstr, but->icon, wcol->text, UI_STYLE_TEXT_CENTER); + /* When the button is not tagged as having a preview icon, do regular icon drawing with the + * standard icon size. */ + const bool draw_as_icon = !(but->flag & UI_BUT_ICON_PREVIEW); + + ui_draw_preview_item_stateless(&UI_style_get()->widget, + rect, + but->drawstr, + but->icon, + wcol->text, + UI_STYLE_TEXT_CENTER, + draw_as_icon); } static void widget_optionbut(uiWidgetColors *wcol, @@ -5572,18 +5607,36 @@ void ui_draw_preview_item_stateless(const uiFontStyle *fstyle, const char *name, int iconid, const uchar text_col[4], - eFontStyle_Align text_align) + eFontStyle_Align text_align, + bool draw_as_icon) { rcti trect = *rect; const float text_size = UI_UNIT_Y; const bool has_text = name && name[0]; + float alpha = 1.0f; + + { + /* Special handling: Previews often want to show a loading icon while the preview is being + * loaded. Draw this with reduced opacity. */ + const bool is_loading_icon = iconid == ICON_TEMP; + if (is_loading_icon) { + alpha *= 0.5f; + draw_as_icon = true; + } + } + if (has_text) { /* draw icon in rect above the space reserved for the label */ rect->ymin += text_size; } GPU_blend(GPU_BLEND_ALPHA); - widget_draw_preview(BIFIconID(iconid), 1.0f, rect); + if (draw_as_icon) { + widget_draw_icon_centered(BIFIconID(iconid), 1.0f, alpha, rect, text_col); + } + else { + widget_draw_preview(BIFIconID(iconid), alpha, rect); + } GPU_blend(GPU_BLEND_NONE); if (!has_text) { diff --git a/source/blender/editors/interface/resources.cc b/source/blender/editors/interface/resources.cc index 972ab1fd8d1..a99753093ea 100644 --- a/source/blender/editors/interface/resources.cc +++ b/source/blender/editors/interface/resources.cc @@ -163,6 +163,12 @@ const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid) else if (g_theme_state.regionid == RGN_TYPE_EXECUTE) { cp = ts->execution_buts; } + else if (g_theme_state.regionid == RGN_TYPE_ASSET_SHELF) { + cp = ts->asset_shelf.back; + } + else if (g_theme_state.regionid == RGN_TYPE_ASSET_SHELF_HEADER) { + cp = ts->asset_shelf.header_back; + } else { cp = ts->button; } @@ -188,7 +194,11 @@ 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_HEADER)) + { cp = ts->header_text; } else { @@ -202,7 +212,11 @@ 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_HEADER)) + { cp = ts->header_text_hi; } else { @@ -216,7 +230,11 @@ 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_HEADER)) + { cp = ts->header_title; } else { diff --git a/source/blender/editors/interface/views/abstract_view_item.cc b/source/blender/editors/interface/views/abstract_view_item.cc index 5bd879b10c6..05d164e9caf 100644 --- a/source/blender/editors/interface/views/abstract_view_item.cc +++ b/source/blender/editors/interface/views/abstract_view_item.cc @@ -363,6 +363,11 @@ class ViewItemAPIWrapper { return !view.is_renaming() && item.supports_renaming(); } + static bool supports_drag(const AbstractViewItem &item) + { + return item.create_drag_controller() != nullptr; + } + static bool drag_start(bContext &C, const AbstractViewItem &item) { const std::unique_ptr drag_controller = @@ -436,6 +441,12 @@ void UI_view_item_context_menu_build(bContext *C, item.build_context_menu(*C, *column); } +bool UI_view_item_supports_drag(const uiViewItemHandle *item_) +{ + const AbstractViewItem &item = reinterpret_cast(*item_); + return ViewItemAPIWrapper::supports_drag(item); +} + bool UI_view_item_drag_start(bContext *C, const uiViewItemHandle *item_) { const AbstractViewItem &item = reinterpret_cast(*item_); diff --git a/source/blender/editors/interface/views/grid_view.cc b/source/blender/editors/interface/views/grid_view.cc index 8b3a015007e..6a6467362a4 100644 --- a/source/blender/editors/interface/views/grid_view.cc +++ b/source/blender/editors/interface/views/grid_view.cc @@ -9,6 +9,8 @@ #include #include +#include "BKE_icons.h" + #include "BLI_index_range.hh" #include "WM_types.h" @@ -413,7 +415,7 @@ void PreviewGridItem::build_grid_tile(uiLayout &layout) const uiBut *but = uiDefBut(block, UI_BTYPE_PREVIEW_TILE, 0, - label.c_str(), + hide_label_ ? "" : label.c_str(), 0, 0, style.tile_width, @@ -424,10 +426,17 @@ void PreviewGridItem::build_grid_tile(uiLayout &layout) const 0, 0, ""); + /* Draw icons that are not previews or images as normal icons with a fixed icon size. Otherwise + * they will be upscaled to the button size. Should probably be done by the widget code. */ + const int is_preview_flag = (BKE_icon_is_preview(preview_icon_id) || + BKE_icon_is_image(preview_icon_id)) ? + int(UI_BUT_ICON_PREVIEW) : + 0; ui_def_but_icon(but, preview_icon_id, /* NOLINTNEXTLINE: bugprone-suspicious-enum-usage */ - UI_HAS_ICON | UI_BUT_ICON_PREVIEW); + UI_HAS_ICON | is_preview_flag); + UI_but_func_tooltip_label_set(but, [this](const uiBut * /*but*/) { return label; }); but->emboss = UI_EMBOSS_NONE; } @@ -441,6 +450,11 @@ void PreviewGridItem::set_is_active_fn(IsActiveFn fn) is_active_fn_ = fn; } +void PreviewGridItem::hide_label() +{ + hide_label_ = true; +} + void PreviewGridItem::on_activate(bContext &C) { if (activate_fn_) { diff --git a/source/blender/editors/interface/views/interface_view.cc b/source/blender/editors/interface/views/interface_view.cc index f39ccda55e2..3a192b9050c 100644 --- a/source/blender/editors/interface/views/interface_view.cc +++ b/source/blender/editors/interface/views/interface_view.cc @@ -199,6 +199,11 @@ uiViewItemHandle *UI_region_views_find_active_item(const ARegion *region) return item_but->view_item; } +uiBut *UI_region_views_find_active_item_but(const ARegion *region) +{ + return ui_view_item_find_active(region); +} + namespace blender::ui { std::unique_ptr region_views_find_drop_target_at(const ARegion *region, diff --git a/source/blender/editors/screen/CMakeLists.txt b/source/blender/editors/screen/CMakeLists.txt index 29d3c4c77f6..bb2cc493614 100644 --- a/source/blender/editors/screen/CMakeLists.txt +++ b/source/blender/editors/screen/CMakeLists.txt @@ -3,7 +3,9 @@ # SPDX-License-Identifier: GPL-2.0-or-later set(INC + ../asset ../include + ../../asset_system ../../blenfont ../../blenkernel ../../blenloader diff --git a/source/blender/editors/screen/area.cc b/source/blender/editors/screen/area.cc index 2a3a742a4ee..54c7d93753c 100644 --- a/source/blender/editors/screen/area.cc +++ b/source/blender/editors/screen/area.cc @@ -34,6 +34,8 @@ #include "WM_toolsystem.h" #include "WM_types.h" +#include "ED_asset.h" +#include "ED_asset_shelf.h" #include "ED_buttons.h" #include "ED_screen.h" #include "ED_screen_types.h" @@ -1301,7 +1303,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_HEADER)) { return true; } @@ -1373,6 +1377,13 @@ static void region_rect_recursive( else if (region->regiontype == RGN_TYPE_FOOTER) { prefsizey = ED_area_footersize(); } + else if (region->regiontype == RGN_TYPE_ASSET_SHELF) { + prefsizey = region->sizey > 1 ? (UI_SCALE_FAC * (region->sizey + 0.5f)) : + ED_asset_shelf_region_prefsizey(); + } + else if (region->regiontype == RGN_TYPE_ASSET_SHELF_HEADER) { + prefsizey = ED_asset_shelf_header_region_size(); + } else if (ED_area_is_global(area)) { prefsizey = ED_region_global_size_y(); } @@ -1784,6 +1795,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) { @@ -2731,12 +2747,9 @@ static ThemeColorID region_background_color_id(const bContext *C, const ARegion } } -static void region_clear_color(const bContext *C, const ARegion *region, ThemeColorID colorid) +void ED_region_clear(const bContext *C, const ARegion *region, const int /*ThemeColorID*/ colorid) { - if (region->alignment == RGN_ALIGN_FLOAT) { - /* handle our own drawing. */ - } - else if (region->overlap) { + if (region->overlap) { /* view should be in pixelspace */ UI_view2d_view_restore(C); @@ -3181,7 +3194,7 @@ void ED_region_panels_draw(const bContext *C, ARegion *region) View2D *v2d = ®ion->v2d; if (region->alignment != RGN_ALIGN_FLOAT) { - region_clear_color( + ED_region_clear( C, region, (region->type->regionid == RGN_TYPE_PREVIEW) ? TH_PREVIEW_BACK : TH_BACK); } @@ -3483,7 +3496,7 @@ void ED_region_header_layout(const bContext *C, ARegion *region) void ED_region_header_draw(const bContext *C, ARegion *region) { /* clear */ - region_clear_color(C, region, region_background_color_id(C, region)); + ED_region_clear(C, region, region_background_color_id(C, region)); UI_view2d_view_ortho(®ion->v2d); diff --git a/source/blender/editors/screen/screen_edit.cc b/source/blender/editors/screen/screen_edit.cc index 79957a7c66b..293d494fe77 100644 --- a/source/blender/editors/screen/screen_edit.cc +++ b/source/blender/editors/screen/screen_edit.cc @@ -1500,7 +1500,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_HEADER)) { region->flag |= RGN_FLAG_HIDDEN; } diff --git a/source/blender/editors/screen/screen_ops.cc b/source/blender/editors/screen/screen_ops.cc index e75f5ab2299..b1cbf0c7bae 100644 --- a/source/blender/editors/screen/screen_ops.cc +++ b/source/blender/editors/screen/screen_ops.cc @@ -2684,14 +2684,20 @@ 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_HEADER))) { 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_HEADER))) { dist -= region->winy; } diff --git a/source/blender/editors/space_file/file_draw.cc b/source/blender/editors/space_file/file_draw.cc index c0b001e5ee5..454588756dc 100644 --- a/source/blender/editors/space_file/file_draw.cc +++ b/source/blender/editors/space_file/file_draw.cc @@ -361,7 +361,7 @@ static void file_draw_preview(const FileList *files, bool show_outline = !is_icon && (file->typeflag & (FILE_TYPE_IMAGE | FILE_TYPE_OBJECT_IO | FILE_TYPE_MOVIE | FILE_TYPE_BLENDER)); const bool is_offline = (file->attributes & FILE_ATTR_OFFLINE); - const bool is_loading = !filelist_is_ready(files) || file->flags & FILE_ENTRY_PREVIEW_LOADING; + const bool is_loading = filelist_file_is_preview_pending(files, file); BLI_assert(imb != nullptr); diff --git a/source/blender/editors/space_file/filelist.cc b/source/blender/editors/space_file/filelist.cc index 7afbd738958..0f9f415cff3 100644 --- a/source/blender/editors/space_file/filelist.cc +++ b/source/blender/editors/space_file/filelist.cc @@ -1160,6 +1160,14 @@ void filelist_file_get_full_path(const FileList *filelist, BLI_path_join(r_filepath, FILE_MAX_LIBEXTRA, root, file->relpath); } +bool filelist_file_is_preview_pending(const FileList *filelist, const FileDirEntry *file) +{ + /* Actual preview loading is only started after the filelist is loaded, so the file isn't flagged + * with #FILE_ENTRY_PREVIEW_LOADING yet. */ + const bool filelist_ready = filelist_is_ready(filelist); + return !filelist_ready || file->flags & FILE_ENTRY_PREVIEW_LOADING; +} + static FileDirEntry *filelist_geticon_get_file(FileList *filelist, const int index) { BLI_assert(G.background == false); diff --git a/source/blender/editors/space_file/filelist.hh b/source/blender/editors/space_file/filelist.hh index 4d1cd1e7d5e..0bb86591164 100644 --- a/source/blender/editors/space_file/filelist.hh +++ b/source/blender/editors/space_file/filelist.hh @@ -74,6 +74,7 @@ void filelist_free_icons(void); void filelist_file_get_full_path(const FileList *filelist, const FileDirEntry *file, char r_filepath[/*FILE_MAX_LIBEXTRA*/]); +bool filelist_file_is_preview_pending(const FileList *filelist, const FileDirEntry *file); ImBuf *filelist_getimage(FileList *filelist, int index); ImBuf *filelist_file_getimage(const FileDirEntry *file); ImBuf *filelist_geticon_image_ex(const FileDirEntry *file); diff --git a/source/blender/makesdna/DNA_screen_types.h b/source/blender/makesdna/DNA_screen_types.h index a7a2ee7e2a5..406dd2a438c 100644 --- a/source/blender/makesdna/DNA_screen_types.h +++ b/source/blender/makesdna/DNA_screen_types.h @@ -8,6 +8,8 @@ #pragma once +#include "BLI_utildefines.h" + #include "DNA_defs.h" #include "DNA_listBase.h" #include "DNA_vec_types.h" @@ -673,8 +675,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_HEADER = 15, -#define RGN_TYPE_NUM (RGN_TYPE_XR + 1) +#define RGN_TYPE_NUM (RGN_TYPE_ASSET_SHELF_HEADER + 1) } eRegion_Type; /** Use for function args. */ @@ -685,8 +689,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_HEADER))) != 0) /** #ARegion.alignment */ enum { @@ -764,6 +768,71 @@ enum { RGN_DRAW_EDITOR_OVERLAYS = 32, }; +typedef struct AssetShelfSettings { + struct AssetShelfSettings *next, *prev; + + ListBase enabled_catalog_paths; /* #LinkData */ + /** If not set (null or empty string), all assets will be displayed ("All" catalog behavior). */ + const char *active_catalog_path; + + /** For filtering assets displayed in the asset view. */ + char search_string[64]; + + short preview_size; + short display_flag; /* #AssetShelfSettings_DisplayFlag */ + char _pad1[4]; + +#ifdef __cplusplus + /* Zero initializes. */ + AssetShelfSettings(); + /* Proper deep copy. */ + AssetShelfSettings(const AssetShelfSettings &other); + AssetShelfSettings &operator=(const AssetShelfSettings &other); + ~AssetShelfSettings(); +#endif +} AssetShelfSettings; + +typedef struct AssetShelf { + DNA_DEFINE_CXX_METHODS(AssetShelf) + + struct AssetShelf *next, *prev; + + /** Identifier that matches the #AssetShelfType.idname this shelf was created with. Used to + * restore the #AssetShelf.type pointer below on file read. */ + char idname[64]; /* MAX_NAME */ + /** Runtime. */ + struct AssetShelfType *type; + + AssetShelfSettings settings; +} AssetShelf; + +/** + * Region-data for the main asset shelf region (#RGN_TYPE_ASSET_SHELF). Managed by the asset shelf + * internals. + * + * Contains storage for all previously activated asset shelf instances plus info on the currently + * active one (only one can be active at any time). + */ +typedef struct RegionAssetShelf { + /** Owning list of previously activated asset shelves. */ + ListBase shelves; + /** + * The currently active shelf, if any. Updated on redraw, so that context changes are reflected. + * Note that this may still be set even though the shelf isn't available anymore + * (#AssetShelfType.poll() fails). The pointer isn't necessarily unset when polling. + */ + AssetShelf *active_shelf; /* Non-owning. */ +#ifdef __cplusplus + static RegionAssetShelf *get_from_asset_shelf_region(const ARegion ®ion); +#endif +} RegionAssetShelf; + +/* #AssetShelfSettings.display_flag */ +typedef enum AssetShelfSettings_DisplayFlag { + ASSETSHELF_SHOW_NAMES = (1 << 0), +} AssetShelfSettings_DisplayFlag; +ENUM_OPERATORS(AssetShelfSettings_DisplayFlag, ASSETSHELF_SHOW_NAMES); + #ifdef __cplusplus } #endif diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index 60b9a0eb7b8..5905cbfb548 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -216,6 +216,11 @@ typedef struct ThemeUI { } ThemeUI; +typedef struct ThemeAssetShelf { + unsigned char header_back[4]; + unsigned char back[4]; +} ThemeAssetShelf; + /* try to put them all in one, if needed a special struct can be created as well * for example later on, when we introduce wire colors for ob types or so... */ @@ -271,6 +276,8 @@ typedef struct ThemeSpace { /* NOTE: cannot use name 'panel' because of DNA mapping old files. */ uiPanelColors panelcolors; + ThemeAssetShelf asset_shelf; + unsigned char shade1[4]; unsigned char shade2[4]; @@ -686,6 +693,8 @@ typedef struct UserDef_Experimental { char use_node_panels; char use_rotation_socket; char use_node_group_operators; + char use_asset_shelf; + char _pad[7]; /** `makesdna` does not allow empty structs. */ } UserDef_Experimental; diff --git a/source/blender/makesrna/intern/rna_screen.cc b/source/blender/makesrna/intern/rna_screen.cc index 3d4f301a9da..d390941882c 100644 --- a/source/blender/makesrna/intern/rna_screen.cc +++ b/source/blender/makesrna/intern/rna_screen.cc @@ -28,6 +28,8 @@ const EnumPropertyItem rna_enum_region_type_items[] = { {RGN_TYPE_UI, "UI", 0, "Sidebar", ""}, {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_HEADER, "ASSET_SHELF_HEADER", 0, "Asset Shelf Header", ""}, {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_space.cc b/source/blender/makesrna/intern/rna_space.cc index a2825eb329a..adda47311f0 100644 --- a/source/blender/makesrna/intern/rna_space.cc +++ b/source/blender/makesrna/intern/rna_space.cc @@ -859,6 +859,22 @@ static void rna_Space_show_region_hud_update(bContext *C, PointerRNA *ptr) rna_Space_bool_from_region_flag_update_by_type(C, ptr, RGN_TYPE_HUD, RGN_FLAG_HIDDEN_BY_USER); } +/* Asset Shelf Regions */ +static bool rna_Space_show_region_asset_shelf_get(PointerRNA *ptr) +{ + return !rna_Space_bool_from_region_flag_get_by_type(ptr, RGN_TYPE_ASSET_SHELF, RGN_FLAG_HIDDEN); +} +static void rna_Space_show_region_asset_shelf_set(PointerRNA *ptr, bool value) +{ + rna_Space_bool_from_region_flag_set_by_type(ptr, RGN_TYPE_ASSET_SHELF, RGN_FLAG_HIDDEN, !value); + rna_Space_bool_from_region_flag_set_by_type( + ptr, RGN_TYPE_ASSET_SHELF_HEADER, RGN_FLAG_HIDDEN, !value); +} +static void rna_Space_show_region_asset_shelf_update(bContext *C, PointerRNA *ptr) +{ + rna_Space_bool_from_region_flag_update_by_type(C, ptr, RGN_TYPE_ASSET_SHELF, RGN_FLAG_HIDDEN); +} + /** \} */ static bool rna_Space_view2d_sync_get(PointerRNA *ptr) @@ -3477,6 +3493,10 @@ static void rna_def_space_generic_show_region_toggles(StructRNA *srna, int regio region_type_mask &= ~(1 << RGN_TYPE_HUD); DEF_SHOW_REGION_PROPERTY(show_region_hud, "Adjust Last Operation", ""); } + if (region_type_mask & ((1 << RGN_TYPE_ASSET_SHELF) | (1 << RGN_TYPE_ASSET_SHELF_HEADER))) { + region_type_mask &= ~((1 << RGN_TYPE_ASSET_SHELF) | (1 << RGN_TYPE_ASSET_SHELF_HEADER)); + DEF_SHOW_REGION_PROPERTY(show_region_asset_shelf, "Asset Shelf", ""); + } BLI_assert(region_type_mask == 0); } diff --git a/source/blender/makesrna/intern/rna_ui.cc b/source/blender/makesrna/intern/rna_ui.cc index a6035c5faca..c2398bb4a49 100644 --- a/source/blender/makesrna/intern/rna_ui.cc +++ b/source/blender/makesrna/intern/rna_ui.cc @@ -1078,6 +1078,183 @@ 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(nullptr, shelf_type->rna_ext.srna, nullptr, &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(nullptr, &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(nullptr, shelf_type->rna_ext.srna, nullptr, &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 asset_shelf_draw_context_menu(const bContext *C, + const AssetShelfType *shelf_type, + const AssetHandle *asset, + uiLayout *layout) +{ + extern FunctionRNA rna_AssetShelf_draw_context_menu_func; + + PointerRNA ptr; + RNA_pointer_create(nullptr, shelf_type->rna_ext.srna, nullptr, &ptr); /* dummy */ + FunctionRNA *func = &rna_AssetShelf_draw_context_menu_func; /* RNA_struct_find_function(&ptr, + "draw_context_menu"); */ + + ParameterList list; + RNA_parameter_list_create(&list, &ptr, func); + RNA_parameter_set_lookup(&list, "context", &C); + RNA_parameter_set_lookup(&list, "asset_handle", &asset); + RNA_parameter_set_lookup(&list, "layout", &layout); + shelf_type->rna_ext.call((bContext *)C, &ptr, func, &list); + + RNA_parameter_list_free(&list); +} + +static bool rna_AssetShelf_unregister(Main * /*bmain*/, StructRNA *type) +{ + AssetShelfType *shelf_type = static_cast(RNA_struct_blender_type_get(type)); + + if (!shelf_type) { + return false; + } + + SpaceType *space_type = BKE_spacetype_from_id(shelf_type->space_type); + if (!space_type) { + return false; + } + + 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, nullptr); + + return true; +} + +static StructRNA *rna_AssetShelf_register(Main *bmain, + ReportList *reports, + void *data, + const char *identifier, + StructValidateFunc validate, + StructCallbackFunc call, + StructFreeFunc free) +{ + AssetShelfType dummy_shelf_type = {}; + AssetShelf dummy_shelf = {}; + PointerRNA dummy_shelf_ptr; + + /* setup dummy shelf & shelf type to store static properties in */ + dummy_shelf.type = &dummy_shelf_type; + RNA_pointer_create(nullptr, &RNA_AssetShelf, &dummy_shelf, &dummy_shelf_ptr); + + bool have_function[3]; + + /* validate the python class */ + if (validate(&dummy_shelf_ptr, data, have_function) != 0) { + return nullptr; + } + + 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 nullptr; + } + + SpaceType *space_type = BKE_spacetype_from_id(dummy_shelf_type.space_type); + if (!space_type) { + BLI_assert_unreachable(); + return nullptr; + } + + /* 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 nullptr; + } + if (!RNA_struct_bl_idname_ok_or_report(reports, dummy_shelf_type.idname, "_AST_")) { + return nullptr; + } + + /* Create the new shelf type. */ + AssetShelfType *shelf_type = static_cast( + 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 : nullptr; + shelf_type->asset_poll = have_function[1] ? asset_shelf_asset_poll : nullptr; + shelf_type->draw_context_menu = have_function[2] ? asset_shelf_draw_context_menu : nullptr; + + BLI_addtail(&space_type->asset_shelf_types, shelf_type); + + /* update while blender is running */ + WM_main_add_notifier(NC_WINDOW, nullptr); + + 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); @@ -1895,6 +2072,107 @@ static void rna_def_menu(BlenderRNA *brna) RNA_define_verify_sdna(true); } +static void rna_def_asset_shelf(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + static const EnumPropertyItem asset_shelf_flag_items[] = { + {ASSET_SHELF_TYPE_FLAG_NO_ASSET_DRAG, + "NO_ASSET_DRAG", + 0, + "No Asset Dragging", + "Disable the default asset dragging on drag events. Useful for implementing custom " + "dragging via custom key-map items"}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + srna = RNA_def_struct(brna, "AssetShelf", nullptr); + 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", nullptr); + 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, nullptr, "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, nullptr, "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"); + + prop = RNA_def_property(srna, "bl_options", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, nullptr, "type->flag"); + RNA_def_property_enum_items(prop, asset_shelf_flag_items); + RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL | PROP_ENUM_FLAG); + RNA_def_property_ui_text(prop, "Options", "Options for this asset shelf type"); + + PropertyRNA *parm; + FunctionRNA *func; + + func = RNA_def_function(srna, "poll", nullptr); + RNA_def_function_ui_description( + func, "If this method returns a non-null output, 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", true, "", "")); + parm = RNA_def_pointer(func, "context", "Context", "", ""); + RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED); + + func = RNA_def_function(srna, "asset_poll", nullptr); + RNA_def_function_ui_description( + func, + "Determine if an asset should be visible in the asset shelf. If this method returns a " + "non-null output, the asset will be visible"); + RNA_def_function_flag(func, FUNC_NO_SELF | FUNC_REGISTER_OPTIONAL); + RNA_def_function_return(func, RNA_def_boolean(func, "visible", true, "", "")); + parm = RNA_def_pointer(func, "asset_handle", "AssetHandle", "", ""); + RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED); + + func = RNA_def_function(srna, "draw_context_menu", nullptr); + RNA_def_function_ui_description( + func, "Draw UI elements into the context menu UI layout displayed on right click"); + RNA_def_function_flag(func, FUNC_NO_SELF | FUNC_REGISTER_OPTIONAL); + parm = RNA_def_pointer(func, "context", "Context", "", ""); + RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED); + parm = RNA_def_pointer(func, "asset_handle", "AssetHandle", "", ""); + RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED); + parm = RNA_def_pointer(func, "layout", "UILayout", "", ""); + RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED); + + prop = RNA_def_property(srna, "show_names", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "settings.display_flag", ASSETSHELF_SHOW_NAMES); + RNA_def_property_ui_text( + prop, + "Show Names", + "Show the asset name together with the preview. Otherwise only the preview will be visible"); + RNA_def_property_update(prop, NC_SPACE | ND_REGIONS_ASSET_SHELF, nullptr); + + prop = RNA_def_property(srna, "preview_size", PROP_INT, PROP_UNSIGNED); + RNA_def_property_int_sdna(prop, nullptr, "settings.preview_size"); + RNA_def_property_range(prop, 32, 256); + RNA_def_property_ui_text(prop, "Preview Size", "Size of the asset preview thumbnails in pixels"); + RNA_def_property_update(prop, NC_SPACE | ND_REGIONS_ASSET_SHELF, nullptr); + + prop = RNA_def_property(srna, "search_filter", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, nullptr, "settings.search_string"); + RNA_def_property_ui_text(prop, "Display Filter", "Filter assets by name"); + RNA_def_property_flag(prop, PROP_TEXTEDIT_UPDATE); + RNA_def_property_update(prop, NC_SPACE | ND_REGIONS_ASSET_SHELF, nullptr); +} + void RNA_def_ui(BlenderRNA *brna) { rna_def_ui_layout(brna); @@ -1902,6 +2180,7 @@ void RNA_def_ui(BlenderRNA *brna) rna_def_uilist(brna); rna_def_header(brna); rna_def_menu(brna); + rna_def_asset_shelf(brna); } #endif /* RNA_RUNTIME */ diff --git a/source/blender/makesrna/intern/rna_userdef.cc b/source/blender/makesrna/intern/rna_userdef.cc index 9ca0ebf7798..f1d08cd3a33 100644 --- a/source/blender/makesrna/intern/rna_userdef.cc +++ b/source/blender/makesrna/intern/rna_userdef.cc @@ -1948,6 +1948,34 @@ static void rna_def_userdef_theme_spaces_list_main(StructRNA *srna) RNA_def_property_ui_text(prop, "Theme Space List", "Settings for space list"); } +static void rna_def_userdef_theme_asset_shelf(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "ThemeAssetShelf", nullptr); + RNA_def_struct_clear_flag(srna, STRUCT_UNDO); + RNA_def_struct_ui_text(srna, "Theme Asset Shelf Color", "Theme settings for asset shelves"); + + prop = RNA_def_property(srna, "header_back", PROP_FLOAT, PROP_COLOR_GAMMA); + RNA_def_property_array(prop, 4); + RNA_def_property_ui_text(prop, "Header Background", ""); + RNA_def_property_update(prop, 0, "rna_userdef_theme_update"); + + prop = RNA_def_property(srna, "back", PROP_FLOAT, PROP_COLOR_GAMMA); + RNA_def_property_array(prop, 4); + RNA_def_property_ui_text(prop, "Main Region Background", ""); + RNA_def_property_update(prop, 0, "rna_userdef_theme_update"); +} + +static void UNUSED_FUNCTION(rna_def_userdef_theme_spaces_asset_shelf_main)(StructRNA *srna) +{ + PropertyRNA *prop = RNA_def_property(srna, "asset_shelf", PROP_POINTER, PROP_NONE); + RNA_def_property_flag(prop, PROP_NEVER_NULL); + RNA_def_property_struct_type(prop, "ThemeAssetShelf"); + RNA_def_property_ui_text(prop, "Asset Shelf", "Settings for asset shelf"); +} + static void rna_def_userdef_theme_spaces_vertex(StructRNA *srna) { PropertyRNA *prop; @@ -4387,6 +4415,7 @@ static void rna_def_userdef_dothemes(BlenderRNA *brna) rna_def_userdef_theme_space_generic(brna); rna_def_userdef_theme_space_gradient(brna); rna_def_userdef_theme_space_list_generic(brna); + rna_def_userdef_theme_asset_shelf(brna); rna_def_userdef_theme_space_view3d(brna); rna_def_userdef_theme_space_graph(brna); @@ -6761,6 +6790,13 @@ static void rna_def_userdef_experimental(BlenderRNA *brna) prop = RNA_def_property(srna, "use_node_group_operators", PROP_BOOLEAN, PROP_NONE); RNA_def_property_ui_text( prop, "Node Group Operators", "Enable using geometry nodes as edit operators"); + + prop = RNA_def_property(srna, "use_asset_shelf", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_ui_text(prop, + "Asset Shelf", + "Enables the asset shelf regions in the 3D view. Used by the Pose " + "Library add-on in Pose Mode only"); + RNA_def_property_update(prop, 0, "rna_userdef_ui_update"); } static void rna_def_userdef_addon_collection(BlenderRNA *brna, PropertyRNA *cprop) diff --git a/source/blender/python/intern/bpy_rna.cc b/source/blender/python/intern/bpy_rna.cc index 358c086c969..a98cec5762d 100644 --- a/source/blender/python/intern/bpy_rna.cc +++ b/source/blender/python/intern/bpy_rna.cc @@ -8891,7 +8891,7 @@ PyDoc_STRVAR(pyrna_register_class_doc, " :class:`bpy.types.Panel`, :class:`bpy.types.UIList`,\n" " :class:`bpy.types.Menu`, :class:`bpy.types.Header`,\n" " :class:`bpy.types.Operator`, :class:`bpy.types.KeyingSetInfo`,\n" - " :class:`bpy.types.RenderEngine`\n" + " :class:`bpy.types.RenderEngine`, :class:`bpy.types.AssetShelf`\n" " :type cls: class\n" " :raises ValueError:\n" " if the class is not a subclass of a registerable blender class.\n" diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h index f5e9ebcbdfc..1b532b524d9 100644 --- a/source/blender/windowmanager/WM_types.h +++ b/source/blender/windowmanager/WM_types.h @@ -495,6 +495,8 @@ typedef struct wmNotifier { #define ND_SPACE_CLIP (20 << 16) #define ND_SPACE_FILE_PREVIEW (21 << 16) #define ND_SPACE_SPREADSHEET (22 << 16) +/* Not a space itself, but a part of another space. */ +#define ND_REGIONS_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 4acb417e838..451c1e7252e 100644 --- a/source/blender/windowmanager/intern/wm_event_system.cc +++ b/source/blender/windowmanager/intern/wm_event_system.cc @@ -6162,6 +6162,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_HEADER, RGN_TYPE_TEMPORARY, RGN_TYPE_HUD)) { diff --git a/source/blender/windowmanager/intern/wm_keymap.cc b/source/blender/windowmanager/intern/wm_keymap.cc index 31e5396477b..812700c3acb 100644 --- a/source/blender/windowmanager/intern/wm_keymap.cc +++ b/source/blender/windowmanager/intern/wm_keymap.cc @@ -454,7 +454,10 @@ bool WM_keymap_poll(bContext *C, wmKeyMap *keymap) !BLI_str_endswith(keymap->idname, " (fallback)") && /* This is an exception which may be empty. * Longer term we might want a flag to indicate an empty key-map is intended. */ - !STREQ(keymap->idname, "Node Tool: Tweak")) + !STREQ(keymap->idname, "Node Tool: Tweak") && + /* Another exception: Asset shelf keymap is meant for add-ons to use, it's empty by + * default. */ + !STREQ(keymap->idname, "Asset Shelf")) { CLOG_WARN(WM_LOG_KEYMAPS, "empty keymap '%s'", keymap->idname); }