Asset Browser: Support dragging assets into catalogs

With this it is possible to select any number of assets in the Asset
Browser and drag them into catalogs. The assets will be moved to that
catalog then. However, this will only work in the "Current File" asset
library, since that is the only library that allows changing assets,
which is what's done here.

While dragging assets over the tree row, a tooltip is shown explaining
what's going to happen.

In preparation to this, the new UI tree-view API was already extended
with custom drop support, see 4ee2d9df42.

----

Changes here to the `wmDrag` code were needed to support dragging multiple
assets. Some of it is considered temporary because a) a proper #AssetHandle
design should replace some ugly parts of this patch and b) the multi-item
support in `wmDrag` isn't that great yet. The entire API will have to be
written anyway (see D4071).

Maniphest Tasks: T91573

Differential Revision: https://developer.blender.org/D12713

Reviewed by: Sybren Stüvel
This commit is contained in:
Julian Eisel 2021-10-03 23:58:20 +02:00
parent 3b1a243039
commit c4dca65228
Notes: blender-bot 2023-02-14 05:43:04 +01:00
Referenced by issue #91573, Support dragging assets into catalogs
14 changed files with 260 additions and 27 deletions

View File

@ -1101,6 +1101,7 @@ context_type_map = {
"scene": ("Scene", False),
"sculpt_object": ("Object", False),
"selectable_objects": ("Object", True),
"selected_asset_files": ("FileSelectEntry", True),
"selected_bones": ("EditBone", True),
"selected_editable_bones": ("EditBone", True),
"selected_editable_fcurves": ("FCurve", True),

View File

@ -181,10 +181,12 @@ class AssetCatalogTreeItem {
AssetCatalogTreeItem(StringRef name,
CatalogID catalog_id,
StringRef simple_name,
const AssetCatalogTreeItem *parent = nullptr);
CatalogID get_catalog_id() const;
StringRef get_name() const;
StringRefNull get_simple_name() const;
StringRefNull get_name() const;
/** Return the full catalog path, defined as the name of this catalog prefixed by the full
* catalog path of its parent and a separator. */
AssetCatalogPath catalog_path() const;
@ -201,6 +203,8 @@ class AssetCatalogTreeItem {
/** The user visible name of this component. */
CatalogPathComponent name_;
CatalogID catalog_id_;
/** Copy of #AssetCatalog::simple_name. */
std::string simple_name_;
/** Pointer back to the parent item. Used to reconstruct the hierarchy from an item (e.g. to
* build a path). */

View File

@ -426,8 +426,9 @@ void AssetCatalogService::create_missing_catalogs()
AssetCatalogTreeItem::AssetCatalogTreeItem(StringRef name,
CatalogID catalog_id,
StringRef simple_name,
const AssetCatalogTreeItem *parent)
: name_(name), catalog_id_(catalog_id), parent_(parent)
: name_(name), catalog_id_(catalog_id), simple_name_(simple_name), parent_(parent)
{
}
@ -436,11 +437,16 @@ CatalogID AssetCatalogTreeItem::get_catalog_id() const
return catalog_id_;
}
StringRef AssetCatalogTreeItem::get_name() const
StringRefNull AssetCatalogTreeItem::get_name() const
{
return name_;
}
StringRefNull AssetCatalogTreeItem::get_simple_name() const
{
return simple_name_;
}
AssetCatalogPath AssetCatalogTreeItem::catalog_path() const
{
AssetCatalogPath current_path = name_;
@ -482,8 +488,10 @@ void AssetCatalogTree::insert_item(const AssetCatalog &catalog)
/* Insert new tree element - if no matching one is there yet! */
auto [key_and_item, was_inserted] = current_item_children->emplace(
component_name,
AssetCatalogTreeItem(
component_name, is_last_component ? catalog.catalog_id : nil_id, parent));
AssetCatalogTreeItem(component_name,
is_last_component ? catalog.catalog_id : nil_id,
is_last_component ? catalog.simple_name : "",
parent));
AssetCatalogTreeItem &item = key_and_item->second;
/* If full path of this catalog already exists as parent path of a previously read catalog,

View File

@ -6226,12 +6226,7 @@ void UI_but_drag_set_asset(uiBut *but,
struct ImBuf *imb,
float scale)
{
wmDragAsset *asset_drag = MEM_mallocN(sizeof(*asset_drag), "wmDragAsset");
BLI_strncpy(asset_drag->name, ED_asset_handle_get_name(asset), sizeof(asset_drag->name));
asset_drag->path = path;
asset_drag->id_type = ED_asset_handle_get_id_type(asset);
asset_drag->import_type = import_type;
wmDragAsset *asset_drag = WM_drag_create_asset_data(asset, path, import_type);
/* FIXME: This is temporary evil solution to get scene/viewlayer/etc in the copy callback of the
* #wmDropBox.

View File

@ -2166,6 +2166,12 @@ static bool ui_but_drag_init(bContext *C,
BLI_rctf_size_x(&but->rect),
BLI_rctf_size_y(&but->rect));
}
/* Special feature for assets: We add another drag item that supports multiple assets. It
* gets the assets from context. */
if (ELEM(but->dragtype, WM_DRAG_ASSET, WM_DRAG_ID)) {
WM_event_start_drag(C, ICON_NONE, WM_DRAG_ASSET_LIST, NULL, 0, WM_DRAG_NOP);
}
}
return true;
}

