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
This commit is contained in:
Julian Eisel
2023-08-03 16:54:39 +02:00
parent 733f90bd24
commit 98142f5e35
51 changed files with 2429 additions and 53 deletions
+4
View File
@@ -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__ = ()
+1
View File
@@ -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.
+1
View File
@@ -11,6 +11,7 @@ if "bpy" in locals():
del reload
_modules = [
"asset_shelf",
"node_add_menu",
"node_add_menu_geometry",
"properties_animviz",
+39
View File
@@ -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)
+9
View File
@@ -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")),
+4
View File
@@ -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.
+41
View File
@@ -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);
+12
View File
@@ -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;
+17 -3
View File
@@ -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, &region->regiondata);
if (region->regiondata) {
if (spacetype == SPACE_VIEW3D) {
if (spacetype == SPACE_VIEW3D) {
if (region->regiontype == RGN_TYPE_WINDOW) {
BLO_read_data_address(reader, &region->regiondata);
RegionView3D *rv3d = static_cast<RegionView3D *>(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;
@@ -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
@@ -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<bool(const AssetHandle &)> is_asset_visible_fn);
AssetItemTree build_filtered_all_catalog_tree(
const AssetLibraryReference &library_ref,
const bContext &C,
@@ -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,
@@ -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);
/**
@@ -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
@@ -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<bool(const AssetHandle &)> is_asset_visible_fn)
{
Set<StringRef> 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,
@@ -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])
{
@@ -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<asset_system::AssetLibrary *>(filelist_asset_library(filelist_));
@@ -533,6 +539,13 @@ asset_system::AssetRepresentation *ED_assetlist_asset_get_by_index(
return reinterpret_cast<asset_system::AssetRepresentation *>(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);
@@ -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 <algorithm>
#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<AssetShelf>(__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<RegionAssetShelf *>(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<SpaceLink *>(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");
}
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(&region->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(&region->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(&region->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(&region->v2d.cur) /
(BLI_rcti_size_y(&region->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(&region->v2d, region->winx - 1, layout_height - padding_y);
UI_view2d_curRect_validate(&region->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(&region->v2d);
/* View2D matrix might have changed due to dynamic sized regions. */
UI_blocklist_update_window_matrix(C, &region->uiblocks);
UI_blocklist_draw(C, &region->uiblocks);
/* Restore view matrix. */
UI_view2d_view_restore(C);
UI_view2d_scrollers_draw(&region->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<AssetLibraryReference *>(&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<AssetShelf *>(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<AssetShelf *>(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<HeaderType>(__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(&region_type->headertypes, ht);
shelf::catalog_selector_panel_register(region_type);
}
/** \} */
@@ -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 &region);
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<void(const asset_system::AssetCatalogPath &catalog_path)> fn);
} // namespace blender::ed::asset::shelf
@@ -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<asset_system::AssetCatalogFilter> 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<asset_system::AssetCatalogFilter> &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<ui::AbstractViewItemDragController> 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<AssetViewItem>(
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<asset_system::AssetCatalogFilter> &catalog_filter)
{
if (catalog_filter) {
catalog_filter_.emplace(*catalog_filter);
}
else {
catalog_filter_ = std::nullopt;
}
}
static std::optional<asset_system::AssetCatalogFilter> 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<FileDirEntry *>(asset_.file_data),
&file_ptr);
uiBlock *block = uiLayoutGetBlock(&layout);
UI_but_context_ptr_set(
block, reinterpret_cast<uiBut *>(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<const AssetView &>(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<const AssetView &>(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<ui::AbstractViewItemDragController> 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<AssetDragController>(get_view(), *asset);
}
/* ---------------------------------------------------------------------- */
void build_asset_view(uiLayout &layout,
const AssetLibraryReference &library_ref,
const AssetShelf &shelf,
const bContext &C,
ARegion &region)
{
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<AssetView>(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<void *>(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
@@ -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<ui::BasicTreeViewItem>(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<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<const Item &>(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<AssetCatalogSelectorTree &>(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<AssetCatalogSelectorTree::Item &>(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<AssetCatalogSelectorTree>(*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<PanelType>(__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(&region_type->paneltypes, pt);
WM_paneltype_add(pt);
}
} // namespace blender::ed::asset::shelf
@@ -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 &region)
{
if (region.regiontype != RGN_TYPE_ASSET_SHELF) {
/* Should only be called on main asset shelf region. */
BLI_assert_unreachable();
return nullptr;
}
return static_cast<RegionAssetShelf *>(region.regiondata);
}
namespace blender::ed::asset::shelf {
RegionAssetShelf *regiondata_duplicate(const RegionAssetShelf *shelf_regiondata)
{
static_assert(std::is_trivial_v<RegionAssetShelf>,
"RegionAssetShelf needs to be trivial to allow freeing with MEM_freeN()");
RegionAssetShelf *new_shelf_regiondata = MEM_new<RegionAssetShelf>(__func__, *shelf_regiondata);
BLI_listbase_clear(&new_shelf_regiondata->shelves);
LISTBASE_FOREACH (const AssetShelf *, shelf, &shelf_regiondata->shelves) {
AssetShelf *new_shelf = MEM_new<AssetShelf>("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
@@ -0,0 +1,144 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edasset
*
* Internal and external APIs for #AssetShelfSettings.
*/
#include <type_traits>
#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<void(const asset_system::AssetCatalogPath &catalog_path)> 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
@@ -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. */
@@ -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<bool> should_be_active() const override;
void on_activate(bContext &C) override;
@@ -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
}
@@ -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
@@ -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);
@@ -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)
@@ -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) {
+21 -3
View File
@@ -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 {
@@ -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<AbstractViewItemDragController> 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<const AbstractViewItem &>(*item_);
return ViewItemAPIWrapper::supports_drag(item);
}
bool UI_view_item_drag_start(bContext *C, const uiViewItemHandle *item_)
{
const AbstractViewItem &item = reinterpret_cast<const AbstractViewItem &>(*item_);
@@ -9,6 +9,8 @@
#include <limits>
#include <stdexcept>
#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_) {
@@ -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<DropTargetInterface> region_views_find_drop_target_at(const ARegion *region,
@@ -3,7 +3,9 @@
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
../asset
../include
../../asset_system
../../blenfont
../../blenkernel
../../blenloader
+21 -8
View File
@@ -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(&region->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(&region->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 = &region->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(&region->v2d);
+3 -1
View File
@@ -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;
}
+10 -4
View File
@@ -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;
}
@@ -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);
@@ -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);
@@ -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);
+72 -3
View File
@@ -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 &region);
#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
@@ -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;
@@ -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", ""},
@@ -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);
}
+279
View File
@@ -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<AssetShelfType *>(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<AssetShelfType *>(
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 */
@@ -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)
+1 -1
View File
@@ -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"
+2
View File
@@ -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
@@ -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))
{
@@ -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);
}