diff options
23 files changed, 638 insertions, 107 deletions
diff --git a/release/scripts/modules/bpy_extras/asset_utils.py b/release/scripts/modules/bpy_extras/asset_utils.py index 2cd5dddefbc..c7ebdc1d5e1 100644 --- a/release/scripts/modules/bpy_extras/asset_utils.py +++ b/release/scripts/modules/bpy_extras/asset_utils.py @@ -52,19 +52,12 @@ class AssetBrowserPanel: bl_space_type = 'FILE_BROWSER' @classmethod - def poll(cls, context): + def asset_browser_panel_poll(cls, context): return SpaceAssetInfo.is_asset_browser_poll(context) - -class AssetBrowserSpecificCategoryPanel(AssetBrowserPanel): - asset_categories = set() # Set of strings like 'ANIMATIONS', see `asset_category_items` in rna_space.c - @classmethod def poll(cls, context): - return ( - SpaceAssetInfo.is_asset_browser_poll(context) - and context.space_data.params.asset_category in cls.asset_categories - ) + return cls.asset_browser_panel_poll(context) class AssetMetaDataPanel: diff --git a/release/scripts/startup/bl_ui/space_filebrowser.py b/release/scripts/startup/bl_ui/space_filebrowser.py index b47404fd727..5dd8c69f3d5 100644 --- a/release/scripts/startup/bl_ui/space_filebrowser.py +++ b/release/scripts/startup/bl_ui/space_filebrowser.py @@ -648,30 +648,6 @@ class ASSETBROWSER_MT_select(AssetBrowserMenu, Menu): layout.operator("file.select_box") -class ASSETBROWSER_PT_navigation_bar(asset_utils.AssetBrowserPanel, Panel): - bl_label = "Asset Navigation" - bl_region_type = 'TOOLS' - bl_options = {'HIDE_HEADER'} - - @classmethod - def poll(cls, context): - return ( - asset_utils.AssetBrowserPanel.poll(context) and - context.preferences.experimental.use_extended_asset_browser - ) - - def draw(self, context): - layout = self.layout - - space_file = context.space_data - - col = layout.column() - - col.scale_x = 1.3 - col.scale_y = 1.3 - col.prop(space_file.params, "asset_category", expand=True) - - class ASSETBROWSER_PT_metadata(asset_utils.AssetBrowserPanel, Panel): bl_region_type = 'TOOL_PROPS' bl_label = "Asset Metadata" @@ -821,7 +797,6 @@ classes = ( ASSETBROWSER_MT_editor_menus, ASSETBROWSER_MT_view, ASSETBROWSER_MT_select, - ASSETBROWSER_PT_navigation_bar, ASSETBROWSER_PT_metadata, ASSETBROWSER_PT_metadata_preview, ASSETBROWSER_PT_metadata_details, diff --git a/source/blender/blenkernel/BKE_asset_library.hh b/source/blender/blenkernel/BKE_asset_library.hh index fc5e137dd3e..1dc02f7aa9b 100644 --- a/source/blender/blenkernel/BKE_asset_library.hh +++ b/source/blender/blenkernel/BKE_asset_library.hh @@ -48,3 +48,7 @@ struct AssetLibrary { }; } // namespace blender::bke + +blender::bke::AssetCatalogService *BKE_asset_library_get_catalog_service( + const ::AssetLibrary *library); +blender::bke::AssetCatalogTree *BKE_asset_library_get_catalog_tree(const ::AssetLibrary *library); diff --git a/source/blender/blenkernel/intern/asset_library.cc b/source/blender/blenkernel/intern/asset_library.cc index 1086efe45fd..27e66ee5725 100644 --- a/source/blender/blenkernel/intern/asset_library.cc +++ b/source/blender/blenkernel/intern/asset_library.cc @@ -68,6 +68,29 @@ bool BKE_asset_library_find_suitable_root_path_from_main(const Main *bmain, char return BKE_asset_library_find_suitable_root_path_from_path(bmain->name, r_library_path); } +blender::bke::AssetCatalogService *BKE_asset_library_get_catalog_service( + const ::AssetLibrary *library_c) +{ + if (library_c == nullptr) { + return nullptr; + } + + const blender::bke::AssetLibrary &library = reinterpret_cast<const blender::bke::AssetLibrary &>( + *library_c); + return library.catalog_service.get(); +} + +blender::bke::AssetCatalogTree *BKE_asset_library_get_catalog_tree(const ::AssetLibrary *library) +{ + blender::bke::AssetCatalogService *catalog_service = BKE_asset_library_get_catalog_service( + library); + if (catalog_service == nullptr) { + return nullptr; + } + + return catalog_service->get_catalog_tree(); +} + namespace blender::bke { void AssetLibrary::load(StringRefNull library_root_directory) diff --git a/source/blender/editors/asset/CMakeLists.txt b/source/blender/editors/asset/CMakeLists.txt index 31c07580570..b6657bfca63 100644 --- a/source/blender/editors/asset/CMakeLists.txt +++ b/source/blender/editors/asset/CMakeLists.txt @@ -31,6 +31,7 @@ set(INC_SYS ) set(SRC + intern/asset_catalog.cc intern/asset_filter.cc intern/asset_handle.cc intern/asset_library_reference.cc @@ -40,6 +41,7 @@ set(SRC intern/asset_ops.cc intern/asset_temp_id_consumer.cc + ED_asset_catalog.hh ED_asset_filter.h ED_asset_handle.h ED_asset_library.h diff --git a/source/blender/editors/asset/ED_asset_catalog.hh b/source/blender/editors/asset/ED_asset_catalog.hh new file mode 100644 index 00000000000..cffd7728a60 --- /dev/null +++ b/source/blender/editors/asset/ED_asset_catalog.hh @@ -0,0 +1,35 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup edasset + */ + +#pragma once + +#include "BKE_asset_catalog.hh" + +#include "BLI_string_ref.hh" + +struct AssetLibrary; +namespace blender::bke { +class AssetCatalog; +} // namespace blender::bke + +blender::bke::AssetCatalog *ED_asset_catalog_add(AssetLibrary *library, + blender::StringRefNull name, + blender::StringRef parent_path = nullptr); +void ED_asset_catalog_remove(AssetLibrary *library, const blender::bke::CatalogID &catalog_id); diff --git a/source/blender/editors/asset/intern/asset_catalog.cc b/source/blender/editors/asset/intern/asset_catalog.cc new file mode 100644 index 00000000000..202d4234051 --- /dev/null +++ b/source/blender/editors/asset/intern/asset_catalog.cc @@ -0,0 +1,87 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup edasset + */ + +#include "BKE_asset_catalog.hh" +#include "BKE_asset_library.hh" + +#include "BLI_string_utils.h" + +#include "ED_asset_catalog.hh" + +using namespace blender; +using namespace blender::bke; + +struct CatalogUniqueNameFnData { + const AssetCatalogService &catalog_service; + StringRef parent_path; +}; + +static std::string to_full_path(StringRef parent_path, StringRef name) +{ + return parent_path.is_empty() ? + std::string(name) : + std::string(parent_path) + AssetCatalogService::PATH_SEPARATOR + name; +} + +static bool catalog_name_exists_fn(void *arg, const char *name) +{ + CatalogUniqueNameFnData &fn_data = *static_cast<CatalogUniqueNameFnData *>(arg); + std::string fullpath = to_full_path(fn_data.parent_path, name); + return fn_data.catalog_service.find_catalog_by_path(fullpath); +} + +static std::string catalog_name_ensure_unique(AssetCatalogService &catalog_service, + StringRefNull name, + StringRef parent_path) +{ + CatalogUniqueNameFnData fn_data = {catalog_service, parent_path}; + + char unique_name[NAME_MAX] = ""; + BLI_uniquename_cb( + catalog_name_exists_fn, &fn_data, name.c_str(), '.', unique_name, sizeof(unique_name)); + + return unique_name; +} + +AssetCatalog *ED_asset_catalog_add(::AssetLibrary *library, + StringRefNull name, + StringRef parent_path) +{ + bke::AssetCatalogService *catalog_service = BKE_asset_library_get_catalog_service(library); + if (!catalog_service) { + return nullptr; + } + + std::string unique_name = catalog_name_ensure_unique(*catalog_service, name, parent_path); + std::string fullpath = to_full_path(parent_path, unique_name); + + return catalog_service->create_catalog(fullpath); +} + +void ED_asset_catalog_remove(::AssetLibrary *library, const CatalogID &catalog_id) +{ + bke::AssetCatalogService *catalog_service = BKE_asset_library_get_catalog_service(library); + if (!catalog_service) { + BLI_assert_unreachable(); + return; + } + + catalog_service->delete_catalog(catalog_id); +} diff --git a/source/blender/editors/asset/intern/asset_ops.cc b/source/blender/editors/asset/intern/asset_ops.cc index a18b7649060..5424bae77b4 100644 --- a/source/blender/editors/asset/intern/asset_ops.cc +++ b/source/blender/editors/asset/intern/asset_ops.cc @@ -18,13 +18,18 @@ * \ingroup edasset */ +#include "BKE_asset_catalog.hh" #include "BKE_context.h" #include "BKE_lib_id.h" #include "BKE_report.h" +#include "BLI_string_ref.hh" #include "BLI_vector.hh" #include "ED_asset.h" +#include "ED_asset_catalog.hh" +/* XXX needs access to the file list, should all be done via the asset system in future. */ +#include "ED_fileselect.h" #include "RNA_access.h" #include "RNA_define.h" @@ -32,6 +37,8 @@ #include "WM_api.h" #include "WM_types.h" +using namespace blender; + /* -------------------------------------------------------------------- */ using PointerRNAVec = blender::Vector<PointerRNA>; @@ -372,10 +379,91 @@ static void ASSET_OT_list_refresh(struct wmOperatorType *ot) /* -------------------------------------------------------------------- */ +static bool asset_catalog_operator_poll(bContext *C) +{ + const SpaceFile *sfile = CTX_wm_space_file(C); + return asset_operation_poll(C) && sfile && ED_fileselect_active_asset_library_get(sfile); +} + +static int asset_catalog_new_exec(bContext *C, wmOperator *op) +{ + SpaceFile *sfile = CTX_wm_space_file(C); + struct AssetLibrary *asset_library = ED_fileselect_active_asset_library_get(sfile); + char *parent_path = RNA_string_get_alloc(op->ptr, "parent_path", nullptr, 0, nullptr); + + ED_asset_catalog_add(asset_library, "Catalog", parent_path); + + MEM_freeN(parent_path); + + WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr); + + return OPERATOR_FINISHED; +} + +static void ASSET_OT_catalog_new(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "New Asset Catalog"; + ot->description = "Create a new catalog to put assets in"; + ot->idname = "ASSET_OT_catalog_new"; + + /* api callbacks */ + ot->exec = asset_catalog_new_exec; + ot->poll = asset_catalog_operator_poll; + + RNA_def_string(ot->srna, + "parent_path", + nullptr, + 0, + "Parent Path", + "Optional path defining the location to put the new catalog under"); +} + +static int asset_catalog_delete_exec(bContext *C, wmOperator *op) +{ + SpaceFile *sfile = CTX_wm_space_file(C); + struct AssetLibrary *asset_library = ED_fileselect_active_asset_library_get(sfile); + char *catalog_id_str = RNA_string_get_alloc(op->ptr, "catalog_id", nullptr, 0, nullptr); + bke::CatalogID catalog_id; + if (!BLI_uuid_parse_string(&catalog_id, catalog_id_str)) { + return OPERATOR_CANCELLED; + } + + ED_asset_catalog_remove(asset_library, catalog_id); + + MEM_freeN(catalog_id_str); + + WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr); + + return OPERATOR_FINISHED; +} + +static void ASSET_OT_catalog_delete(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Delete Asset Catalog"; + ot->description = + "Remove an asset catalog from the asset library (contained assets will not be affected and " + "show up as unassigned)"; + ot->idname = "ASSET_OT_catalog_delete"; + + /* api callbacks */ + ot->exec = asset_catalog_delete_exec; + ot->invoke = WM_operator_confirm; + ot->poll = asset_catalog_operator_poll; + + RNA_def_string(ot->srna, "catalog_id", nullptr, 0, "Catalog ID", "ID of the catalog to delete"); +} + +/* -------------------------------------------------------------------- */ + void ED_operatortypes_asset(void) { WM_operatortype_append(ASSET_OT_mark); WM_operatortype_append(ASSET_OT_clear); + WM_operatortype_append(ASSET_OT_catalog_new); + WM_operatortype_append(ASSET_OT_catalog_delete); + WM_operatortype_append(ASSET_OT_list_refresh); } diff --git a/source/blender/editors/include/ED_fileselect.h b/source/blender/editors/include/ED_fileselect.h index 82057c726a5..423d619f41a 100644 --- a/source/blender/editors/include/ED_fileselect.h +++ b/source/blender/editors/include/ED_fileselect.h @@ -142,6 +142,7 @@ void ED_fileselect_exit(struct wmWindowManager *wm, struct SpaceFile *sfile); bool ED_fileselect_is_file_browser(const struct SpaceFile *sfile); bool ED_fileselect_is_asset_browser(const struct SpaceFile *sfile); +struct AssetLibrary *ED_fileselect_active_asset_library_get(const struct SpaceFile *sfile); struct ID *ED_fileselect_active_asset_get(const struct SpaceFile *sfile); /* Activate the file that corresponds to the given ID. diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index f7842270746..106f6166760 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -2761,7 +2761,8 @@ void UI_interface_tag_script_reload(void); /* Support click-drag motion which presses the button and closes a popover (like a menu). */ #define USE_UI_POPOVER_ONCE -bool UI_tree_view_item_is_active(uiTreeViewItemHandle *item_); +bool UI_tree_view_item_is_active(const uiTreeViewItemHandle *item); +bool UI_tree_view_item_matches(const uiTreeViewItemHandle *a, const uiTreeViewItemHandle *b); #ifdef __cplusplus } diff --git a/source/blender/editors/include/UI_tree_view.hh b/source/blender/editors/include/UI_tree_view.hh index fac880a0a67..81a614cd195 100644 --- a/source/blender/editors/include/UI_tree_view.hh +++ b/source/blender/editors/include/UI_tree_view.hh @@ -190,6 +190,12 @@ class AbstractTreeViewItem : public TreeViewItemContainer { * last redraw to this item. If sub-classes introduce more advanced state they should override * this and make it update their state accordingly. */ virtual void update_from_old(const AbstractTreeViewItem &old); + /** Compare this item to \a other to check if they represent the same data. This is critical for + * being able to recognize an item from a previous redraw, to be able to keep its state (e.g. + * open/closed, active, etc.). Items are only matched if their parents also match. + * By default this just matches the items names/labels (if their parents match!). If that isn't + * good enough for a sub-class, that can override it. */ + virtual bool matches(const AbstractTreeViewItem &other) const; const AbstractTreeView &get_tree_view() const; int count_parents() const; diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c index a98af00572d..c53bffca778 100644 --- a/source/blender/editors/interface/interface.c +++ b/source/blender/editors/interface/interface.c @@ -743,6 +743,15 @@ static bool ui_but_equals_old(const uiBut *but, const uiBut *oldbut) return false; } + if ((but->type == UI_BTYPE_TREEROW) && (oldbut->type == UI_BTYPE_TREEROW)) { + uiButTreeRow *but_treerow = (uiButTreeRow *)but; + uiButTreeRow *oldbut_treerow = (uiButTreeRow *)oldbut; + if (!but_treerow->tree_item || !oldbut_treerow->tree_item || + !UI_tree_view_item_matches(but_treerow->tree_item, oldbut_treerow->tree_item)) { + return false; + } + } + return true; } diff --git a/source/blender/editors/interface/tree_view.cc b/source/blender/editors/interface/tree_view.cc index ee50126f974..16499065019 100644 --- a/source/blender/editors/interface/tree_view.cc +++ b/source/blender/editors/interface/tree_view.cc @@ -123,7 +123,7 @@ AbstractTreeViewItem *AbstractTreeView::find_matching_child( const AbstractTreeViewItem &lookup_item, const TreeViewItemContainer &items) { for (const auto &iter_item : items.children_) { - if (lookup_item.label_ == iter_item->label_) { + if (lookup_item.matches(*iter_item)) { /* We have a matching item! */ return iter_item.get(); } @@ -145,6 +145,11 @@ void AbstractTreeViewItem::update_from_old(const AbstractTreeViewItem &old) is_active_ = old.is_active_; } +bool AbstractTreeViewItem::matches(const AbstractTreeViewItem &other) const +{ + return label_ == other.label_; +} + const AbstractTreeView &AbstractTreeViewItem::get_tree_view() const { return static_cast<AbstractTreeView &>(*root_); @@ -309,8 +314,16 @@ uiBut *BasicTreeViewItem::button() using namespace blender::ui; -bool UI_tree_view_item_is_active(uiTreeViewItemHandle *item_) +bool UI_tree_view_item_is_active(const uiTreeViewItemHandle *item_handle) { - AbstractTreeViewItem &item = reinterpret_cast<AbstractTreeViewItem &>(*item_); + const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_handle); return item.is_active(); } + +bool UI_tree_view_item_matches(const uiTreeViewItemHandle *a_handle, + const uiTreeViewItemHandle *b_handle) +{ + const AbstractTreeViewItem &a = reinterpret_cast<const AbstractTreeViewItem &>(*a_handle); + const AbstractTreeViewItem &b = reinterpret_cast<const AbstractTreeViewItem &>(*b_handle); + return a.matches(b); +} diff --git a/source/blender/editors/space_file/CMakeLists.txt b/source/blender/editors/space_file/CMakeLists.txt index b60f9df82f6..4b508f16c1e 100644 --- a/source/blender/editors/space_file/CMakeLists.txt +++ b/source/blender/editors/space_file/CMakeLists.txt @@ -34,6 +34,7 @@ set(INC ) set(SRC + asset_catalog_tree_view.cc file_draw.c file_ops.c file_panels.c diff --git a/source/blender/editors/space_file/asset_catalog_tree_view.cc b/source/blender/editors/space_file/asset_catalog_tree_view.cc new file mode 100644 index 00000000000..7eea9af925b --- /dev/null +++ b/source/blender/editors/space_file/asset_catalog_tree_view.cc @@ -0,0 +1,230 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2007 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup spfile + */ + +#include "ED_fileselect.h" + +#include "DNA_space_types.h" + +#include "BKE_asset_catalog.hh" +#include "BKE_asset_library.hh" + +#include "BLI_string_ref.hh" + +#include "BLT_translation.h" + +#include "RNA_access.h" + +#include "UI_interface.h" +#include "UI_interface.hh" +#include "UI_resources.h" +#include "UI_tree_view.hh" + +#include "WM_api.h" +#include "WM_types.h" + +#include "file_intern.h" + +using namespace blender; +using namespace blender::bke; + +namespace blender::ed::asset_browser { + +class AssetCatalogTreeView : public ui::AbstractTreeView { + /** The asset catalog tree this tree-view represents. */ + bke::AssetCatalogTree *catalog_tree_; + FileAssetSelectParams *params_; + + friend class AssetCatalogTreeViewItem; + + public: + AssetCatalogTreeView(::AssetLibrary *library, FileAssetSelectParams *params); + + void build_tree() override; + + private: + ui::BasicTreeViewItem &build_catalog_items_recursive(ui::TreeViewItemContainer &view_parent_item, + AssetCatalogTreeItem &catalog); + + void add_all_item(); + void add_unassigned_item(); + bool is_active_catalog(CatalogID catalog_id) const; +}; +/* ---------------------------------------------------------------------- */ + +class AssetCatalogTreeViewItem : public ui::BasicTreeViewItem { + /** The catalog tree item this tree view item represents. */ + AssetCatalogTreeItem &catalog_item_; + + public: + AssetCatalogTreeViewItem(AssetCatalogTreeItem *catalog_item) + : BasicTreeViewItem(catalog_item->get_name()), catalog_item_(*catalog_item) + { + } + + void on_activate() override + { + const AssetCatalogTreeView &tree_view = static_cast<const AssetCatalogTreeView &>( + get_tree_view()); + tree_view.params_->asset_catalog_visibility = FILE_SHOW_ASSETS_FROM_CATALOG; + tree_view.params_->catalog_id = catalog_item_.get_catalog_id(); + WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr); + } + + void build_row(uiLayout &row) override + { + ui::BasicTreeViewItem::build_row(row); + + if (!is_active()) { + return; + } + + PointerRNA *props; + const CatalogID catalog_id = catalog_item_.get_catalog_id(); + + props = UI_but_extra_operator_icon_add( + button(), "ASSET_OT_catalog_new", WM_OP_INVOKE_DEFAULT, ICON_ADD); + RNA_string_set(props, "parent_path", catalog_item_.catalog_path().c_str()); + + /* Tree items without a catalog ID represent components of catalog paths that are not + * associated with an actual catalog. They exist merely by the presence of a child catalog, and + * thus cannot be deleted themselves. */ + if (!BLI_uuid_is_nil(catalog_id)) { + char catalog_id_str_buffer[UUID_STRING_LEN] = ""; + BLI_uuid_format(catalog_id_str_buffer, catalog_id); + + props = UI_but_extra_operator_icon_add( + button(), "ASSET_OT_catalog_delete", WM_OP_INVOKE_DEFAULT, ICON_X); + RNA_string_set(props, "catalog_id", catalog_id_str_buffer); + } + } +}; + +/** Only reason this isn't just `BasicTreeViewItem` is to add a '+' icon for adding a root level + * catalog. */ +class AssetCatalogTreeViewAllItem : public ui::BasicTreeViewItem { + using BasicTreeViewItem::BasicTreeViewItem; + + void build_row(uiLayout &row) override + { + ui::BasicTreeViewItem::build_row(row); + + if (!is_active()) { + return; + } + + PointerRNA *props; + props = UI_but_extra_operator_icon_add( + button(), "ASSET_OT_catalog_new", WM_OP_INVOKE_DEFAULT, ICON_ADD); + /* No parent path to use the root level. */ + RNA_string_set(props, "parent_path", nullptr); + } +}; + +AssetCatalogTreeView::AssetCatalogTreeView(::AssetLibrary *library, FileAssetSelectParams *params) + : catalog_tree_(BKE_asset_library_get_catalog_tree(library)), params_(params) +{ +} + +void AssetCatalogTreeView::build_tree() +{ + add_all_item(); + + if (catalog_tree_) { + catalog_tree_->foreach_root_item([this](AssetCatalogTreeItem &item) { + ui::BasicTreeViewItem &child_view_item = build_catalog_items_recursive(*this, item); + + /* Open root-level items by default. */ + child_view_item.set_collapsed(false); + }); + } + + add_unassigned_item(); +} + +ui::BasicTreeViewItem &AssetCatalogTreeView::build_catalog_items_recursive( + ui::TreeViewItemContainer &view_parent_item, AssetCatalogTreeItem &catalog) +{ + ui::BasicTreeViewItem &view_item = view_parent_item.add_tree_item<AssetCatalogTreeViewItem>( + &catalog); + if (is_active_catalog(catalog.get_catalog_id())) { + view_item.set_active(); + } + + catalog.foreach_child([&view_item, this](AssetCatalogTreeItem &child) { + build_catalog_items_recursive(view_item, child); + }); + return view_item; +} + +void AssetCatalogTreeView::add_all_item() +{ + FileAssetSelectParams *params = params_; + + ui::AbstractTreeViewItem &item = add_tree_item<AssetCatalogTreeViewAllItem>( + IFACE_("All"), ICON_HOME, [params](ui::BasicTreeViewItem & /*item*/) { + params->asset_catalog_visibility = FILE_SHOW_ASSETS_ALL_CATALOGS; + WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr); + }); + if (params->asset_catalog_visibility == FILE_SHOW_ASSETS_ALL_CATALOGS) { + item.set_active(); + } +} + +void AssetCatalogTreeView::add_unassigned_item() +{ + FileAssetSelectParams *params = params_; + + ui::AbstractTreeViewItem &item = add_tree_item<ui::BasicTreeViewItem>( + IFACE_("Unassigned"), ICON_FILE_HIDDEN, [params](ui::BasicTreeViewItem & /*item*/) { + params->asset_catalog_visibility = FILE_SHOW_ASSETS_WITHOUT_CATALOG; + WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr); + }); + if (params->asset_catalog_visibility == FILE_SHOW_ASSETS_WITHOUT_CATALOG) { + item.set_active(); + } +} + +bool AssetCatalogTreeView::is_active_catalog(CatalogID catalog_id) const +{ + return (params_->asset_catalog_visibility == FILE_SHOW_ASSETS_FROM_CATALOG) && + (params_->catalog_id == catalog_id); +} + +} // namespace blender::ed::asset_browser + +/* ---------------------------------------------------------------------- */ + +void file_create_asset_catalog_tree_view_in_layout(::AssetLibrary *asset_library, + uiLayout *layout, + FileAssetSelectParams *params) +{ + uiBlock *block = uiLayoutGetBlock(layout); + + ui::AbstractTreeView *tree_view = UI_block_add_view( + *block, + "asset catalog tree view", + std::make_unique<ed::asset_browser::AssetCatalogTreeView>(asset_library, params)); + + ui::TreeViewBuilder builder(*block); + builder.build_tree_view(*tree_view); +} diff --git a/source/blender/editors/space_file/file_intern.h b/source/blender/editors/space_file/file_intern.h index 65e0ad94c72..d39aefff691 100644 --- a/source/blender/editors/space_file/file_intern.h +++ b/source/blender/editors/space_file/file_intern.h @@ -31,8 +31,11 @@ extern "C" { struct ARegion; struct ARegionType; +struct AssetLibrary; struct FileSelectParams; +struct FileAssetSelectParams; struct SpaceFile; +struct uiLayout; struct View2D; /* file_draw.c */ @@ -151,12 +154,19 @@ void file_on_reload_callback_register(struct SpaceFile *sfile, /* file_panels.c */ void file_tool_props_region_panels_register(struct ARegionType *art); void file_execute_region_panels_register(struct ARegionType *art); +void file_tools_region_panels_register(struct ARegionType *art); /* file_utils.c */ void file_tile_boundbox(const ARegion *region, FileLayout *layout, const int file, rcti *r_bounds); void file_path_to_ui_path(const char *path, char *r_pathi, int max_size); +/* asset_catalog_tree_view.cc */ + +void file_create_asset_catalog_tree_view_in_layout(struct AssetLibrary *asset_library, + struct uiLayout *layout, + struct FileAssetSelectParams *params); + #ifdef __cplusplus } #endif diff --git a/source/blender/editors/space_file/file_panels.c b/source/blender/editors/space_file/file_panels.c index 7032d55b331..95aad202f1a 100644 --- a/source/blender/editors/space_file/file_panels.c +++ b/source/blender/editors/space_file/file_panels.c @@ -47,6 +47,7 @@ #include "WM_types.h" #include "file_intern.h" +#include "filelist.h" #include "fsmenu.h" #include <string.h> @@ -57,6 +58,12 @@ static bool file_panel_operator_poll(const bContext *C, PanelType *UNUSED(pt)) return (sfile && sfile->op); } +static bool file_panel_asset_browsing_poll(const bContext *C, PanelType *UNUSED(pt)) +{ + SpaceFile *sfile = CTX_wm_space_file(C); + return sfile && sfile->files && ED_fileselect_is_asset_browser(sfile); +} + static void file_panel_operator_header(const bContext *C, Panel *panel) { SpaceFile *sfile = CTX_wm_space_file(C); @@ -222,3 +229,28 @@ void file_execute_region_panels_register(ARegionType *art) pt->draw = file_panel_execution_buttons_draw; BLI_addtail(&art->paneltypes, pt); } + +static void file_panel_asset_catalog_buttons_draw(const bContext *C, Panel *panel) +{ + SpaceFile *sfile = CTX_wm_space_file(C); + /* May be null if the library wasn't loaded yet. */ + struct AssetLibrary *asset_library = filelist_asset_library(sfile->files); + 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); +} + +void file_tools_region_panels_register(ARegionType *art) +{ + PanelType *pt; + + pt = MEM_callocN(sizeof(PanelType), "spacetype file asset catalog buttons"); + strcpy(pt->idname, "FILE_PT_asset_catalog_buttons"); + strcpy(pt->label, N_("Asset Catalogs")); + strcpy(pt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA); + pt->flag = PANEL_TYPE_NO_HEADER; + pt->poll = file_panel_asset_browsing_poll; + pt->draw = file_panel_asset_catalog_buttons_draw; + BLI_addtail(&art->paneltypes, pt); +} diff --git a/source/blender/editors/space_file/filelist.c b/source/blender/editors/space_file/filelist.c index 60fe5364aba..91178b85823 100644 --- a/source/blender/editors/space_file/filelist.c +++ b/source/blender/editors/space_file/filelist.c @@ -50,6 +50,7 @@ #include "BLI_task.h" #include "BLI_threads.h" #include "BLI_utildefines.h" +#include "BLI_uuid.h" #ifdef WIN32 # include "BLI_winstuff.h" @@ -369,6 +370,9 @@ typedef struct FileListFilter { char filter_glob[FILE_MAXFILE]; char filter_search[66]; /* + 2 for heading/trailing implicit '*' wildcards. */ short flags; + + eFileSel_Params_AssetCatalogVisibility asset_catalog_visibility; + bUUID asset_catalog_id; } FileListFilter; /* FileListFilter.flags */ @@ -806,6 +810,22 @@ static bool is_filtered_hidden(const char *filename, return true; } + /* TODO Make catalog activation work properly with the "Current File" asset library. Currently + * this will only work for external asset data. */ + if (file->imported_asset_data) { + switch (filter->asset_catalog_visibility) { + case FILE_SHOW_ASSETS_WITHOUT_CATALOG: + return !BLI_uuid_is_nil(file->imported_asset_data->catalog_id); + case FILE_SHOW_ASSETS_FROM_CATALOG: + /* TODO show all assets that are in child catalogs of the selected catalog. */ + return BLI_uuid_is_nil(filter->asset_catalog_id) || + !BLI_uuid_equal(filter->asset_catalog_id, file->imported_asset_data->catalog_id); + case FILE_SHOW_ASSETS_ALL_CATALOGS: + /* All asset files should be visible. */ + break; + } + } + return false; } @@ -1038,6 +1058,33 @@ void filelist_setfilter_options(FileList *filelist, } /** + * \param catalog_id: The catalog that should be filtered by if \a catalog_visibility is + * #FILE_SHOW_ASSETS_FROM_CATALOG. May be NULL otherwise. + */ +void filelist_set_asset_catalog_filter_options( + FileList *filelist, + eFileSel_Params_AssetCatalogVisibility catalog_visibility, + const bUUID *catalog_id) +{ + bool update = false; + + if (filelist->filter_data.asset_catalog_visibility != catalog_visibility) { + filelist->filter_data.asset_catalog_visibility = catalog_visibility; + update = true; + } + + if (filelist->filter_data.asset_catalog_visibility == FILE_SHOW_ASSETS_FROM_CATALOG && + catalog_id && !BLI_uuid_equal(filelist->filter_data.asset_catalog_id, *catalog_id)) { + filelist->filter_data.asset_catalog_id = *catalog_id; + update = true; + } + + if (update) { + filelist_filter_clear(filelist); + } +} + +/** * Checks two libraries for equality. * \return True if the libraries match. */ @@ -1809,6 +1856,11 @@ void filelist_free(struct FileList *filelist) filelist->flags &= ~(FL_NEED_SORTING | FL_NEED_FILTERING); } +AssetLibrary *filelist_asset_library(FileList *filelist) +{ + return filelist->asset_library; +} + void filelist_freelib(struct FileList *filelist) { if (filelist->libfiledata) { diff --git a/source/blender/editors/space_file/filelist.h b/source/blender/editors/space_file/filelist.h index b51ceee4aa0..d1f37b5b365 100644 --- a/source/blender/editors/space_file/filelist.h +++ b/source/blender/editors/space_file/filelist.h @@ -31,6 +31,7 @@ struct AssetLibraryReference; struct BlendHandle; struct FileList; struct FileSelection; +struct bUUID; struct wmWindowManager; struct FileDirEntry; @@ -71,6 +72,10 @@ void filelist_setfilter_options(struct FileList *filelist, const bool filter_assets_only, const char *filter_glob, const char *filter_search); +void filelist_set_asset_catalog_filter_options( + struct FileList *filelist, + eFileSel_Params_AssetCatalogVisibility catalog_visibility, + const struct bUUID *catalog_id); void filelist_filter(struct FileList *filelist); void filelist_setlibrary(struct FileList *filelist, const struct AssetLibraryReference *asset_library_ref); @@ -144,6 +149,8 @@ void filelist_entry_parent_select_set(struct FileList *filelist, void filelist_setrecursion(struct FileList *filelist, const int recursion_level); +struct AssetLibrary *filelist_asset_library(struct FileList *filelist); + struct BlendHandle *filelist_lib(struct FileList *filelist); bool filelist_islibrary(struct FileList *filelist, char *dir, char **r_group); void filelist_freelib(struct FileList *filelist); diff --git a/source/blender/editors/space_file/filesel.c b/source/blender/editors/space_file/filesel.c index 2ca08a3105c..83b33fe8aa9 100644 --- a/source/blender/editors/space_file/filesel.c +++ b/source/blender/editors/space_file/filesel.c @@ -126,13 +126,10 @@ static void fileselect_ensure_updated_asset_params(SpaceFile *sfile) FileSelectParams *base_params = &asset_params->base_params; base_params->file[0] = '\0'; base_params->filter_glob[0] = '\0'; - /* TODO: this way of using filters to form categories is notably slower than specifying a - * "group" to read. That's because all types are read and filtering is applied afterwards. Would - * be nice if we could lazy-read individual groups. */ base_params->flag |= U_default.file_space_data.flag | FILE_ASSETS_ONLY | FILE_FILTER; base_params->flag &= ~FILE_DIRSEL_ONLY; base_params->filter |= FILE_TYPE_BLENDERLIB; - base_params->filter_id = FILTER_ID_OB | FILTER_ID_GR; + base_params->filter_id = FILTER_ID_ALL; base_params->display = FILE_IMGDISPLAY; base_params->sort = FILE_SORT_ALPHA; /* Asset libraries include all sub-directories, so enable maximal recursion. */ @@ -462,6 +459,15 @@ bool ED_fileselect_is_asset_browser(const SpaceFile *sfile) return (sfile->browse_mode == FILE_BROWSE_MODE_ASSETS); } +struct AssetLibrary *ED_fileselect_active_asset_library_get(const SpaceFile *sfile) +{ + if (!ED_fileselect_is_asset_browser(sfile) || !sfile->files) { + return NULL; + } + + return filelist_asset_library(sfile->files); +} + struct ID *ED_fileselect_active_asset_get(const SpaceFile *sfile) { if (!ED_fileselect_is_asset_browser(sfile)) { diff --git a/source/blender/editors/space_file/space_file.c b/source/blender/editors/space_file/space_file.c index ac23767f933..a563f24e24e 100644 --- a/source/blender/editors/space_file/space_file.c +++ b/source/blender/editors/space_file/space_file.c @@ -335,9 +335,9 @@ static void file_refresh(const bContext *C, ScrArea *area) params->highlight_file = -1; /* added this so it opens nicer (ton) */ } - if (!U.experimental.use_extended_asset_browser && ED_fileselect_is_asset_browser(sfile)) { + if (ED_fileselect_is_asset_browser(sfile)) { /* Only poses supported as non-experimental right now. */ - params->filter_id = FILTER_ID_AC; + params->filter_id = U.experimental.use_extended_asset_browser ? FILTER_ID_ALL : FILTER_ID_AC; } filelist_settype(sfile->files, params->type); @@ -355,6 +355,10 @@ static void file_refresh(const bContext *C, ScrArea *area) (params->flag & FILE_ASSETS_ONLY) != 0, params->filter_glob, params->filter_search); + if (asset_params) { + filelist_set_asset_catalog_filter_options( + sfile->files, asset_params->asset_catalog_visibility, &asset_params->catalog_id); + } /* Update the active indices of bookmarks & co. */ sfile->systemnr = fsmenu_get_active_indices(fsmenu, FS_CATEGORY_SYSTEM, params->dir); @@ -1050,6 +1054,7 @@ void ED_spacetype_file(void) art->init = file_tools_region_init; art->draw = file_tools_region_draw; BLI_addhead(&st->regiontypes, art); + file_tools_region_panels_register(art); /* regions: tool properties */ art = MEM_callocN(sizeof(ARegionType), "spacetype file operator region"); diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h index 2f3f52a6b82..70a1ca63e21 100644 --- a/source/blender/makesdna/DNA_space_types.h +++ b/source/blender/makesdna/DNA_space_types.h @@ -806,9 +806,14 @@ typedef struct FileAssetSelectParams { FileSelectParams base_params; AssetLibraryReference asset_library_ref; + short asset_catalog_visibility; /* eFileSel_Params_AssetCatalogVisibility */ + char _pad[6]; + /** If #asset_catalog_visibility is #FILE_SHOW_ASSETS_FROM_CATALOG, this sets the ID of the + * catalog to show. */ + bUUID catalog_id; short import_type; /* eFileAssetImportType */ - char _pad[6]; + char _pad2[6]; } FileAssetSelectParams; typedef enum eFileAssetImportType { @@ -1009,8 +1014,16 @@ typedef enum eFileSel_Params_Flag { FILE_HIDE_TOOL_PROPS = (1 << 12), FILE_CHECK_EXISTING = (1 << 13), FILE_ASSETS_ONLY = (1 << 14), + /** Enables filtering by asset catalog. */ + FILE_FILTER_ASSET_CATALOG = (1 << 15), } eFileSel_Params_Flag; +typedef enum eFileSel_Params_AssetCatalogVisibility { + FILE_SHOW_ASSETS_ALL_CATALOGS, + FILE_SHOW_ASSETS_FROM_CATALOG, + FILE_SHOW_ASSETS_WITHOUT_CATALOG, +} eFileSel_Params_AssetCatalogVisibility; + /* sfile->params->rename_flag */ /* NOTE: short flag. Defined as bitflags, but currently only used as exclusive status markers... */ typedef enum eFileSel_Params_RenameFlag { diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c index 9e06533d41b..78c26df7be6 100644 --- a/source/blender/makesrna/intern/rna_space.c +++ b/source/blender/makesrna/intern/rna_space.c @@ -2614,18 +2614,6 @@ static void rna_FileAssetSelectParams_asset_library_set(PointerRNA *ptr, int val params->asset_library_ref = ED_asset_library_reference_from_enum_value(value); } -static void rna_FileAssetSelectParams_asset_category_set(PointerRNA *ptr, uint64_t value) -{ - FileSelectParams *params = ptr->data; - params->filter_id = value; -} - -static uint64_t rna_FileAssetSelectParams_asset_category_get(PointerRNA *ptr) -{ - FileSelectParams *params = ptr->data; - return params->filter_id; -} - static PointerRNA rna_FileBrowser_FileSelectEntry_asset_data_get(PointerRNA *ptr) { const FileDirEntry *entry = ptr->data; @@ -6595,47 +6583,6 @@ static void rna_def_fileselect_asset_params(BlenderRNA *brna) StructRNA *srna; PropertyRNA *prop; - /* XXX copied from rna_enum_id_type_filter_items. */ - static const EnumPropertyItem asset_category_items[] = { - {FILTER_ID_SCE, "SCENES", ICON_SCENE_DATA, "Scenes", "Show scenes"}, - {FILTER_ID_AC, "ANIMATIONS", ICON_ANIM_DATA, "Animations", "Show animation data"}, - {FILTER_ID_OB | FILTER_ID_GR, - "OBJECTS_AND_COLLECTIONS", - ICON_GROUP, - "Objects & Collections", - "Show objects and collections"}, - {FILTER_ID_AR | FILTER_ID_CU | FILTER_ID_LT | FILTER_ID_MB | FILTER_ID_ME - /* XXX avoid warning */ - // | FILTER_ID_HA | FILTER_ID_PT | FILTER_ID_VO - , - "GEOMETRY", - ICON_MESH_DATA, - "Geometry", - "Show meshes, curves, lattice, armatures and metaballs data"}, - {FILTER_ID_LS | FILTER_ID_MA | FILTER_ID_NT | FILTER_ID_TE, - "SHADING", - ICON_MATERIAL_DATA, - "Shading", - "Show materials, nodetrees, textures and Freestyle's linestyles"}, - {FILTER_ID_IM | FILTER_ID_MC | FILTER_ID_MSK | FILTER_ID_SO, - "IMAGES_AND_SOUNDS", - ICON_IMAGE_DATA, - "Images & Sounds", - "Show images, movie clips, sounds and masks"}, - {FILTER_ID_CA | FILTER_ID_LA | FILTER_ID_LP | FILTER_ID_SPK | FILTER_ID_WO, - "ENVIRONMENTS", - ICON_WORLD_DATA, - "Environment", - "Show worlds, lights, cameras and speakers"}, - {FILTER_ID_BR | FILTER_ID_GD | FILTER_ID_PA | FILTER_ID_PAL | FILTER_ID_PC | FILTER_ID_TXT | - FILTER_ID_VF | FILTER_ID_CF | FILTER_ID_WS, - "MISC", - ICON_GREASEPENCIL, - "Miscellaneous", - "Show other data types"}, - {0, NULL, 0, NULL, NULL}, - }; - static const EnumPropertyItem asset_import_type_items[] = { {FILE_ASSET_IMPORT_LINK, "LINK", 0, "Link", "Import the assets as linked data-block"}, {FILE_ASSET_IMPORT_APPEND, @@ -6664,15 +6611,6 @@ static void rna_def_fileselect_asset_params(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Asset Library", ""); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL); - prop = RNA_def_property(srna, "asset_category", PROP_ENUM, PROP_NONE); - RNA_def_property_enum_items(prop, asset_category_items); - RNA_def_property_enum_funcs(prop, - "rna_FileAssetSelectParams_asset_category_get", - "rna_FileAssetSelectParams_asset_category_set", - NULL); - RNA_def_property_ui_text(prop, "Asset Category", "Determine which kind of assets to display"); - RNA_def_property_update(prop, NC_SPACE | ND_SPACE_FILE_LIST, NULL); - prop = RNA_def_property(srna, "import_type", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, asset_import_type_items); RNA_def_property_ui_text(prop, "Import Type", "Determine how the asset will be imported"); |