View File

@ -25,6 +25,7 @@
#include "DNA_space_types.h"
#include "BKE_asset.h"
#include "BKE_asset_catalog.hh"
#include "BKE_asset_library.hh"
@ -43,6 +44,7 @@
#include "WM_types.h"
#include "file_intern.h"
#include "filelist.h"
using namespace blender;
using namespace blender::bke;
@ -53,11 +55,14 @@ class AssetCatalogTreeView : public ui::AbstractTreeView {
/** The asset catalog tree this tree-view represents. */
bke::AssetCatalogTree *catalog_tree_;
FileAssetSelectParams *params_;
SpaceFile &space_file_;
friend class AssetCatalogTreeViewItem;
public:
AssetCatalogTreeView(::AssetLibrary *library, FileAssetSelectParams *params);
AssetCatalogTreeView(::AssetLibrary *library,
FileAssetSelectParams *params,
SpaceFile &space_file);
void build_tree() override;
@ -117,6 +122,70 @@ class AssetCatalogTreeViewItem : public ui::BasicTreeViewItem {
RNA_string_set(props, "catalog_id", catalog_id_str_buffer);
}
}
bool has_droppable_item(const wmDrag &drag) const
{
const ListBase *asset_drags = WM_drag_asset_list_get(&drag);
/* There needs to be at least one asset from the current file. */
LISTBASE_FOREACH (const wmDragAssetListItem *, asset_item, asset_drags) {
if (!asset_item->is_external) {
return true;
}
}
return false;
}
bool can_drop(const wmDrag &drag) const override
{
if (drag.type != WM_DRAG_ASSET_LIST) {
return false;
}
return has_droppable_item(drag);
}
std::string drop_tooltip(const bContext & /*C*/,
const wmDrag &drag,
const wmEvent & /*event*/) const override
{
const ListBase *asset_drags = WM_drag_asset_list_get(&drag);
const bool is_multiple_assets = !BLI_listbase_is_single(asset_drags);
/* Don't try to be smart by dynamically adding the 's' for the plural. Just makes translation
* harder, so use full literals. */
std::string basic_tip = is_multiple_assets ? TIP_("Move assets to catalog") :
TIP_("Move asset to catalog");
return basic_tip + ": " + catalog_item_.get_name() + " (" +
catalog_item_.catalog_path().str() + ")";
}
bool on_drop(const wmDrag &drag) override
{
const ListBase *asset_drags = WM_drag_asset_list_get(&drag);
if (!asset_drags) {
return false;
}
const AssetCatalogTreeView &tree_view = static_cast<const AssetCatalogTreeView &>(
get_tree_view());
LISTBASE_FOREACH (wmDragAssetListItem *, asset_item, asset_drags) {
if (asset_item->is_external) {
/* Only internal assets can be modified! */
continue;
}
BKE_asset_metadata_catalog_id_set(asset_item->asset_data.local_id->asset_data,
catalog_item_.get_catalog_id(),
catalog_item_.get_simple_name().c_str());
/* Trigger re-run of filtering to update visible assets. */
filelist_tag_needs_filtering(tree_view.space_file_.files);
file_select_deselect_all(&tree_view.space_file_, FILE_SEL_SELECTED | FILE_SEL_HIGHLIGHTED);
}
return true;
}
};
/** Only reason this isn't just `BasicTreeViewItem` is to add a '+' icon for adding a root level
@ -140,8 +209,12 @@ class AssetCatalogTreeViewAllItem : public ui::BasicTreeViewItem {
}
};
AssetCatalogTreeView::AssetCatalogTreeView(::AssetLibrary *library, FileAssetSelectParams *params)
: catalog_tree_(BKE_asset_library_get_catalog_tree(library)), params_(params)
AssetCatalogTreeView::AssetCatalogTreeView(::AssetLibrary *library,
FileAssetSelectParams *params,
SpaceFile &space_file)
: catalog_tree_(BKE_asset_library_get_catalog_tree(library)),
params_(params),
space_file_(space_file)
{
}
@ -216,6 +289,7 @@ bool AssetCatalogTreeView::is_active_catalog(CatalogID catalog_id) const
void file_create_asset_catalog_tree_view_in_layout(::AssetLibrary *asset_library,
uiLayout *layout,
SpaceFile *space_file,
FileAssetSelectParams *params)
{
uiBlock *block = uiLayoutGetBlock(layout);
@ -223,7 +297,8 @@ void file_create_asset_catalog_tree_view_in_layout(::AssetLibrary *asset_library
ui::AbstractTreeView *tree_view = UI_block_add_view(
*block,
"asset catalog tree view",
std::make_unique<ed::asset_browser::AssetCatalogTreeView>(asset_library, params));
std::make_unique<ed::asset_browser::AssetCatalogTreeView>(
asset_library, params, *space_file));
ui::TreeViewBuilder builder(*block);
builder.build_tree_view(*tree_view);

View File

@ -165,6 +165,7 @@ void file_path_to_ui_path(const char *path, char *r_pathi, int max_size);
void file_create_asset_catalog_tree_view_in_layout(struct AssetLibrary *asset_library,
struct uiLayout *layout,
struct SpaceFile *space_file,
struct FileAssetSelectParams *params);
#ifdef __cplusplus

View File

@ -238,7 +238,7 @@ static void file_panel_asset_catalog_buttons_draw(const bContext *C, Panel *pane
FileAssetSelectParams *params = ED_fileselect_get_asset_params(sfile);
BLI_assert(params != NULL);
file_create_asset_catalog_tree_view_in_layout(asset_library, panel->layout, params);
file_create_asset_catalog_tree_view_in_layout(asset_library, panel->layout, sfile, params);
}
void file_tools_region_panels_register(ARegionType *art)

View File

@ -490,7 +490,6 @@ static void filelist_readjob_main_assets(struct FileListReadJob *job_params,
static int groupname_to_code(const char *group);
static uint64_t groupname_to_filter_id(const char *group);
static void filelist_tag_needs_filtering(FileList *filelist);
static void filelist_cache_clear(FileListEntryCache *cache, size_t new_size);
/* ********** Sort helpers ********** */
@ -970,7 +969,7 @@ static bool is_filtered_main_assets(FileListInternEntry *file,
is_filtered_asset(file, filter);
}
static void filelist_tag_needs_filtering(FileList *filelist)
void filelist_tag_needs_filtering(FileList *filelist)
{
filelist->flags |= FL_NEED_FILTERING;
}

View File

@ -76,6 +76,7 @@ void filelist_set_asset_catalog_filter_options(
struct FileList *filelist,
eFileSel_Params_AssetCatalogVisibility catalog_visibility,
const struct bUUID *catalog_id);
void filelist_tag_needs_filtering(struct FileList *filelist);
void filelist_filter(struct FileList *filelist);
void filelist_setlibrary(struct FileList *filelist,
const struct AssetLibraryReference *asset_library_ref);

View File

@ -894,6 +894,7 @@ const char *file_context_dir[] = {
"active_file",
"selected_files",
"asset_library_ref",
"selected_asset_files",
"id",
NULL,
};
@ -951,6 +952,24 @@ static int /*eContextResult*/ file_context(const bContext *C,
result, &screen->id, &RNA_AssetLibraryReference, &asset_params->asset_library_ref);
return CTX_RESULT_OK;
}
/** TODO temporary AssetHandle design: For now this returns the file entry. Would be better if it
* was `"selected_assets"` and returned the assets (e.g. as `AssetHandle`) directly. See comment
* for #AssetHandle for more info. */
if (CTX_data_equals(member, "selected_asset_files")) {
const int num_files_filtered = filelist_files_ensure(sfile->files);
for (int file_index = 0; file_index < num_files_filtered; file_index++) {
if (filelist_entry_is_selected(sfile->files, file_index)) {
FileDirEntry *entry = filelist_file(sfile->files, file_index);
if (entry->asset_data) {
CTX_data_list_add(result, &screen->id, &RNA_FileSelectEntry, entry);
}
}
}
CTX_data_type_set(result, CTX_DATA_TYPE_COLLECTION);
return CTX_RESULT_OK;
}
if (CTX_data_equals(member, "id")) {
const FileDirEntry *file = filelist_file(sfile->files, params->active_file);
if (file == NULL) {

View File

@ -41,6 +41,8 @@ extern "C" {
#endif
struct ARegion;
struct AssetHandle;
struct AssetLibraryReference;
struct GHashIterator;
struct GPUViewport;
struct ID;
@ -741,6 +743,9 @@ struct ID *WM_drag_get_local_ID(const struct wmDrag *drag, short idcode);
struct ID *WM_drag_get_local_ID_from_event(const struct wmEvent *event, short idcode);
bool WM_drag_is_ID_type(const struct wmDrag *drag, int idcode);
wmDragAsset *WM_drag_create_asset_data(const struct AssetHandle *asset,
const char *path,
int import_type);
struct wmDragAsset *WM_drag_get_asset_data(const struct wmDrag *drag, int idcode);
struct ID *WM_drag_get_local_ID_or_import_from_asset(const struct wmDrag *drag, int idcode);
@ -748,6 +753,12 @@ void WM_drag_free_imported_drag_ID(struct Main *bmain,
struct wmDrag *drag,
struct wmDropBox *drop);
void WM_drag_add_asset_list_item(wmDrag *drag,
const struct bContext *C,
const struct AssetLibraryReference *asset_library_ref,
const struct AssetHandle *asset);
const ListBase *WM_drag_asset_list_get(const wmDrag *drag);
const char *WM_drag_get_item_name(struct wmDrag *drag);
/* Set OpenGL viewport and scissor */

View File

@ -915,12 +915,16 @@ typedef void (*wmPaintCursorDraw)(struct bContext *C, int, int, void *customdata
#define WM_DRAG_ID 0
#define WM_DRAG_ASSET 1
#define WM_DRAG_RNA 2
#define WM_DRAG_PATH 3
#define WM_DRAG_NAME 4
#define WM_DRAG_VALUE 5
#define WM_DRAG_COLOR 6
#define WM_DRAG_DATASTACK 7
/** The user is dragging multiple assets. This is only supported in few specific cases, proper
* multi-item support for dragging isn't supported well yet. Therefore this is kept separate from
* #WM_DRAG_ASSET. */
#define WM_DRAG_ASSET_LIST 2
#define WM_DRAG_RNA 3
#define WM_DRAG_PATH 4
#define WM_DRAG_NAME 5
#define WM_DRAG_VALUE 6
#define WM_DRAG_COLOR 7
#define WM_DRAG_DATASTACK 8
typedef enum wmDragFlags {
WM_DRAG_NOP = 0,
@ -953,6 +957,25 @@ typedef struct wmDragAsset {
struct bContext *evil_C;
} wmDragAsset;
/**
* For some specific cases we support dragging multiple assets (#WM_DRAG_ASSET_LIST). There is no
* proper support for dragging multiple items in the `wmDrag`/`wmDrop` API yet, so this is really
* just to enable specific features for assets.
*
* This struct basically contains a tagged union to either store a local ID pointer, or information
* about an externally stored asset.
*/
typedef struct wmDragAssetListItem {
struct wmDragAssetListItem *next, *prev;
union {
struct ID *local_id;
wmDragAsset *external_info;
} asset_data;
bool is_external;
} wmDragAssetListItem;
typedef char *(*WMDropboxTooltipFunc)(struct bContext *,
struct wmDrag *,
const struct wmEvent *event,
@ -979,6 +1002,8 @@ typedef struct wmDrag {
/** List of wmDragIDs, all are guaranteed to have the same ID type. */
ListBase ids;
/** List of `wmDragAssetListItem`s. */
ListBase asset_items;
} wmDrag;
/**

View File

@ -46,6 +46,8 @@
#include "BLO_readfile.h"
#include "ED_asset.h"
#include "GPU_shader.h"
#include "GPU_state.h"
#include "GPU_viewport.h"
@ -66,6 +68,8 @@
static ListBase dropboxes = {NULL, NULL};
static void wm_drag_free_asset_data(wmDragAsset **asset_data);
/* drop box maps are stored global for now */
/* these are part of blender's UI/space specs, and not like keymaps */
/* when editors become configurable, they can add own dropbox definitions */
@ -176,6 +180,19 @@ wmDrag *WM_event_start_drag(
drag->poin = poin;
drag->flags |= WM_DRAG_FREE_DATA;
break;
/* The asset-list case is special: We get multiple assets from context and attach them to the
* drag item. */
case WM_DRAG_ASSET_LIST: {
const AssetLibraryReference *asset_library = CTX_wm_asset_library_ref(C);
ListBase asset_file_links = CTX_data_collection_get(C, "selected_asset_files");
LISTBASE_FOREACH (const CollectionPointerLink *, link, &asset_file_links) {
const FileDirEntry *asset_file = link->ptr.data;
const AssetHandle asset_handle = {asset_file};
WM_drag_add_asset_list_item(drag, C, asset_library, &asset_handle);
}
BLI_freelistN(&asset_file_links);
break;
}
default:
drag->poin = poin;
break;
@ -202,10 +219,12 @@ void WM_drag_data_free(int dragtype, void *poin)
/* Not too nice, could become a callback. */
if (dragtype == WM_DRAG_ASSET) {
wmDragAsset *asset_drag = poin;
MEM_freeN((void *)asset_drag->path);
wmDragAsset *asset_data = poin;
wm_drag_free_asset_data(&asset_data);
}
else {
MEM_freeN(poin);
}
MEM_freeN(poin);
}
void WM_drag_free(wmDrag *drag)
@ -214,6 +233,12 @@ void WM_drag_free(wmDrag *drag)
WM_drag_data_free(drag->type, drag->poin);
}
BLI_freelistN(&drag->ids);
LISTBASE_FOREACH_MUTABLE (wmDragAssetListItem *, asset_item, &drag->asset_items) {
if (asset_item->is_external) {
wm_drag_free_asset_data(&asset_item->asset_data.external_info);
}
BLI_freelinkN(&drag->asset_items, asset_item);
}
MEM_freeN(drag);
}
@ -378,6 +403,27 @@ bool WM_drag_is_ID_type(const wmDrag *drag, int idcode)
return WM_drag_get_local_ID(drag, idcode) || WM_drag_get_asset_data(drag, idcode);
}
/**
* \note: Does not store \a asset in any way, so it's fine to pass a temporary.
*/
wmDragAsset *WM_drag_create_asset_data(const AssetHandle *asset, const char *path, int import_type)
{
wmDragAsset *asset_drag = MEM_mallocN(sizeof(*asset_drag), "wmDragAsset");
BLI_strncpy(asset_drag->name, ED_asset_handle_get_name(asset), sizeof(asset_drag->name));
asset_drag->path = path;
asset_drag->id_type = ED_asset_handle_get_id_type(asset);
asset_drag->import_type = import_type;
return asset_drag;
}
static void wm_drag_free_asset_data(wmDragAsset **asset_data)
{
MEM_freeN((char *)(*asset_data)->path);
MEM_SAFE_FREE(*asset_data);
}
wmDragAsset *WM_drag_get_asset_data(const wmDrag *drag, int idcode)
{
if (drag->type != WM_DRAG_ASSET) {
@ -495,6 +541,48 @@ void WM_drag_free_imported_drag_ID(struct Main *bmain, wmDrag *drag, wmDropBox *
}
}
/**
* \note: Does not store \a asset in any way, so it's fine to pass a temporary.
*/
void WM_drag_add_asset_list_item(
wmDrag *drag,
/* Context only needed for the hack in #ED_asset_handle_get_full_library_path(). */
const bContext *C,
const AssetLibraryReference *asset_library_ref,
const AssetHandle *asset)
{
if (drag->type != WM_DRAG_ASSET_LIST) {
return;
}
/* No guarantee that the same asset isn't added twice. */
/* Add to list. */
wmDragAssetListItem *drag_asset = MEM_callocN(sizeof(*drag_asset), __func__);
ID *local_id = ED_asset_handle_get_local_id(asset);
if (local_id) {
drag_asset->is_external = false;
drag_asset->asset_data.local_id = local_id;
}
else {
char asset_blend_path[FILE_MAX_LIBEXTRA];
ED_asset_handle_get_full_library_path(C, asset_library_ref, asset, asset_blend_path);
drag_asset->is_external = true;
drag_asset->asset_data.external_info = WM_drag_create_asset_data(
asset, BLI_strdup(asset_blend_path), FILE_ASSET_IMPORT_APPEND);
}
BLI_addtail(&drag->asset_items, drag_asset);
}
const ListBase *WM_drag_asset_list_get(const wmDrag *drag)
{
if (drag->type != WM_DRAG_ASSET_LIST) {
return NULL;
}
return &drag->asset_items;
}
/* ************** draw ***************** */
static void wm_drop_operator_draw(const char *name, int x, int y)