diff options
author | Jeroen Bakker <jeroen@blender.org> | 2021-11-09 12:34:41 +0300 |
---|---|---|
committer | Jeroen Bakker <jeroen@blender.org> | 2021-11-09 12:34:41 +0300 |
commit | b65df10346f5fe47c881b183901e2d7eff775848 (patch) | |
tree | 95120de32f77d7193edc3687f0b97a0692288e8b /source/blender/editors/space_file | |
parent | 8bf8db8ca2dd534ce4aaa32a0921b599f36098c4 (diff) | |
parent | a7540f4b3611a0d06f197e6f27148319927188f7 (diff) |
Merge branch 'master' into tmp-vulkan
Diffstat (limited to 'source/blender/editors/space_file')
-rw-r--r-- | source/blender/editors/space_file/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/blender/editors/space_file/asset_catalog_tree_view.cc | 655 | ||||
-rw-r--r-- | source/blender/editors/space_file/file_draw.c | 154 | ||||
-rw-r--r-- | source/blender/editors/space_file/file_intern.h | 60 | ||||
-rw-r--r-- | source/blender/editors/space_file/file_ops.c | 331 | ||||
-rw-r--r-- | source/blender/editors/space_file/file_panels.c | 46 | ||||
-rw-r--r-- | source/blender/editors/space_file/filelist.c | 1317 | ||||
-rw-r--r-- | source/blender/editors/space_file/filelist.h | 37 | ||||
-rw-r--r-- | source/blender/editors/space_file/filesel.c | 228 | ||||
-rw-r--r-- | source/blender/editors/space_file/fsmenu.c | 23 | ||||
-rw-r--r-- | source/blender/editors/space_file/space_file.c | 179 |
11 files changed, 2277 insertions, 755 deletions
diff --git a/source/blender/editors/space_file/CMakeLists.txt b/source/blender/editors/space_file/CMakeLists.txt index 993a52b9084..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 @@ -49,6 +50,7 @@ set(SRC ) set(LIB + bf_blenkernel ) if(WITH_HEADLESS) 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..b3b81c5e07f --- /dev/null +++ b/source/blender/editors/space_file/asset_catalog_tree_view.cc @@ -0,0 +1,655 @@ +/* + * 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 "DNA_space_types.h" + +#include "BKE_asset.h" +#include "BKE_asset_catalog.hh" +#include "BKE_asset_library.hh" + +#include "BLI_string_ref.hh" + +#include "BLT_translation.h" + +#include "ED_asset.h" +#include "ED_fileselect.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" +#include "filelist.h" + +using namespace blender; +using namespace blender::bke; + +namespace blender::ed::asset_browser { + +class AssetCatalogTreeView : public ui::AbstractTreeView { + ::AssetLibrary *asset_library_; + /** The asset catalog tree this tree-view represents. */ + bke::AssetCatalogTree *catalog_tree_; + FileAssetSelectParams *params_; + SpaceFile &space_file_; + + friend class AssetCatalogTreeViewItem; + friend class AssetCatalogDropController; + + public: + AssetCatalogTreeView(::AssetLibrary *library, + FileAssetSelectParams *params, + SpaceFile &space_file); + + 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); + + void on_activate() override; + + void build_row(uiLayout &row) override; + void build_context_menu(bContext &C, uiLayout &column) const override; + + bool can_rename() const override; + bool rename(StringRefNull new_name) override; + + /** Add drag support for catalog items. */ + std::unique_ptr<ui::AbstractTreeViewItemDragController> create_drag_controller() const override; + /** Add dropping support for catalog items. */ + std::unique_ptr<ui::AbstractTreeViewItemDropController> create_drop_controller() const override; +}; + +class AssetCatalogDragController : public ui::AbstractTreeViewItemDragController { + AssetCatalogTreeItem &catalog_item_; + + public: + explicit AssetCatalogDragController(AssetCatalogTreeItem &catalog_item); + + int get_drag_type() const override; + void *create_drag_data() const override; +}; + +class AssetCatalogDropController : public ui::AbstractTreeViewItemDropController { + AssetCatalogTreeItem &catalog_item_; + + public: + AssetCatalogDropController(AssetCatalogTreeView &tree_view, AssetCatalogTreeItem &catalog_item); + + bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override; + std::string drop_tooltip(const wmDrag &drag) const override; + bool on_drop(const wmDrag &drag) override; + + ::AssetLibrary &get_asset_library() const; + + static bool has_droppable_asset(const wmDrag &drag, const char **r_disabled_hint); + static bool drop_assets_into_catalog(const AssetCatalogTreeView &tree_view, + const wmDrag &drag, + CatalogID catalog_id, + StringRefNull simple_name = ""); + + private: + bool drop_asset_catalog_into_catalog(const wmDrag &drag); + std::string drop_tooltip_asset_list(const wmDrag &drag) const; + std::string drop_tooltip_asset_catalog(const wmDrag &drag) const; +}; + +/** 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; +}; + +class AssetCatalogTreeViewUnassignedItem : public ui::BasicTreeViewItem { + using BasicTreeViewItem::BasicTreeViewItem; + + struct DropController : public ui::AbstractTreeViewItemDropController { + DropController(AssetCatalogTreeView &tree_view); + + bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override; + std::string drop_tooltip(const wmDrag &drag) const override; + bool on_drop(const wmDrag &drag) override; + }; + + std::unique_ptr<ui::AbstractTreeViewItemDropController> create_drop_controller() const override; +}; + +/* ---------------------------------------------------------------------- */ + +AssetCatalogTreeView::AssetCatalogTreeView(::AssetLibrary *library, + FileAssetSelectParams *params, + SpaceFile &space_file) + : asset_library_(library), + catalog_tree_(BKE_asset_library_get_catalog_tree(library)), + params_(params), + space_file_(space_file) +{ +} + +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); + view_item.is_active([this, &catalog]() { return is_active_catalog(catalog.get_catalog_id()); }); + + 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_; + + AssetCatalogTreeViewAllItem &item = add_tree_item<AssetCatalogTreeViewAllItem>(IFACE_("All"), + ICON_HOME); + item.on_activate([params](ui::BasicTreeViewItem & /*item*/) { + params->asset_catalog_visibility = FILE_SHOW_ASSETS_ALL_CATALOGS; + WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr); + }); + item.is_active( + [params]() { return params->asset_catalog_visibility == FILE_SHOW_ASSETS_ALL_CATALOGS; }); +} + +void AssetCatalogTreeView::add_unassigned_item() +{ + FileAssetSelectParams *params = params_; + + AssetCatalogTreeViewUnassignedItem &item = add_tree_item<AssetCatalogTreeViewUnassignedItem>( + IFACE_("Unassigned"), ICON_FILE_HIDDEN); + + item.on_activate([params](ui::BasicTreeViewItem & /*item*/) { + params->asset_catalog_visibility = FILE_SHOW_ASSETS_WITHOUT_CATALOG; + WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr); + }); + item.is_active( + [params]() { return params->asset_catalog_visibility == FILE_SHOW_ASSETS_WITHOUT_CATALOG; }); +} + +bool AssetCatalogTreeView::is_active_catalog(CatalogID catalog_id) const +{ + return (params_->asset_catalog_visibility == FILE_SHOW_ASSETS_FROM_CATALOG) && + (params_->catalog_id == catalog_id); +} + +/* ---------------------------------------------------------------------- */ + +AssetCatalogTreeViewItem::AssetCatalogTreeViewItem(AssetCatalogTreeItem *catalog_item) + : BasicTreeViewItem(catalog_item->get_name()), catalog_item_(*catalog_item) +{ +} + +void AssetCatalogTreeViewItem::on_activate() +{ + 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 AssetCatalogTreeViewItem::build_row(uiLayout &row) +{ + const std::string label_override = catalog_item_.has_unsaved_changes() ? (label_ + "*") : label_; + add_label(row, label_override); + + if (!is_hovered()) { + return; + } + + uiButTreeRow *tree_row_but = tree_row_button(); + PointerRNA *props; + + props = UI_but_extra_operator_icon_add( + (uiBut *)tree_row_but, "ASSET_OT_catalog_new", WM_OP_INVOKE_DEFAULT, ICON_ADD); + RNA_string_set(props, "parent_path", catalog_item_.catalog_path().c_str()); +} + +void AssetCatalogTreeViewItem::build_context_menu(bContext &C, uiLayout &column) const +{ + PointerRNA props; + + uiItemFullO(&column, + "ASSET_OT_catalog_new", + "New Catalog", + ICON_NONE, + nullptr, + WM_OP_INVOKE_DEFAULT, + 0, + &props); + RNA_string_set(&props, "parent_path", catalog_item_.catalog_path().c_str()); + + char catalog_id_str_buffer[UUID_STRING_LEN] = ""; + BLI_uuid_format(catalog_id_str_buffer, catalog_item_.get_catalog_id()); + uiItemFullO(&column, + "ASSET_OT_catalog_delete", + "Delete Catalog", + ICON_NONE, + nullptr, + WM_OP_INVOKE_DEFAULT, + 0, + &props); + RNA_string_set(&props, "catalog_id", catalog_id_str_buffer); + uiItemO(&column, "Rename", ICON_NONE, "UI_OT_tree_view_item_rename"); + + /* Doesn't actually exist right now, but could be defined in Python. Reason that this isn't done + * in Python yet is that catalogs are not exposed in BPY, and we'd somehow pass the clicked on + * catalog to the menu draw callback (via context probably).*/ + MenuType *mt = WM_menutype_find("ASSETBROWSER_MT_catalog_context_menu", true); + if (!mt) { + return; + } + UI_menutype_draw(&C, mt, &column); +} + +bool AssetCatalogTreeViewItem::can_rename() const +{ + return true; +} + +bool AssetCatalogTreeViewItem::rename(StringRefNull new_name) +{ + /* Important to keep state. */ + BasicTreeViewItem::rename(new_name); + + const AssetCatalogTreeView &tree_view = static_cast<const AssetCatalogTreeView &>( + get_tree_view()); + ED_asset_catalog_rename(tree_view.asset_library_, catalog_item_.get_catalog_id(), new_name); + return true; +} + +std::unique_ptr<ui::AbstractTreeViewItemDropController> AssetCatalogTreeViewItem:: + create_drop_controller() const +{ + return std::make_unique<AssetCatalogDropController>( + static_cast<AssetCatalogTreeView &>(get_tree_view()), catalog_item_); +} + +std::unique_ptr<ui::AbstractTreeViewItemDragController> AssetCatalogTreeViewItem:: + create_drag_controller() const +{ + return std::make_unique<AssetCatalogDragController>(catalog_item_); +} + +/* ---------------------------------------------------------------------- */ + +AssetCatalogDropController::AssetCatalogDropController(AssetCatalogTreeView &tree_view, + AssetCatalogTreeItem &catalog_item) + : ui::AbstractTreeViewItemDropController(tree_view), catalog_item_(catalog_item) +{ +} + +bool AssetCatalogDropController::can_drop(const wmDrag &drag, const char **r_disabled_hint) const +{ + if (drag.type == WM_DRAG_ASSET_CATALOG) { + /* Always supported. */ + return true; + } + if (drag.type == WM_DRAG_ASSET_LIST) { + return has_droppable_asset(drag, r_disabled_hint); + } + return false; +} + +std::string AssetCatalogDropController::drop_tooltip(const wmDrag &drag) const +{ + if (drag.type == WM_DRAG_ASSET_CATALOG) { + return drop_tooltip_asset_catalog(drag); + } + return drop_tooltip_asset_list(drag); +} + +std::string AssetCatalogDropController::drop_tooltip_asset_catalog(const wmDrag &drag) const +{ + BLI_assert(drag.type == WM_DRAG_ASSET_CATALOG); + + const ::AssetLibrary *asset_library = tree_view<AssetCatalogTreeView>().asset_library_; + bke::AssetCatalogService *catalog_service = BKE_asset_library_get_catalog_service(asset_library); + wmDragAssetCatalog *catalog_drag = WM_drag_get_asset_catalog_data(&drag); + AssetCatalog *src_catalog = catalog_service->find_catalog(catalog_drag->drag_catalog_id); + + return std::string(TIP_("Move Catalog")) + " '" + src_catalog->path.name() + "' " + + TIP_("into") + " '" + catalog_item_.get_name() + "'"; +} + +std::string AssetCatalogDropController::drop_tooltip_asset_list(const wmDrag &drag) const +{ + BLI_assert(drag.type == WM_DRAG_ASSET_LIST); + + 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 AssetCatalogDropController::on_drop(const wmDrag &drag) +{ + if (drag.type == WM_DRAG_ASSET_CATALOG) { + return drop_asset_catalog_into_catalog(drag); + } + return drop_assets_into_catalog(tree_view<AssetCatalogTreeView>(), + drag, + catalog_item_.get_catalog_id(), + catalog_item_.get_simple_name()); +} + +bool AssetCatalogDropController::drop_asset_catalog_into_catalog(const wmDrag &drag) +{ + BLI_assert(drag.type == WM_DRAG_ASSET_CATALOG); + wmDragAssetCatalog *catalog_drag = WM_drag_get_asset_catalog_data(&drag); + ED_asset_catalog_move( + &get_asset_library(), catalog_drag->drag_catalog_id, catalog_item_.get_catalog_id()); + + WM_main_add_notifier(NC_ASSET | ND_ASSET_CATALOGS, nullptr); + return true; +} + +bool AssetCatalogDropController::drop_assets_into_catalog(const AssetCatalogTreeView &tree_view, + const wmDrag &drag, + CatalogID catalog_id, + StringRefNull simple_name) +{ + BLI_assert(drag.type == WM_DRAG_ASSET_LIST); + const ListBase *asset_drags = WM_drag_asset_list_get(&drag); + if (!asset_drags) { + return false; + } + + 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_id, 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); + WM_main_add_notifier(NC_SPACE | ND_SPACE_FILE_LIST, nullptr); + } + + return true; +} + +bool AssetCatalogDropController::has_droppable_asset(const wmDrag &drag, + const char **r_disabled_hint) +{ + const ListBase *asset_drags = WM_drag_asset_list_get(&drag); + + *r_disabled_hint = nullptr; + /* 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; + } + } + + *r_disabled_hint = "Only assets from this current file can be moved between catalogs"; + return false; +} + +::AssetLibrary &AssetCatalogDropController::get_asset_library() const +{ + return *tree_view<AssetCatalogTreeView>().asset_library_; +} + +/* ---------------------------------------------------------------------- */ + +AssetCatalogDragController::AssetCatalogDragController(AssetCatalogTreeItem &catalog_item) + : catalog_item_(catalog_item) +{ +} + +int AssetCatalogDragController::get_drag_type() const +{ + return WM_DRAG_ASSET_CATALOG; +} + +void *AssetCatalogDragController::create_drag_data() const +{ + wmDragAssetCatalog *drag_catalog = (wmDragAssetCatalog *)MEM_callocN(sizeof(*drag_catalog), + __func__); + drag_catalog->drag_catalog_id = catalog_item_.get_catalog_id(); + return drag_catalog; +} + +/* ---------------------------------------------------------------------- */ + +void AssetCatalogTreeViewAllItem::build_row(uiLayout &row) +{ + ui::BasicTreeViewItem::build_row(row); + + PointerRNA *props; + + UI_but_extra_operator_icon_add( + (uiBut *)tree_row_button(), "ASSET_OT_catalogs_save", WM_OP_INVOKE_DEFAULT, ICON_FILE_TICK); + + props = UI_but_extra_operator_icon_add( + (uiBut *)tree_row_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); +} + +/* ---------------------------------------------------------------------- */ + +std::unique_ptr<ui::AbstractTreeViewItemDropController> AssetCatalogTreeViewUnassignedItem:: + create_drop_controller() const +{ + return std::make_unique<AssetCatalogTreeViewUnassignedItem::DropController>( + static_cast<AssetCatalogTreeView &>(get_tree_view())); +} + +AssetCatalogTreeViewUnassignedItem::DropController::DropController(AssetCatalogTreeView &tree_view) + : ui::AbstractTreeViewItemDropController(tree_view) +{ +} + +bool AssetCatalogTreeViewUnassignedItem::DropController::can_drop( + const wmDrag &drag, const char **r_disabled_hint) const +{ + if (drag.type != WM_DRAG_ASSET_LIST) { + return false; + } + return AssetCatalogDropController::has_droppable_asset(drag, r_disabled_hint); +} + +std::string AssetCatalogTreeViewUnassignedItem::DropController::drop_tooltip( + const wmDrag &drag) const +{ + const ListBase *asset_drags = WM_drag_asset_list_get(&drag); + const bool is_multiple_assets = !BLI_listbase_is_single(asset_drags); + + return is_multiple_assets ? TIP_("Move assets out of any catalog") : + TIP_("Move asset out of any catalog"); +} + +bool AssetCatalogTreeViewUnassignedItem::DropController::on_drop(const wmDrag &drag) +{ + /* Assign to nil catalog ID. */ + return AssetCatalogDropController::drop_assets_into_catalog( + tree_view<AssetCatalogTreeView>(), drag, CatalogID{}); +} + +} // namespace blender::ed::asset_browser + +/* ---------------------------------------------------------------------- */ + +namespace blender::ed::asset_browser { + +class AssetCatalogFilterSettings { + public: + eFileSel_Params_AssetCatalogVisibility asset_catalog_visibility; + bUUID asset_catalog_id; + + std::unique_ptr<AssetCatalogFilter> catalog_filter; +}; + +} // namespace blender::ed::asset_browser + +using namespace blender::ed::asset_browser; + +FileAssetCatalogFilterSettingsHandle *file_create_asset_catalog_filter_settings() +{ + AssetCatalogFilterSettings *filter_settings = OBJECT_GUARDED_NEW(AssetCatalogFilterSettings); + return reinterpret_cast<FileAssetCatalogFilterSettingsHandle *>(filter_settings); +} + +void file_delete_asset_catalog_filter_settings( + FileAssetCatalogFilterSettingsHandle **filter_settings_handle) +{ + AssetCatalogFilterSettings **filter_settings = reinterpret_cast<AssetCatalogFilterSettings **>( + filter_settings_handle); + OBJECT_GUARDED_SAFE_DELETE(*filter_settings, AssetCatalogFilterSettings); +} + +/** + * \return True if the file list should update its filtered results (e.g. because filtering + * parameters changed). + */ +bool file_set_asset_catalog_filter_settings( + FileAssetCatalogFilterSettingsHandle *filter_settings_handle, + eFileSel_Params_AssetCatalogVisibility catalog_visibility, + ::bUUID catalog_id) +{ + AssetCatalogFilterSettings *filter_settings = reinterpret_cast<AssetCatalogFilterSettings *>( + filter_settings_handle); + bool needs_update = false; + + if (filter_settings->asset_catalog_visibility != catalog_visibility) { + filter_settings->asset_catalog_visibility = catalog_visibility; + needs_update = true; + } + + if (filter_settings->asset_catalog_visibility == FILE_SHOW_ASSETS_FROM_CATALOG && + !BLI_uuid_equal(filter_settings->asset_catalog_id, catalog_id)) { + filter_settings->asset_catalog_id = catalog_id; + needs_update = true; + } + + return needs_update; +} + +void file_ensure_updated_catalog_filter_data( + FileAssetCatalogFilterSettingsHandle *filter_settings_handle, + const ::AssetLibrary *asset_library) +{ + AssetCatalogFilterSettings *filter_settings = reinterpret_cast<AssetCatalogFilterSettings *>( + filter_settings_handle); + const AssetCatalogService *catalog_service = BKE_asset_library_get_catalog_service( + asset_library); + + if (filter_settings->asset_catalog_visibility != FILE_SHOW_ASSETS_ALL_CATALOGS) { + filter_settings->catalog_filter = std::make_unique<AssetCatalogFilter>( + catalog_service->create_catalog_filter(filter_settings->asset_catalog_id)); + } +} + +bool file_is_asset_visible_in_catalog_filter_settings( + const FileAssetCatalogFilterSettingsHandle *filter_settings_handle, + const AssetMetaData *asset_data) +{ + const AssetCatalogFilterSettings *filter_settings = + reinterpret_cast<const AssetCatalogFilterSettings *>(filter_settings_handle); + + switch (filter_settings->asset_catalog_visibility) { + case FILE_SHOW_ASSETS_WITHOUT_CATALOG: + return !filter_settings->catalog_filter->is_known(asset_data->catalog_id); + case FILE_SHOW_ASSETS_FROM_CATALOG: + return filter_settings->catalog_filter->contains(asset_data->catalog_id); + case FILE_SHOW_ASSETS_ALL_CATALOGS: + /* All asset files should be visible. */ + return true; + } + + BLI_assert_unreachable(); + return false; +} + +/* ---------------------------------------------------------------------- */ + +void file_create_asset_catalog_tree_view_in_layout(::AssetLibrary *asset_library, + uiLayout *layout, + SpaceFile *space_file, + FileAssetSelectParams *params) +{ + uiBlock *block = uiLayoutGetBlock(layout); + + UI_block_layout_set_current(block, layout); + + ui::AbstractTreeView *tree_view = UI_block_add_view( + *block, + "asset catalog tree view", + std::make_unique<ed::asset_browser::AssetCatalogTreeView>( + asset_library, params, *space_file)); + + ui::TreeViewBuilder builder(*block); + builder.build_tree_view(*tree_view); +} diff --git a/source/blender/editors/space_file/file_draw.c b/source/blender/editors/space_file/file_draw.c index 6e409d2f5a4..66aabe39e44 100644 --- a/source/blender/editors/space_file/file_draw.c +++ b/source/blender/editors/space_file/file_draw.c @@ -25,6 +25,8 @@ #include <math.h> #include <string.h> +#include "MEM_guardedalloc.h" + #include "BLI_alloca.h" #include "BLI_blenlib.h" #include "BLI_fileops_types.h" @@ -80,6 +82,9 @@ void ED_file_path_button(bScreen *screen, PointerRNA params_rna_ptr; uiBut *but; + BLI_assert_msg(params != NULL, + "File select parameters not set. The caller is expected to check this."); + RNA_pointer_create(&screen->id, &RNA_FileSelectParams, params, ¶ms_rna_ptr); /* callbacks for operator check functions */ @@ -108,7 +113,7 @@ void ED_file_path_button(bScreen *screen, UI_but_func_complete_set(but, autocomplete_directory, NULL); UI_but_funcN_set(but, file_directory_enter_handle, NULL, but); - /* TODO, directory editing is non-functional while a library is loaded + /* TODO: directory editing is non-functional while a library is loaded * until this is properly supported just disable it. */ if (sfile && sfile->files && filelist_lib(sfile->files)) { UI_but_flag_enable(but, UI_BUT_DISABLED); @@ -165,10 +170,10 @@ static void file_draw_icon(const SpaceFile *sfile, const float a2 = dimmed ? 0.3f : 0.0f; but = uiDefIconBut( block, UI_BTYPE_LABEL, 0, icon, x, y, width, height, NULL, 0.0f, 0.0f, a1, a2, NULL); - UI_but_func_tooltip_set(but, file_draw_tooltip_func, BLI_strdup(path)); + UI_but_func_tooltip_set(but, file_draw_tooltip_func, BLI_strdup(path), MEM_freeN); if (drag) { - /* TODO duplicated from file_draw_preview(). */ + /* TODO: duplicated from file_draw_preview(). */ ID *id; if ((id = filelist_file_get_id(file))) { @@ -183,9 +188,9 @@ static void file_draw_icon(const SpaceFile *sfile, BLI_assert(asset_params != NULL); UI_but_drag_set_asset(but, - file->name, + &(AssetHandle){.file_data = file}, BLI_strdup(blend_path), - file->blentype, + file->asset_data, asset_params->import_type, icon, preview_image, @@ -238,8 +243,9 @@ static void file_draw_string(int sx, } /** - * \param r_sx, r_sy: The lower right corner of the last line drawn. AKA the cursor position on - * completion. + * \param r_sx, r_sy: The lower right corner of the last line drawn, plus the height of the last + * line. This is the cursor position on completion to allow drawing more text + * behind that. */ static void file_draw_string_multiline(int sx, int sy, @@ -461,6 +467,17 @@ static void file_draw_preview(const SpaceFile *sfile, UI_icon_draw_ex(icon_x, icon_y, icon, 1.0f / U.dpi_fac, 0.6f, 0.0f, light, false); } + const bool is_current_main_data = filelist_file_get_id(file) != NULL; + if (is_current_main_data) { + /* Smaller, fainter icon at the top-right indicating that the file represents data from the + * current file (from current #Main in fact). */ + float icon_x, icon_y; + const uchar light[4] = {255, 255, 255, 255}; + icon_x = xco + ex - UI_UNIT_X; + icon_y = yco + ey - UI_UNIT_Y; + UI_icon_draw_ex(icon_x, icon_y, ICON_FILE_BLEND, 1.0f / U.dpi_fac, 0.6f, 0.0f, light, false); + } + /* Contrasting outline around some preview types. */ if (show_outline) { GPUVertFormat *format = immVertexFormat(); @@ -489,7 +506,8 @@ static void file_draw_preview(const SpaceFile *sfile, UI_but_drag_set_id(but, id); } /* path is no more static, cannot give it directly to but... */ - else if (file->typeflag & FILE_TYPE_ASSET) { + else if (sfile->browse_mode == FILE_BROWSE_MODE_ASSETS && + (file->typeflag & FILE_TYPE_ASSET) != 0) { char blend_path[FILE_MAX_LIBEXTRA]; if (BLO_library_path_explode(path, blend_path, NULL, NULL)) { @@ -497,9 +515,9 @@ static void file_draw_preview(const SpaceFile *sfile, BLI_assert(asset_params != NULL); UI_but_drag_set_asset(but, - file->name, + &(AssetHandle){.file_data = file}, BLI_strdup(blend_path), - file->blentype, + file->asset_data, asset_params->import_type, icon, imb, @@ -520,6 +538,7 @@ static void renamebutton_cb(bContext *C, void *UNUSED(arg1), char *oldname) char orgname[FILE_MAX + 12]; char filename[FILE_MAX + 12]; wmWindowManager *wm = CTX_wm_manager(C); + wmWindow *win = CTX_wm_window(C); SpaceFile *sfile = (SpaceFile *)CTX_wm_space_data(C); ARegion *region = CTX_wm_region(C); FileSelectParams *params = ED_fileselect_get_active_params(sfile); @@ -539,17 +558,15 @@ static void renamebutton_cb(bContext *C, void *UNUSED(arg1), char *oldname) else { /* If rename is successful, scroll to newly renamed entry. */ BLI_strncpy(params->renamefile, filename, sizeof(params->renamefile)); - params->rename_flag = FILE_PARAMS_RENAME_POSTSCROLL_PENDING; - - if (sfile->smoothscroll_timer != NULL) { - WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), sfile->smoothscroll_timer); - } - sfile->smoothscroll_timer = WM_event_add_timer(wm, CTX_wm_window(C), TIMER1, 1.0 / 1000.0); - sfile->scroll_offset = 0; + file_params_invoke_rename_postscroll(wm, win, sfile); } /* to make sure we show what is on disk */ - ED_fileselect_clear(wm, CTX_data_scene(C), sfile); + ED_fileselect_clear(wm, sfile); + } + else { + /* Renaming failed, reset the name for further renaming handling. */ + BLI_strncpy(params->renamefile, oldname, sizeof(params->renamefile)); } ED_region_tag_redraw(region); @@ -724,40 +741,45 @@ static void draw_columnheader_columns(const FileSelectParams *params, /** * Updates the stat string stored in file->entry if necessary. */ -static const char *filelist_get_details_column_string(FileAttributeColumnType column, - const FileDirEntry *file, - const bool small_size, - const bool update_stat_strings) +static const char *filelist_get_details_column_string( + FileAttributeColumnType column, + /* Generated string will be cached in the file, so non-const. */ + FileDirEntry *file, + const bool small_size, + const bool update_stat_strings) { switch (column) { case COLUMN_DATETIME: if (!(file->typeflag & FILE_TYPE_BLENDERLIB) && !FILENAME_IS_CURRPAR(file->relpath)) { - if ((file->entry->datetime_str[0] == '\0') || update_stat_strings) { + if ((file->draw_data.datetime_str[0] == '\0') || update_stat_strings) { char date[FILELIST_DIRENTRY_DATE_LEN], time[FILELIST_DIRENTRY_TIME_LEN]; bool is_today, is_yesterday; BLI_filelist_entry_datetime_to_string( - NULL, file->entry->time, small_size, time, date, &is_today, &is_yesterday); + NULL, file->time, small_size, time, date, &is_today, &is_yesterday); if (is_today || is_yesterday) { BLI_strncpy(date, is_today ? N_("Today") : N_("Yesterday"), sizeof(date)); } - BLI_snprintf( - file->entry->datetime_str, sizeof(file->entry->datetime_str), "%s %s", date, time); + BLI_snprintf(file->draw_data.datetime_str, + sizeof(file->draw_data.datetime_str), + "%s %s", + date, + time); } - return file->entry->datetime_str; + return file->draw_data.datetime_str; } break; case COLUMN_SIZE: if ((file->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) || !(file->typeflag & (FILE_TYPE_DIR | FILE_TYPE_BLENDERLIB))) { - if ((file->entry->size_str[0] == '\0') || update_stat_strings) { + if ((file->draw_data.size_str[0] == '\0') || update_stat_strings) { BLI_filelist_entry_size_to_string( - NULL, file->entry->size, small_size, file->entry->size_str); + NULL, file->size, small_size, file->draw_data.size_str); } - return file->entry->size_str; + return file->draw_data.size_str; } break; default: @@ -769,7 +791,7 @@ static const char *filelist_get_details_column_string(FileAttributeColumnType co static void draw_details_columns(const FileSelectParams *params, const FileLayout *layout, - const FileDirEntry *file, + FileDirEntry *file, const int pos_x, const int pos_y, const uchar text_col[4]) @@ -810,6 +832,8 @@ static void draw_details_columns(const FileSelectParams *params, void file_draw_list(const bContext *C, ARegion *region) { + wmWindowManager *wm = CTX_wm_manager(C); + wmWindow *win = CTX_wm_window(C); SpaceFile *sfile = CTX_wm_space_file(C); FileSelectParams *params = ED_fileselect_get_active_params(sfile); FileLayout *layout = ED_fileselect_get_layout(sfile, region); @@ -880,12 +904,12 @@ void file_draw_list(const bContext *C, ARegion *region) // printf("%s: preview task: %d\n", __func__, previews_running); if (previews_running && !sfile->previews_timer) { sfile->previews_timer = WM_event_add_timer_notifier( - CTX_wm_manager(C), CTX_wm_window(C), NC_SPACE | ND_SPACE_FILE_PREVIEW, 0.01); + wm, win, NC_SPACE | ND_SPACE_FILE_PREVIEW, 0.01); } if (!previews_running && sfile->previews_timer) { /* Preview is not running, no need to keep generating update events! */ // printf("%s: Inactive preview task, sleeping!\n", __func__); - WM_event_remove_timer_notifier(CTX_wm_manager(C), CTX_wm_window(C), sfile->previews_timer); + WM_event_remove_timer_notifier(wm, win, sfile->previews_timer); sfile->previews_timer = NULL; } } @@ -996,8 +1020,19 @@ void file_draw_list(const bContext *C, ARegion *region) UI_but_flag_enable(but, UI_BUT_NO_UTF8); /* allow non utf8 names */ UI_but_flag_disable(but, UI_BUT_UNDO); if (false == UI_but_active_only(C, region, block, but)) { - file_selflag = filelist_entry_select_set( - sfile->files, file, FILE_SEL_REMOVE, FILE_SEL_EDITING, CHECK_ALL); + /* Note that this is the only place where we can also handle a cancelled renaming. */ + + file_params_rename_end(wm, win, sfile, file); + + /* After the rename button is removed, we need to make sure the view is redrawn once more, + * in case selection changed. Usually UI code would trigger that redraw, but the rename + * operator may have been called from a different region. + * Tagging regions for redrawing while drawing is rightfully prevented. However, this + * active button removing basically introduces handling logic to drawing code. So a + * notifier should be an acceptable workaround. */ + WM_event_add_notifier_ex(wm, win, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL); + + file_selflag = filelist_entry_select_get(sfile->files, file, CHECK_ALL); } } @@ -1032,7 +1067,9 @@ void file_draw_list(const bContext *C, ARegion *region) layout->curr_size = params->thumbnail_size; } -static void file_draw_invalid_library_hint(const SpaceFile *sfile, const ARegion *region) +static void file_draw_invalid_library_hint(const bContext *C, + const SpaceFile *sfile, + ARegion *region) { const FileAssetSelectParams *asset_params = ED_fileselect_get_asset_params(sfile); @@ -1040,9 +1077,7 @@ static void file_draw_invalid_library_hint(const SpaceFile *sfile, const ARegion file_path_to_ui_path(asset_params->base_params.dir, library_ui_path, sizeof(library_ui_path)); uchar text_col[4]; - uchar text_alert_col[4]; UI_GetThemeColor4ubv(TH_TEXT, text_col); - UI_GetThemeColor4ubv(TH_REDALERT, text_alert_col); const View2D *v2d = ®ion->v2d; const int pad = sfile->layout->tile_border_x; @@ -1053,23 +1088,42 @@ static void file_draw_invalid_library_hint(const SpaceFile *sfile, const ARegion int sy = v2d->tot.ymax; { - const char *message = TIP_("Library not found"); - const int draw_string_str_len = strlen(message) + 2 + sizeof(library_ui_path); - char *draw_string = alloca(draw_string_str_len); - BLI_snprintf(draw_string, draw_string_str_len, "%s: %s", message, library_ui_path); - file_draw_string_multiline(sx, sy, draw_string, width, line_height, text_alert_col, NULL, &sy); + const char *message = TIP_("Path to asset library does not exist:"); + file_draw_string_multiline(sx, sy, message, width, line_height, text_col, NULL, &sy); + + sy -= line_height; + file_draw_string(sx, sy, library_ui_path, width, line_height, UI_STYLE_TEXT_LEFT, text_col); } - /* Next line, but separate it a bit further. */ - sy -= line_height; + /* Separate a bit further. */ + sy -= line_height * 2.2f; { UI_icon_draw(sx, sy - UI_UNIT_Y, ICON_INFO); const char *suggestion = TIP_( - "Set up the library or edit libraries in the Preferences, File Paths section"); + "Asset Libraries are local directories that can contain .blend files with assets inside.\n" + "Manage Asset Libraries from the File Paths section in Preferences"); file_draw_string_multiline( - sx + UI_UNIT_X, sy, suggestion, width - UI_UNIT_X, line_height, text_col, NULL, NULL); + sx + UI_UNIT_X, sy, suggestion, width - UI_UNIT_X, line_height, text_col, NULL, &sy); + + uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS); + uiBut *but = uiDefIconTextButO(block, + UI_BTYPE_BUT, + "SCREEN_OT_userpref_show", + WM_OP_INVOKE_DEFAULT, + ICON_PREFERENCES, + NULL, + sx + UI_UNIT_X, + sy - line_height - UI_UNIT_Y * 1.2f, + UI_UNIT_X * 8, + UI_UNIT_Y, + NULL); + PointerRNA *but_opptr = UI_but_operator_ptr_get(but); + RNA_enum_set(but_opptr, "section", USER_SECTION_FILE_PATHS); + + UI_block_end(C, block); + UI_block_draw(C, block); } } @@ -1077,7 +1131,7 @@ static void file_draw_invalid_library_hint(const SpaceFile *sfile, const ARegion * Draw a string hint if the file list is invalid. * \return true if the list is invalid and a hint was drawn. */ -bool file_draw_hint_if_invalid(const SpaceFile *sfile, const ARegion *region) +bool file_draw_hint_if_invalid(const bContext *C, const SpaceFile *sfile, ARegion *region) { FileAssetSelectParams *asset_params = ED_fileselect_get_asset_params(sfile); /* Only for asset browser. */ @@ -1085,12 +1139,12 @@ bool file_draw_hint_if_invalid(const SpaceFile *sfile, const ARegion *region) return false; } /* Check if the library exists. */ - if ((asset_params->asset_library.type == FILE_ASSET_LIBRARY_LOCAL) || + if ((asset_params->asset_library_ref.type == ASSET_LIBRARY_LOCAL) || filelist_is_dir(sfile->files, asset_params->base_params.dir)) { return false; } - file_draw_invalid_library_hint(sfile, region); + file_draw_invalid_library_hint(C, sfile, region); return true; } diff --git a/source/blender/editors/space_file/file_intern.h b/source/blender/editors/space_file/file_intern.h index a7c57459729..4be5d6d8008 100644 --- a/source/blender/editors/space_file/file_intern.h +++ b/source/blender/editors/space_file/file_intern.h @@ -23,12 +23,21 @@ #pragma once +#include "DNA_space_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + /* internal exports only */ struct ARegion; struct ARegionType; +struct AssetLibrary; struct FileSelectParams; +struct FileAssetSelectParams; struct SpaceFile; +struct uiLayout; struct View2D; /* file_draw.c */ @@ -38,7 +47,7 @@ struct View2D; void file_calc_previews(const bContext *C, ARegion *region); void file_draw_list(const bContext *C, ARegion *region); -bool file_draw_hint_if_invalid(const SpaceFile *sfile, const ARegion *region); +bool file_draw_hint_if_invalid(const bContext *C, const SpaceFile *sfile, ARegion *region); void file_draw_check_ex(bContext *C, struct ScrArea *area); void file_draw_check(bContext *C); @@ -62,7 +71,6 @@ void FILE_OT_bookmark_cleanup(struct wmOperatorType *ot); void FILE_OT_bookmark_move(struct wmOperatorType *ot); void FILE_OT_reset_recent(wmOperatorType *ot); void FILE_OT_hidedot(struct wmOperatorType *ot); -void FILE_OT_associate_blend(struct wmOperatorType *ot); void FILE_OT_execute(struct wmOperatorType *ot); void FILE_OT_mouse_execute(struct wmOperatorType *ot); void FILE_OT_cancel(struct wmOperatorType *ot); @@ -71,6 +79,7 @@ void FILE_OT_directory_new(struct wmOperatorType *ot); void FILE_OT_previous(struct wmOperatorType *ot); void FILE_OT_next(struct wmOperatorType *ot); void FILE_OT_refresh(struct wmOperatorType *ot); +void FILE_OT_asset_library_refresh(struct wmOperatorType *ot); void FILE_OT_filenum(struct wmOperatorType *ot); void FILE_OT_delete(struct wmOperatorType *ot); void FILE_OT_rename(struct wmOperatorType *ot); @@ -93,6 +102,9 @@ void file_sfile_to_operator(struct Main *bmain, struct wmOperator *op, struct Sp void file_operator_to_sfile(struct Main *bmain, struct SpaceFile *sfile, struct wmOperator *op); +/* space_file.c */ +extern const char *file_context_dir[]; /* doc access */ + /* filesel.c */ void fileselect_refresh_params(struct SpaceFile *sfile); void fileselect_file_set(SpaceFile *sfile, const int index); @@ -109,10 +121,22 @@ FileAttributeColumnType file_attribute_column_type_find_isect(const View2D *v2d, float file_string_width(const char *str); float file_font_pointsize(void); +void file_select_deselect_all(SpaceFile *sfile, uint flag); int file_select_match(struct SpaceFile *sfile, const char *pattern, char *matched_file); int autocomplete_directory(struct bContext *C, char *str, void *arg_v); int autocomplete_file(struct bContext *C, char *str, void *arg_v); +void file_params_smoothscroll_timer_clear(struct wmWindowManager *wm, + struct wmWindow *win, + SpaceFile *sfile); +void file_params_renamefile_clear(struct FileSelectParams *params); +void file_params_invoke_rename_postscroll(struct wmWindowManager *wm, + struct wmWindow *win, + SpaceFile *sfile); +void file_params_rename_end(struct wmWindowManager *wm, + struct wmWindow *win, + SpaceFile *sfile, + struct FileDirEntry *rename_file); void file_params_renamefile_activate(struct SpaceFile *sfile, struct FileSelectParams *params); typedef void *onReloadFnData; @@ -133,8 +157,40 @@ 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 */ + +/* C-handle for #ed::asset_browser::AssetCatalogFilterSettings. */ +typedef struct FileAssetCatalogFilterSettingsHandle FileAssetCatalogFilterSettingsHandle; + +FileAssetCatalogFilterSettingsHandle *file_create_asset_catalog_filter_settings(void); +void file_delete_asset_catalog_filter_settings( + FileAssetCatalogFilterSettingsHandle **filter_settings_handle); +/** + * \return True if the stored filter settings were modified. + */ +bool file_set_asset_catalog_filter_settings( + FileAssetCatalogFilterSettingsHandle *filter_settings_handle, + eFileSel_Params_AssetCatalogVisibility catalog_visibility, + bUUID catalog_id); +void file_ensure_updated_catalog_filter_data( + FileAssetCatalogFilterSettingsHandle *filter_settings_handle, + const struct AssetLibrary *asset_library); +bool file_is_asset_visible_in_catalog_filter_settings( + const FileAssetCatalogFilterSettingsHandle *filter_settings_handle, + const AssetMetaData *asset_data); + +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 +} +#endif diff --git a/source/blender/editors/space_file/file_ops.c b/source/blender/editors/space_file/file_ops.c index 0584e2ff938..844514759f3 100644 --- a/source/blender/editors/space_file/file_ops.c +++ b/source/blender/editors/space_file/file_ops.c @@ -104,15 +104,6 @@ static FileSelection find_file_mouse_rect(SpaceFile *sfile, return sel; } -static void file_deselect_all(SpaceFile *sfile, uint flag) -{ - FileSelection sel; - sel.first = 0; - sel.last = filelist_files_ensure(sfile->files) - 1; - - filelist_entries_select_index_range_set(sfile->files, &sel, FILE_SEL_REMOVE, flag, CHECK_ALL); -} - typedef enum FileSelect { FILE_SELECT_NOTHING = 0, FILE_SELECT_DIR = 1, @@ -239,7 +230,7 @@ static FileSelect file_select_do(bContext *C, int selected_idx, bool do_diropen) } /** - * \warning: loops over all files so better use cautiously + * \warning Loops over all files so better use cautiously. */ static bool file_is_any_selected(struct FileList *files) { @@ -444,7 +435,7 @@ static int file_box_select_modal(bContext *C, wmOperator *op, const wmEvent *eve if ((sel.first != params->sel_first) || (sel.last != params->sel_last)) { int idx; - file_deselect_all(sfile, FILE_SEL_HIGHLIGHTED); + file_select_deselect_all(sfile, FILE_SEL_HIGHLIGHTED); filelist_entries_select_index_range_set( sfile->files, &sel, FILE_SEL_ADD, FILE_SEL_HIGHLIGHTED, CHECK_ALL); WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL); @@ -472,7 +463,7 @@ static int file_box_select_modal(bContext *C, wmOperator *op, const wmEvent *eve params->highlight_file = -1; params->sel_first = params->sel_last = -1; fileselect_file_set(sfile, params->active_file); - file_deselect_all(sfile, FILE_SEL_HIGHLIGHTED); + file_select_deselect_all(sfile, FILE_SEL_HIGHLIGHTED); WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL); } @@ -491,7 +482,7 @@ static int file_box_select_exec(bContext *C, wmOperator *op) const eSelectOp sel_op = RNA_enum_get(op->ptr, "mode"); const bool select = (sel_op != SEL_OP_SUB); if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - file_deselect_all(sfile, FILE_SEL_SELECTED); + file_select_deselect_all(sfile, FILE_SEL_SELECTED); } ED_fileselect_layout_isect_rect(sfile->layout, ®ion->v2d, &rect, &rect); @@ -522,6 +513,7 @@ void FILE_OT_select_box(wmOperatorType *ot) ot->invoke = WM_gesture_box_invoke; ot->exec = file_box_select_exec; ot->modal = file_box_select_modal; + /* Operator works for file or asset browsing */ ot->poll = ED_operator_file_active; ot->cancel = WM_gesture_box_cancel; @@ -544,7 +536,7 @@ static rcti file_select_mval_to_select_rect(const int mval[2]) return rect; } -static int file_select_invoke(bContext *C, wmOperator *op, const wmEvent *event) +static int file_select_exec(bContext *C, wmOperator *op) { ARegion *region = CTX_wm_region(C); SpaceFile *sfile = CTX_wm_space_file(C); @@ -554,26 +546,47 @@ static int file_select_invoke(bContext *C, wmOperator *op, const wmEvent *event) const bool fill = RNA_boolean_get(op->ptr, "fill"); const bool do_diropen = RNA_boolean_get(op->ptr, "open"); const bool deselect_all = RNA_boolean_get(op->ptr, "deselect_all"); + const bool only_activate_if_selected = RNA_boolean_get(op->ptr, "only_activate_if_selected"); + /* Used so right mouse clicks can do both, activate and spawn the context menu. */ + const bool pass_through = RNA_boolean_get(op->ptr, "pass_through"); + bool wait_to_deselect_others = RNA_boolean_get(op->ptr, "wait_to_deselect_others"); if (region->regiontype != RGN_TYPE_WINDOW) { return OPERATOR_CANCELLED; } - rect = file_select_mval_to_select_rect(event->mval); + int mval[2]; + mval[0] = RNA_int_get(op->ptr, "mouse_x"); + mval[1] = RNA_int_get(op->ptr, "mouse_y"); + rect = file_select_mval_to_select_rect(mval); if (!ED_fileselect_layout_is_inside_pt(sfile->layout, ®ion->v2d, rect.xmin, rect.ymin)) { return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; } + if (extend || fill) { + wait_to_deselect_others = false; + } + + int ret_val = OPERATOR_FINISHED; + const FileSelectParams *params = ED_fileselect_get_active_params(sfile); if (sfile && params) { int idx = params->highlight_file; int numfiles = filelist_files_ensure(sfile->files); if ((idx >= 0) && (idx < numfiles)) { + const bool is_selected = filelist_entry_select_index_get(sfile->files, idx, CHECK_ALL) & + FILE_SEL_SELECTED; + if (only_activate_if_selected && is_selected) { + /* Don't deselect other items. */ + } + else if (wait_to_deselect_others && is_selected) { + ret_val = OPERATOR_RUNNING_MODAL; + } /* single select, deselect all selected first */ - if (!extend) { - file_deselect_all(sfile, FILE_SEL_SELECTED); + else if (!extend) { + file_select_deselect_all(sfile, FILE_SEL_SELECTED); } } } @@ -588,7 +601,7 @@ static int file_select_invoke(bContext *C, wmOperator *op, const wmEvent *event) if (ret == FILE_SELECT_NOTHING) { if (deselect_all) { - file_deselect_all(sfile, FILE_SEL_SELECTED); + file_select_deselect_all(sfile, FILE_SEL_SELECTED); } } else if (ret == FILE_SELECT_DIR) { @@ -601,7 +614,10 @@ static int file_select_invoke(bContext *C, wmOperator *op, const wmEvent *event) WM_event_add_mousemove(CTX_wm_window(C)); /* for directory changes */ WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL); - return OPERATOR_FINISHED; + if ((ret_val == OPERATOR_FINISHED) && pass_through) { + ret_val |= OPERATOR_PASS_THROUGH; + } + return ret_val; } void FILE_OT_select(wmOperatorType *ot) @@ -614,10 +630,14 @@ void FILE_OT_select(wmOperatorType *ot) ot->description = "Handle mouse clicks to select and activate items"; /* api callbacks */ - ot->invoke = file_select_invoke; + ot->invoke = WM_generic_select_invoke; + ot->exec = file_select_exec; + ot->modal = WM_generic_select_modal; + /* Operator works for file or asset browsing */ ot->poll = ED_operator_file_active; /* properties */ + WM_operator_properties_generic_select(ot); prop = RNA_def_boolean(ot->srna, "extend", false, @@ -635,6 +655,20 @@ void FILE_OT_select(wmOperatorType *ot) "Deselect On Nothing", "Deselect all when nothing under the cursor"); RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean(ot->srna, + "only_activate_if_selected", + false, + "Only Activate if Selected", + "Do not change selection if the item under the cursor is already " + "selected, only activate it"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean(ot->srna, + "pass_through", + false, + "Pass Through", + "Even on successful execution, pass the event on so other operators can " + "execute on it as well"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN); } /** \} */ @@ -721,7 +755,7 @@ static bool file_walk_select_selection_set(wmWindow *win, } else { /* deselect all first */ - file_deselect_all(sfile, FILE_SEL_SELECTED); + file_select_deselect_all(sfile, FILE_SEL_SELECTED); /* highlight file under mouse pos */ params->highlight_file = -1; @@ -873,6 +907,7 @@ void FILE_OT_select_walk(wmOperatorType *ot) /* api callbacks */ ot->invoke = file_walk_select_invoke; + /* Operator works for file or asset browsing */ ot->poll = ED_operator_file_active; /* properties */ @@ -960,6 +995,7 @@ void FILE_OT_select_all(wmOperatorType *ot) /* api callbacks */ ot->exec = file_select_all_exec; + /* Operator works for file or asset browsing */ ot->poll = ED_operator_file_active; /* properties */ @@ -1012,6 +1048,7 @@ void FILE_OT_view_selected(wmOperatorType *ot) /* api callbacks */ ot->exec = file_view_selected_exec; + /* Operator works for file or asset browsing */ ot->poll = ED_operator_file_active; } @@ -1023,7 +1060,6 @@ void FILE_OT_view_selected(wmOperatorType *ot) /* Note we could get rid of this one, but it's used by some addon so... * Does not hurt keeping it around for now. */ -/* TODO disallow bookmark editing in assets mode? */ static int bookmark_select_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); @@ -1056,7 +1092,8 @@ void FILE_OT_select_bookmark(wmOperatorType *ot) /* api callbacks */ ot->exec = bookmark_select_exec; - ot->poll = ED_operator_file_active; + /* Bookmarks are for file browsing only (not asset browsing). */ + ot->poll = ED_operator_file_browsing_active; /* properties */ prop = RNA_def_string(ot->srna, "dir", NULL, FILE_MAXDIR, "Directory", ""); @@ -1102,7 +1139,8 @@ void FILE_OT_bookmark_add(wmOperatorType *ot) /* api callbacks */ ot->exec = bookmark_add_exec; - ot->poll = ED_operator_file_active; + /* Bookmarks are for file browsing only (not asset browsing). */ + ot->poll = ED_operator_file_browsing_active; } /** \} */ @@ -1156,7 +1194,8 @@ void FILE_OT_bookmark_delete(wmOperatorType *ot) /* api callbacks */ ot->exec = bookmark_delete_exec; - ot->poll = ED_operator_file_active; + /* Bookmarks are for file browsing only (not asset browsing). */ + ot->poll = ED_operator_file_browsing_active; /* properties */ prop = RNA_def_int(ot->srna, "index", -1, -1, 20000, "Index", "", -1, 20000); @@ -1214,7 +1253,8 @@ void FILE_OT_bookmark_cleanup(wmOperatorType *ot) /* api callbacks */ ot->exec = bookmark_cleanup_exec; - ot->poll = ED_operator_file_active; + /* Bookmarks are for file browsing only (not asset browsing). */ + ot->poll = ED_operator_file_browsing_active; /* properties */ } @@ -1302,8 +1342,9 @@ void FILE_OT_bookmark_move(wmOperatorType *ot) ot->description = "Move the active bookmark up/down in the list"; /* api callbacks */ - ot->poll = ED_operator_file_active; ot->exec = bookmark_move_exec; + /* Bookmarks are for file browsing only (not asset browsing). */ + ot->poll = ED_operator_file_browsing_active; /* flags */ ot->flag = OPTYPE_REGISTER; /* No undo! */ @@ -1350,7 +1391,8 @@ void FILE_OT_reset_recent(wmOperatorType *ot) /* api callbacks */ ot->exec = reset_recent_exec; - ot->poll = ED_operator_file_active; + /* File browsing only operator (not asset browsing). */ + ot->poll = ED_operator_file_browsing_active; } /** \} */ @@ -1365,7 +1407,9 @@ int file_highlight_set(SpaceFile *sfile, ARegion *region, int mx, int my) FileSelectParams *params; int numfiles, origfile; - if (sfile == NULL || sfile->files == NULL) { + /* In case blender starts where the mouse is over a File browser, + * this operator can be invoked when the `sfile` or `sfile->layout` isn't initialized yet. */ + if (sfile == NULL || sfile->files == NULL || sfile->layout == NULL) { return 0; } @@ -1405,7 +1449,7 @@ static int file_highlight_invoke(bContext *C, wmOperator *UNUSED(op), const wmEv ARegion *region = CTX_wm_region(C); SpaceFile *sfile = CTX_wm_space_file(C); - if (!file_highlight_set(sfile, region, event->x, event->y)) { + if (!file_highlight_set(sfile, region, event->xy[0], event->xy[1])) { return OPERATOR_PASS_THROUGH; } @@ -1423,6 +1467,7 @@ void FILE_OT_highlight(struct wmOperatorType *ot) /* api callbacks */ ot->invoke = file_highlight_invoke; + /* Operator works for file or asset browsing */ ot->poll = ED_operator_file_active; } @@ -1474,6 +1519,7 @@ void FILE_OT_sort_column_ui_context(wmOperatorType *ot) /* api callbacks */ ot->invoke = file_column_sort_ui_context_invoke; + /* Operator works for file or asset browsing */ ot->poll = ED_operator_file_active; ot->flag = OPTYPE_INTERNAL; @@ -1487,7 +1533,7 @@ void FILE_OT_sort_column_ui_context(wmOperatorType *ot) static bool file_operator_poll(bContext *C) { - bool poll = ED_operator_file_active(C); + bool poll = ED_operator_file_browsing_active(C); SpaceFile *sfile = CTX_wm_space_file(C); if (!sfile || !sfile->op) { @@ -1810,7 +1856,7 @@ void FILE_OT_execute(struct wmOperatorType *ot) * * Avoid using #file_operator_poll since this is also used for entering directories * which is used even when the file manager doesn't have an operator. */ - ot->poll = ED_operator_file_active; + ot->poll = ED_operator_file_browsing_active; } /** @@ -1865,7 +1911,7 @@ void FILE_OT_mouse_execute(wmOperatorType *ot) /* api callbacks */ ot->invoke = file_execute_mouse_invoke; - ot->poll = ED_operator_file_active; + ot->poll = ED_operator_file_browsing_active; ot->flag = OPTYPE_INTERNAL; } @@ -1882,7 +1928,7 @@ static int file_refresh_exec(bContext *C, wmOperator *UNUSED(unused)) SpaceFile *sfile = CTX_wm_space_file(C); struct FSMenu *fsmenu = ED_fsmenu_get(); - ED_fileselect_clear(wm, CTX_data_scene(C), sfile); + ED_fileselect_clear(wm, sfile); /* refresh system directory menu */ fsmenu_refresh_system_category(fsmenu); @@ -1904,7 +1950,36 @@ void FILE_OT_refresh(struct wmOperatorType *ot) /* api callbacks */ ot->exec = file_refresh_exec; - ot->poll = ED_operator_file_active; /* <- important, handler is on window level */ + ot->poll = ED_operator_file_browsing_active; /* <- important, handler is on window level */ +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Refresh Asset Library Operator + * \{ */ + +static int file_asset_library_refresh_exec(bContext *C, wmOperator *UNUSED(unused)) +{ + wmWindowManager *wm = CTX_wm_manager(C); + SpaceFile *sfile = CTX_wm_space_file(C); + + ED_fileselect_clear(wm, sfile); + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL); + + return OPERATOR_FINISHED; +} + +void FILE_OT_asset_library_refresh(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Refresh Asset Library"; + ot->description = "Reread assets and asset catalogs from the asset library on disk"; + ot->idname = "FILE_OT_asset_library_refresh"; + + /* api callbacks */ + ot->exec = file_asset_library_refresh_exec; + ot->poll = ED_operator_asset_browsing_active; } /** \} */ @@ -1944,7 +2019,8 @@ void FILE_OT_parent(struct wmOperatorType *ot) /* api callbacks */ ot->exec = file_parent_exec; - ot->poll = ED_operator_file_active; /* <- important, handler is on window level */ + /* File browsing only operator (not asset browsing). */ + ot->poll = ED_operator_file_browsing_active; /* <- important, handler is on window level */ } /** \} */ @@ -1979,7 +2055,8 @@ void FILE_OT_previous(struct wmOperatorType *ot) /* api callbacks */ ot->exec = file_previous_exec; - ot->poll = ED_operator_file_active; /* <- important, handler is on window level */ + /* File browsing only operator (not asset browsing). */ + ot->poll = ED_operator_file_browsing_active; /* <- important, handler is on window level */ } /** \} */ @@ -2015,7 +2092,8 @@ void FILE_OT_next(struct wmOperatorType *ot) /* api callbacks */ ot->exec = file_next_exec; - ot->poll = ED_operator_file_active; /* <- important, handler is on window level */ + /* File browsing only operator (not asset browsing). */ + ot->poll = ED_operator_file_browsing_active; /* <- important, handler is on window level */ } /** \} */ @@ -2059,13 +2137,15 @@ static int file_smoothscroll_invoke(bContext *C, wmOperator *UNUSED(op), const w } } + wmWindowManager *wm = CTX_wm_manager(C); + wmWindow *win = CTX_wm_window(C); + /* if we are not editing, we are done */ if (edit_idx == -1) { /* Do not invalidate timer if filerename is still pending, * we might still be building the filelist and yet have to find edited entry. */ if (params->rename_flag == 0) { - WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), sfile->smoothscroll_timer); - sfile->smoothscroll_timer = NULL; + file_params_smoothscroll_timer_clear(wm, win, sfile); } return OPERATOR_PASS_THROUGH; } @@ -2073,8 +2153,7 @@ static int file_smoothscroll_invoke(bContext *C, wmOperator *UNUSED(op), const w /* we need the correct area for scrolling */ region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW); if (!region || region->regiontype != RGN_TYPE_WINDOW) { - WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), sfile->smoothscroll_timer); - sfile->smoothscroll_timer = NULL; + file_params_smoothscroll_timer_clear(wm, win, sfile); return OPERATOR_PASS_THROUGH; } @@ -2093,7 +2172,7 @@ static int file_smoothscroll_invoke(bContext *C, wmOperator *UNUSED(op), const w sfile->layout, (int)region->v2d.cur.xmin, (int)-region->v2d.cur.ymax); const int last_visible_item = first_visible_item + numfiles_layout + 1; - /* Note: the special case for vertical layout is because filename is at the bottom of items then, + /* NOTE: the special case for vertical layout is because filename is at the bottom of items then, * so we artificially move current row back one step, to ensure we show bottom of * active item rather than its top (important in case visible height is low). */ const int middle_offset = max_ii( @@ -2131,13 +2210,11 @@ static int file_smoothscroll_invoke(bContext *C, wmOperator *UNUSED(op), const w (max_middle_offset - middle_offset < items_block_size)); if (is_ready && (is_centered || is_full_start || is_full_end)) { - WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), sfile->smoothscroll_timer); - sfile->smoothscroll_timer = NULL; + file_params_smoothscroll_timer_clear(wm, win, sfile); /* Post-scroll (after rename has been validated by user) is done, * rename process is totally finished, cleanup. */ if ((params->rename_flag & FILE_PARAMS_RENAME_POSTSCROLL_ACTIVE) != 0) { - params->renamefile[0] = '\0'; - params->rename_flag = 0; + file_params_renamefile_clear(params); } return OPERATOR_FINISHED; } @@ -2207,7 +2284,7 @@ void FILE_OT_smoothscroll(wmOperatorType *ot) /* api callbacks */ ot->invoke = file_smoothscroll_invoke; - + /* Operator works for file or asset browsing */ ot->poll = ED_operator_file_active; } @@ -2251,7 +2328,8 @@ void FILE_OT_filepath_drop(wmOperatorType *ot) ot->idname = "FILE_OT_filepath_drop"; ot->exec = filepath_drop_exec; - ot->poll = WM_operator_winactive; + /* File browsing only operator (not asset browsing). */ + ot->poll = ED_operator_file_browsing_active; RNA_def_string_file_path(ot->srna, "filepath", "Path", FILE_MAX, "", ""); } @@ -2346,21 +2424,22 @@ static int file_directory_new_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + eFileSel_Params_RenameFlag rename_flag = params->rename_flag; + /* If we don't enter the directory directly, remember file to jump into editing. */ if (do_diropen == false) { + BLI_assert_msg(params->rename_id == NULL, + "File rename handling should immediately clear rename_id when done, " + "because otherwise it will keep taking precedence over renamefile."); BLI_strncpy(params->renamefile, name, FILE_MAXFILE); - params->rename_flag = FILE_PARAMS_RENAME_PENDING; + rename_flag = FILE_PARAMS_RENAME_PENDING; } - /* Set timer to smoothly view newly generated file. */ - if (sfile->smoothscroll_timer != NULL) { - WM_event_remove_timer(wm, CTX_wm_window(C), sfile->smoothscroll_timer); - } - sfile->smoothscroll_timer = WM_event_add_timer(wm, CTX_wm_window(C), TIMER1, 1.0 / 1000.0); - sfile->scroll_offset = 0; + file_params_invoke_rename_postscroll(wm, CTX_wm_window(C), sfile); + params->rename_flag = rename_flag; /* reload dir to make sure we're seeing what's in the directory */ - ED_fileselect_clear(wm, CTX_data_scene(C), sfile); + ED_fileselect_clear(wm, sfile); if (do_diropen) { BLI_strncpy(params->dir, path, sizeof(params->dir)); @@ -2384,7 +2463,8 @@ void FILE_OT_directory_new(struct wmOperatorType *ot) /* api callbacks */ ot->invoke = WM_operator_confirm_or_exec; ot->exec = file_directory_new_exec; - ot->poll = ED_operator_file_active; /* <- important, handler is on window level */ + /* File browsing only operator (not asset browsing). */ + ot->poll = ED_operator_file_browsing_active; /* <- important, handler is on window level */ prop = RNA_def_string_dir_path( ot->srna, "directory", NULL, FILE_MAX, "Directory", "Name of new directory"); @@ -2400,7 +2480,7 @@ void FILE_OT_directory_new(struct wmOperatorType *ot) /** \name Refresh File List Operator * \{ */ -/* TODO This should go to BLI_path_utils. */ +/* TODO: This should go to BLI_path_utils. */ static void file_expand_directory(bContext *C) { Main *bmain = CTX_data_main(C); @@ -2411,12 +2491,14 @@ static void file_expand_directory(bContext *C) if (BLI_path_is_rel(params->dir)) { /* Use of 'default' folder here is just to avoid an error message on '//' prefix. */ BLI_path_abs(params->dir, - G.relbase_valid ? BKE_main_blendfile_path(bmain) : BKE_appdir_folder_default()); + G.relbase_valid ? BKE_main_blendfile_path(bmain) : + BKE_appdir_folder_default_or_root()); } else if (params->dir[0] == '~') { char tmpstr[sizeof(params->dir) - 1]; BLI_strncpy(tmpstr, params->dir + 1, sizeof(tmpstr)); - BLI_join_dirfile(params->dir, sizeof(params->dir), BKE_appdir_folder_default(), tmpstr); + BLI_path_join( + params->dir, sizeof(params->dir), BKE_appdir_folder_default_or_root(), tmpstr, NULL); } else if (params->dir[0] == '\0') @@ -2441,7 +2523,7 @@ static void file_expand_directory(bContext *C) } } -/* TODO check we still need this, it's annoying to have OS-specific code here... :/ */ +/* TODO: check we still need this, it's annoying to have OS-specific code here... :/. */ #if defined(WIN32) static bool can_create_dir(const char *dir) { @@ -2507,7 +2589,7 @@ void file_directory_enter_handle(bContext *C, void *UNUSED(arg_unused), void *UN /* don't do for now because it selects entire text instead of * placing cursor at the end */ - /* UI_textbutton_activate_but(C, but); */ + // UI_textbutton_activate_but(C, but); } #if defined(WIN32) else if (!can_create_dir(params->dir)) { @@ -2611,7 +2693,7 @@ static int file_hidedot_exec(bContext *C, wmOperator *UNUSED(unused)) if (params) { params->flag ^= FILE_HIDE_DOT; - ED_fileselect_clear(wm, CTX_data_scene(C), sfile); + ED_fileselect_clear(wm, sfile); WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL); } @@ -2627,44 +2709,8 @@ void FILE_OT_hidedot(struct wmOperatorType *ot) /* api callbacks */ ot->exec = file_hidedot_exec; - ot->poll = ED_operator_file_active; /* <- important, handler is on window level */ -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Associate File Type Operator (Windows only) - * \{ */ - -static int associate_blend_exec(bContext *UNUSED(C), wmOperator *op) -{ -#ifdef WIN32 - WM_cursor_wait(true); - if (BLI_windows_register_blend_extension(true)) { - BKE_report(op->reports, RPT_INFO, "File association registered"); - WM_cursor_wait(false); - return OPERATOR_FINISHED; - } - else { - BKE_report(op->reports, RPT_ERROR, "Unable to register file association"); - WM_cursor_wait(false); - return OPERATOR_CANCELLED; - } -#else - BKE_report(op->reports, RPT_WARNING, "Operator Not supported"); - return OPERATOR_CANCELLED; -#endif -} - -void FILE_OT_associate_blend(struct wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Register File Association"; - ot->description = "Use this installation for .blend files and to display thumbnails"; - ot->idname = "FILE_OT_associate_blend"; - - /* api callbacks */ - ot->exec = associate_blend_exec; + /* File browsing only operator (not asset browsing). */ + ot->poll = ED_operator_file_browsing_active; /* <- important, handler is on window level */ } /** \} */ @@ -2677,7 +2723,8 @@ static bool file_filenum_poll(bContext *C) { SpaceFile *sfile = CTX_wm_space_file(C); - if (!ED_operator_file_active(C)) { + /* File browsing only operator (not asset browsing). */ + if (!ED_operator_file_browsing_active(C)) { return false; } @@ -2776,20 +2823,6 @@ static void file_rename_state_activate(SpaceFile *sfile, int file_idx, bool requ } } -static int file_rename_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *UNUSED(event)) -{ - ScrArea *area = CTX_wm_area(C); - SpaceFile *sfile = (SpaceFile *)CTX_wm_space_data(C); - FileSelectParams *params = ED_fileselect_get_active_params(sfile); - - if (params) { - file_rename_state_activate(sfile, params->active_file, true); - ED_area_tag_redraw(area); - } - - return OPERATOR_FINISHED; -} - static int file_rename_exec(bContext *C, wmOperator *UNUSED(op)) { ScrArea *area = CTX_wm_area(C); @@ -2797,7 +2830,7 @@ static int file_rename_exec(bContext *C, wmOperator *UNUSED(op)) FileSelectParams *params = ED_fileselect_get_active_params(sfile); if (params) { - file_rename_state_activate(sfile, params->highlight_file, false); + file_rename_state_activate(sfile, params->active_file, false); ED_area_tag_redraw(area); } @@ -2812,9 +2845,9 @@ void FILE_OT_rename(struct wmOperatorType *ot) ot->idname = "FILE_OT_rename"; /* api callbacks */ - ot->invoke = file_rename_invoke; ot->exec = file_rename_exec; - ot->poll = ED_operator_file_active; + /* File browsing only operator (not asset browsing). */ + ot->poll = ED_operator_file_browsing_active; } /** \} */ @@ -2825,53 +2858,40 @@ void FILE_OT_rename(struct wmOperatorType *ot) static bool file_delete_poll(bContext *C) { - bool poll = ED_operator_file_active(C); + if (!ED_operator_file_browsing_active(C)) { + return false; + } + SpaceFile *sfile = CTX_wm_space_file(C); FileSelectParams *params = ED_fileselect_get_active_params(sfile); + if (!sfile || !params) { + return false; + } - if (sfile && params) { - char dir[FILE_MAX_LIBEXTRA]; - int numfiles = filelist_files_ensure(sfile->files); - int i; - int num_selected = 0; + char dir[FILE_MAX_LIBEXTRA]; + if (filelist_islibrary(sfile->files, dir, NULL)) { + return false; + } - if (filelist_islibrary(sfile->files, dir, NULL)) { - poll = 0; - } - for (i = 0; i < numfiles; i++) { - if (filelist_entry_select_index_get(sfile->files, i, CHECK_ALL)) { - num_selected++; - } - } - if (num_selected <= 0) { - poll = 0; + int numfiles = filelist_files_ensure(sfile->files); + for (int i = 0; i < numfiles; i++) { + if (filelist_entry_select_index_get(sfile->files, i, CHECK_ALL)) { + /* Has a selected file -> the operator can run. */ + return true; } } - else { - poll = 0; - } - return poll; + return false; } static bool file_delete_single(const FileSelectParams *params, FileDirEntry *file, const char **r_error_message) { - if (file->typeflag & FILE_TYPE_ASSET) { - ID *id = filelist_file_get_id(file); - if (!id) { - *r_error_message = "File is not a local data-block asset."; - return false; - } - ED_asset_clear_id(id); - } - else { - char str[FILE_MAX]; - BLI_join_dirfile(str, sizeof(str), params->dir, file->relpath); - if (BLI_delete_soft(str, r_error_message) != 0 || BLI_exists(str)) { - return false; - } + char str[FILE_MAX]; + BLI_join_dirfile(str, sizeof(str), params->dir, file->relpath); + if (BLI_delete_soft(str, r_error_message) != 0 || BLI_exists(str)) { + return false; } return true; @@ -2908,7 +2928,7 @@ static int file_delete_exec(bContext *C, wmOperator *op) } } - ED_fileselect_clear(wm, CTX_data_scene(C), sfile); + ED_fileselect_clear(wm, sfile); WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL); return OPERATOR_FINISHED; @@ -2964,6 +2984,7 @@ void FILE_OT_start_filter(struct wmOperatorType *ot) /* api callbacks */ ot->exec = file_start_filter_exec; + /* Operator works for file or asset browsing */ ot->poll = ED_operator_file_active; } diff --git a/source/blender/editors/space_file/file_panels.c b/source/blender/editors/space_file/file_panels.c index 7032d55b331..0e468718a04 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,42 @@ 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) +{ + bScreen *screen = CTX_wm_screen(C); + 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); + + uiLayout *col = uiLayoutColumn(panel->layout, false); + uiLayout *row = uiLayoutRow(col, true); + + PointerRNA params_ptr; + RNA_pointer_create(&screen->id, &RNA_FileAssetSelectParams, params, ¶ms_ptr); + + uiItemR(row, ¶ms_ptr, "asset_library_ref", 0, "", ICON_NONE); + if (params->asset_library_ref.type != ASSET_LIBRARY_LOCAL) { + uiItemO(row, "", ICON_FILE_REFRESH, "FILE_OT_asset_library_refresh"); + } + + uiItemS(col); + + file_create_asset_catalog_tree_view_in_layout(asset_library, col, sfile, 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 40a7be0423e..a73fa2b9740 100644 --- a/source/blender/editors/space_file/filelist.c +++ b/source/blender/editors/space_file/filelist.c @@ -37,6 +37,8 @@ #endif #include "MEM_guardedalloc.h" +#include "BLF_api.h" + #include "BLI_blenlib.h" #include "BLI_fileops.h" #include "BLI_fileops_types.h" @@ -48,12 +50,14 @@ #include "BLI_task.h" #include "BLI_threads.h" #include "BLI_utildefines.h" +#include "BLI_uuid.h" #ifdef WIN32 # include "BLI_winstuff.h" #endif #include "BKE_asset.h" +#include "BKE_asset_library.h" #include "BKE_context.h" #include "BKE_global.h" #include "BKE_icons.h" @@ -85,6 +89,7 @@ #include "atomic_ops.h" +#include "file_intern.h" #include "filelist.h" #define FILEDIR_NBR_ENTRIES_UNSET -1 @@ -263,8 +268,7 @@ ListBase folder_history_list_duplicate(ListBase *listbase) typedef struct FileListInternEntry { struct FileListInternEntry *next, *prev; - /** ASSET_UUID_LENGTH */ - char uuid[16]; + FileUID uid; /** eFileSel_File_Types */ int typeflag; @@ -306,7 +310,7 @@ typedef struct FileListIntern { ListBase entries; FileListInternEntry **filtered; - char curr_uuid[16]; /* Used to generate uuid during internal listing. */ + FileUID curr_uid; /* Used to generate UID during internal listing. */ } FileListIntern; #define FILELIST_ENTRYCACHESIZE_DEFAULT 1024 /* Keep it a power of two! */ @@ -315,7 +319,7 @@ typedef struct FileListEntryCache { int flags; - /* This one gathers all entries from both block and misc caches. Used for easy bulk-freing. */ + /* This one gathers all entries from both block and misc caches. Used for easy bulk-freeing. */ ListBase cached_entries; /* Block cache: all entries between start and end index. @@ -324,17 +328,18 @@ typedef struct FileListEntryCache { int block_start_index, block_end_index, block_center_index, block_cursor; /* Misc cache: random indices, FIFO behavior. - * Note: Not 100% sure we actually need that, time will say. */ + * NOTE: Not 100% sure we actually need that, time will say. */ int misc_cursor; int *misc_entries_indices; GHash *misc_entries; - /* Allows to quickly get a cached entry from its UUID. */ - GHash *uuids; + /* Allows to quickly get a cached entry from its UID. */ + GHash *uids; /* Previews handling. */ TaskPool *previews_pool; ThreadQueue *previews_done; + size_t previews_todo_count; } FileListEntryCache; /* FileListCache.flags */ @@ -366,6 +371,8 @@ typedef struct FileListFilter { char filter_glob[FILE_MAXFILE]; char filter_search[66]; /* + 2 for heading/trailing implicit '*' wildcards. */ short flags; + + FileAssetCatalogFilterSettingsHandle *asset_catalog_filter; } FileListFilter; /* FileListFilter.flags */ @@ -377,12 +384,14 @@ enum { FLF_ASSETS_ONLY = 1 << 4, }; +struct FileListReadJob; typedef struct FileList { FileDirEntryArr filelist; eFileSelectType type; /* The library this list was created for. Stored here so we know when to re-read. */ - FileSelectAssetLibraryUID *asset_library; + AssetLibraryReference *asset_library_ref; + struct AssetLibrary *asset_library; /* Non-owning pointer. */ short flags; @@ -413,11 +422,12 @@ typedef struct FileList { bool (*check_dir_fn)(struct FileList *, char *, const bool); /* Fill filelist (to be called by read job). */ - void (*read_job_fn)( - Main *, struct FileList *, const char *, short *, short *, float *, ThreadMutex *); + void (*read_job_fn)(struct FileListReadJob *, short *, short *, float *); /* Filter an entry of current filelist. */ bool (*filter_fn)(struct FileListInternEntry *, const char *, FileListFilter *); + /* Executed before filtering individual items, to set up additional filter data. */ + void (*prepare_filter_fn)(const struct FileList *, FileListFilter *); short tags; /* FileListTags */ } FileList; @@ -425,11 +435,14 @@ typedef struct FileList { /* FileList.flags */ enum { FL_FORCE_RESET = 1 << 0, - FL_IS_READY = 1 << 1, - FL_IS_PENDING = 1 << 2, - FL_NEED_SORTING = 1 << 3, - FL_NEED_FILTERING = 1 << 4, - FL_SORT_INVERT = 1 << 5, + /* Don't do a full reset (unless #FL_FORCE_RESET is also set), only reset files representing main + * data (assets from the current file/#Main). */ + FL_FORCE_RESET_MAIN_FILES = 1 << 1, + FL_IS_READY = 1 << 2, + FL_IS_PENDING = 1 << 3, + FL_NEED_SORTING = 1 << 4, + FL_NEED_FILTERING = 1 << 5, + FL_SORT_INVERT = 1 << 6, }; /* FileList.tags */ @@ -457,40 +470,31 @@ enum { static ImBuf *gSpecialFileImages[SPECIAL_IMG_MAX]; -static void filelist_readjob_main(Main *current_main, - FileList *filelist, - const char *main_name, +static void filelist_readjob_main(struct FileListReadJob *job_params, short *stop, short *do_update, - float *progress, - ThreadMutex *lock); -static void filelist_readjob_lib(Main *current_main, - FileList *filelist, - const char *main_name, + float *progress); +static void filelist_readjob_lib(struct FileListReadJob *job_params, short *stop, short *do_update, - float *progress, - ThreadMutex *lock); -static void filelist_readjob_dir(Main *current_main, - FileList *filelist, - const char *main_name, + float *progress); +static void filelist_readjob_dir(struct FileListReadJob *job_params, short *stop, short *do_update, - float *progress, - ThreadMutex *lock); -static void filelist_readjob_main_assets(Main *current_main, - FileList *filelist, - const char *main_name, + float *progress); +static void filelist_readjob_asset_library(struct FileListReadJob *job_params, + short *stop, + short *do_update, + float *progress); +static void filelist_readjob_main_assets(struct FileListReadJob *job_params, short *stop, short *do_update, - float *progress, - ThreadMutex *lock); + float *progress); /* helper, could probably go in BKE actually? */ static int groupname_to_code(const char *group); static uint64_t groupname_to_filter_id(const char *group); -static void filelist_filter_clear(FileList *filelist); static void filelist_cache_clear(FileListEntryCache *cache, size_t new_size); /* ********** Sort helpers ********** */ @@ -720,7 +724,7 @@ void filelist_sort(struct FileList *filelist) sort_cb, &(struct FileSortData){.inverted = (filelist->flags & FL_SORT_INVERT) != 0}); - filelist_filter_clear(filelist); + filelist_tag_needs_filtering(filelist); filelist->flags &= ~FL_NEED_SORTING; } } @@ -813,105 +817,201 @@ static bool is_filtered_hidden(const char *filename, return false; } -static bool is_filtered_file(FileListInternEntry *file, - const char *UNUSED(root), - FileListFilter *filter) +/** + * Apply the filter string as file path matching pattern. + * \return true when the file should be in the result set, false if it should be filtered out. */ +static bool is_filtered_file_relpath(const FileListInternEntry *file, const FileListFilter *filter) { - bool is_filtered = !is_filtered_hidden(file->relpath, filter, file); + if (filter->filter_search[0] == '\0') { + return true; + } - if (is_filtered && !FILENAME_IS_CURRPAR(file->relpath)) { - /* We only check for types if some type are enabled in filtering. */ - if (filter->filter && (filter->flags & FLF_DO_FILTER)) { - if (file->typeflag & FILE_TYPE_DIR) { - if (file->typeflag & - (FILE_TYPE_BLENDERLIB | FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) { - if (!(filter->filter & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP))) { - is_filtered = false; - } - } - else { - if (!(filter->filter & FILE_TYPE_FOLDER)) { - is_filtered = false; - } + /* If there's a filter string, apply it as filter even if FLF_DO_FILTER is not set. */ + return fnmatch(filter->filter_search, file->relpath, FNM_CASEFOLD) == 0; +} + +/** \return true when the file should be in the result set, false if it should be filtered out. */ +static bool is_filtered_file_type(const FileListInternEntry *file, const FileListFilter *filter) +{ + if (is_filtered_hidden(file->relpath, filter, file)) { + return false; + } + + if (FILENAME_IS_CURRPAR(file->relpath)) { + return false; + } + + /* We only check for types if some type are enabled in filtering. */ + if (filter->filter && (filter->flags & FLF_DO_FILTER)) { + if (file->typeflag & FILE_TYPE_DIR) { + if (file->typeflag & (FILE_TYPE_BLENDERLIB | FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) { + if (!(filter->filter & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP))) { + return false; } } else { - if (!(file->typeflag & filter->filter)) { - is_filtered = false; + if (!(filter->filter & FILE_TYPE_FOLDER)) { + return false; } } } - /* If there's a filter string, apply it as filter even if FLF_DO_FILTER is not set. */ - if (is_filtered && (filter->filter_search[0] != '\0')) { - if (fnmatch(filter->filter_search, file->relpath, FNM_CASEFOLD) != 0) { - is_filtered = false; + else { + if (!(file->typeflag & filter->filter)) { + return false; } } } + return true; +} - return is_filtered; +/** \return true when the file should be in the result set, false if it should be filtered out. */ +static bool is_filtered_file(FileListInternEntry *file, + const char *UNUSED(root), + FileListFilter *filter) +{ + return is_filtered_file_type(file, filter) && is_filtered_file_relpath(file, filter); } -static bool is_filtered_id_file(const FileListInternEntry *file, - const char *id_group, - const char *name, - const FileListFilter *filter) +static bool is_filtered_id_file_type(const FileListInternEntry *file, + const char *id_group, + const char *name, + const FileListFilter *filter) { - bool is_filtered = !is_filtered_hidden(file->relpath, filter, file); - if (is_filtered && !FILENAME_IS_CURRPAR(file->relpath)) { - /* We only check for types if some type are enabled in filtering. */ - if ((filter->filter || filter->filter_id) && (filter->flags & FLF_DO_FILTER)) { - if (file->typeflag & FILE_TYPE_DIR) { - if (file->typeflag & - (FILE_TYPE_BLENDERLIB | FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) { - if (!(filter->filter & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP))) { - is_filtered = false; - } - } - else { - if (!(filter->filter & FILE_TYPE_FOLDER)) { - is_filtered = false; - } - } + if (!is_filtered_file_type(file, filter)) { + return false; + } + + /* We only check for types if some type are enabled in filtering. */ + if ((filter->filter || filter->filter_id) && (filter->flags & FLF_DO_FILTER)) { + if (id_group) { + if (!name && (filter->flags & FLF_HIDE_LIB_DIR)) { + return false; } - if (is_filtered && id_group) { - if (!name && (filter->flags & FLF_HIDE_LIB_DIR)) { - is_filtered = false; - } - else { - uint64_t filter_id = groupname_to_filter_id(id_group); - if (!(filter_id & filter->filter_id)) { - is_filtered = false; - } - } + + uint64_t filter_id = groupname_to_filter_id(id_group); + if (!(filter_id & filter->filter_id)) { + return false; } } - /* If there's a filter string, apply it as filter even if FLF_DO_FILTER is not set. */ - if (is_filtered && (filter->filter_search[0] != '\0')) { - if (fnmatch(filter->filter_search, file->relpath, FNM_CASEFOLD) != 0) { - is_filtered = false; - } + } + + return true; +} + +/** + * Get the asset metadata of a file, if it represents an asset. This may either be of a local ID + * (ID in the current #Main) or read from an external asset library. + */ +static AssetMetaData *filelist_file_internal_get_asset_data(const FileListInternEntry *file) +{ + const ID *local_id = file->local_data.id; + return local_id ? local_id->asset_data : file->imported_asset_data; +} + +static void prepare_filter_asset_library(const FileList *filelist, FileListFilter *filter) +{ + /* Not used yet for the asset view template. */ + if (!filter->asset_catalog_filter) { + return; + } + + file_ensure_updated_catalog_filter_data(filter->asset_catalog_filter, filelist->asset_library); +} + +/** + * Copy a string from source to `dest`, but prefix and suffix it with a single space. + * Assumes `dest` has at least space enough for the two spaces. + */ +static void tag_copy_with_spaces(char *dest, const char *source, const size_t dest_size) +{ + BLI_assert(dest_size > 2); + const size_t source_length = BLI_strncpy_rlen(dest + 1, source, dest_size - 2); + dest[0] = ' '; + dest[source_length + 1] = ' '; + dest[source_length + 2] = '\0'; +} + +/** + * Return whether at least one tag matches the search filter. + * Tags are searched as "entire words", so instead of searching for "tag" in the + * filter string, this function searches for " tag ". Assumes the search filter + * starts and ends with a space. + * + * Here the tags on the asset are written in set notation: + * + * `asset_tag_matches_filter(" some tags ", {"some", "blue"})` -> true + * `asset_tag_matches_filter(" some tags ", {"som", "tag"})` -> false + * `asset_tag_matches_filter(" some tags ", {})` -> false + */ +static bool asset_tag_matches_filter(const char *filter_search, const AssetMetaData *asset_data) +{ + LISTBASE_FOREACH (const AssetTag *, asset_tag, &asset_data->tags) { + char tag_name[MAX_NAME + 2]; /* sizeof(AssetTag::name) + 2 */ + tag_copy_with_spaces(tag_name, asset_tag->name, sizeof(tag_name)); + if (BLI_strcasestr(filter_search, tag_name) != NULL) { + return true; } } + return false; +} + +static bool is_filtered_asset(FileListInternEntry *file, FileListFilter *filter) +{ + const AssetMetaData *asset_data = filelist_file_internal_get_asset_data(file); + + /* Not used yet for the asset view template. */ + if (filter->asset_catalog_filter && !file_is_asset_visible_in_catalog_filter_settings( + filter->asset_catalog_filter, asset_data)) { + return false; + } + + if (filter->filter_search[0] == '\0') { + /* If there is no filter text, everything matches. */ + return true; + } - return is_filtered; + /* filter->filter_search contains "*the search text*". */ + char filter_search[66]; /* sizeof(FileListFilter::filter_search) */ + const size_t string_length = STRNCPY_RLEN(filter_search, filter->filter_search); + + /* When doing a name comparison, get rid of the leading/trailing asterisks. */ + filter_search[string_length - 1] = '\0'; + if (BLI_strcasestr(file->name, filter_search + 1) != NULL) { + return true; + } + + /* Replace the asterisks with spaces, so that we can do matching on " sometag "; that way + * an artist searching for "redder" doesn't result in a match for the tag "red". */ + filter_search[string_length - 1] = ' '; + filter_search[0] = ' '; + + return asset_tag_matches_filter(filter_search, asset_data); } -static bool is_filtered_lib(FileListInternEntry *file, const char *root, FileListFilter *filter) +static bool is_filtered_lib_type(FileListInternEntry *file, + const char *root, + FileListFilter *filter) { - bool is_filtered; char path[FILE_MAX_LIBEXTRA], dir[FILE_MAX_LIBEXTRA], *group, *name; BLI_join_dirfile(path, sizeof(path), root, file->relpath); if (BLO_library_path_explode(path, dir, &group, &name)) { - is_filtered = is_filtered_id_file(file, group, name, filter); - } - else { - is_filtered = is_filtered_file(file, root, filter); + return is_filtered_id_file_type(file, group, name, filter); } + return is_filtered_file_type(file, filter); +} - return is_filtered; +static bool is_filtered_lib(FileListInternEntry *file, const char *root, FileListFilter *filter) +{ + return is_filtered_lib_type(file, root, filter) && is_filtered_file_relpath(file, filter); +} + +static bool is_filtered_asset_library(FileListInternEntry *file, + const char *root, + FileListFilter *filter) +{ + return is_filtered_lib_type(file, root, filter) && is_filtered_asset(file, filter); } static bool is_filtered_main(FileListInternEntry *file, @@ -926,10 +1026,11 @@ static bool is_filtered_main_assets(FileListInternEntry *file, FileListFilter *filter) { /* "Filtered" means *not* being filtered out... So return true if the file should be visible. */ - return is_filtered_id_file(file, file->relpath, file->name, filter); + return is_filtered_id_file_type(file, file->relpath, file->name, filter) && + is_filtered_asset(file, filter); } -static void filelist_filter_clear(FileList *filelist) +void filelist_tag_needs_filtering(FileList *filelist) { filelist->flags |= FL_NEED_FILTERING; } @@ -959,6 +1060,10 @@ void filelist_filter(FileList *filelist) } } + if (filelist->prepare_filter_fn) { + filelist->prepare_filter_fn(filelist, &filelist->filter_data); + } + filtered_tmp = MEM_mallocN(sizeof(*filtered_tmp) * (size_t)num_files, __func__); /* Filter remap & count how many files are left after filter in a single loop. */ @@ -1037,7 +1142,29 @@ void filelist_setfilter_options(FileList *filelist, if (update) { /* And now, free filtered data so that we know we have to filter again. */ - filelist_filter_clear(filelist); + filelist_tag_needs_filtering(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) +{ + if (!filelist->filter_data.asset_catalog_filter) { + /* There's no filter data yet. */ + filelist->filter_data.asset_catalog_filter = file_create_asset_catalog_filter_settings(); + } + + const bool needs_update = file_set_asset_catalog_filter_settings( + filelist->filter_data.asset_catalog_filter, catalog_visibility, *catalog_id); + + if (needs_update) { + filelist_tag_needs_filtering(filelist); } } @@ -1045,13 +1172,13 @@ void filelist_setfilter_options(FileList *filelist, * Checks two libraries for equality. * \return True if the libraries match. */ -static bool filelist_compare_asset_libraries(const FileSelectAssetLibraryUID *library_a, - const FileSelectAssetLibraryUID *library_b) +static bool filelist_compare_asset_libraries(const AssetLibraryReference *library_a, + const AssetLibraryReference *library_b) { if (library_a->type != library_b->type) { return false; } - if (library_a->type == FILE_ASSET_LIBRARY_CUSTOM) { + if (library_a->type == ASSET_LIBRARY_CUSTOM) { /* Don't only check the index, also check that it's valid. */ bUserAssetLibrary *library_ptr_a = BKE_preferences_asset_library_find_from_index( &U, library_a->custom_library_index); @@ -1063,28 +1190,28 @@ static bool filelist_compare_asset_libraries(const FileSelectAssetLibraryUID *li } /** - * \param asset_library: May be NULL to unset the library. + * \param asset_library_ref: May be NULL to unset the library. */ -void filelist_setlibrary(FileList *filelist, const FileSelectAssetLibraryUID *asset_library) +void filelist_setlibrary(FileList *filelist, const AssetLibraryReference *asset_library_ref) { /* Unset if needed. */ - if (!asset_library) { - if (filelist->asset_library) { - MEM_SAFE_FREE(filelist->asset_library); + if (!asset_library_ref) { + if (filelist->asset_library_ref) { + MEM_SAFE_FREE(filelist->asset_library_ref); filelist->flags |= FL_FORCE_RESET; } return; } - if (!filelist->asset_library) { - filelist->asset_library = MEM_mallocN(sizeof(*filelist->asset_library), - "filelist asset library"); - *filelist->asset_library = *asset_library; + if (!filelist->asset_library_ref) { + filelist->asset_library_ref = MEM_mallocN(sizeof(*filelist->asset_library_ref), + "filelist asset library"); + *filelist->asset_library_ref = *asset_library_ref; filelist->flags |= FL_FORCE_RESET; } - else if (!filelist_compare_asset_libraries(filelist->asset_library, asset_library)) { - *filelist->asset_library = *asset_library; + else if (!filelist_compare_asset_libraries(filelist->asset_library_ref, asset_library_ref)) { + *filelist->asset_library_ref = *asset_library_ref; filelist->flags |= FL_FORCE_RESET; } } @@ -1154,7 +1281,7 @@ ImBuf *filelist_file_getimage(const FileDirEntry *file) return file->preview_icon_id ? BKE_icon_imbuf_get_buffer(file->preview_icon_id) : NULL; } -static ImBuf *filelist_geticon_image_ex(FileDirEntry *file) +ImBuf *filelist_geticon_image_ex(const FileDirEntry *file) { ImBuf *ibuf = NULL; @@ -1308,6 +1435,11 @@ int ED_file_icon(const FileDirEntry *file) filelist_geticon_ex(file, NULL, false, false); } +static bool filelist_intern_entry_is_main_file(const FileListInternEntry *intern_entry) +{ + return intern_entry->local_data.id != NULL; +} + /* ********** Main ********** */ static void parent_dir_until_exists_or_default_root(char *dir) @@ -1370,9 +1502,6 @@ static void filelist_entry_clear(FileDirEntry *entry) if (entry->name && ((entry->flags & FILE_ENTRY_NAME_FREE) != 0)) { MEM_freeN(entry->name); } - if (entry->description) { - MEM_freeN(entry->description); - } if (entry->relpath) { MEM_freeN(entry->relpath); } @@ -1383,40 +1512,6 @@ static void filelist_entry_clear(FileDirEntry *entry) BKE_icon_delete(entry->preview_icon_id); entry->preview_icon_id = 0; } - /* For now, consider FileDirEntryRevision::poin as not owned here, - * so no need to do anything about it */ - - if (!BLI_listbase_is_empty(&entry->variants)) { - FileDirEntryVariant *var; - - for (var = entry->variants.first; var; var = var->next) { - if (var->name) { - MEM_freeN(var->name); - } - if (var->description) { - MEM_freeN(var->description); - } - - if (!BLI_listbase_is_empty(&var->revisions)) { - FileDirEntryRevision *rev; - - for (rev = var->revisions.first; rev; rev = rev->next) { - if (rev->comment) { - MEM_freeN(rev->comment); - } - } - - BLI_freelistN(&var->revisions); - } - } - - /* TODO: tags! */ - - BLI_freelistN(&entry->variants); - } - else if (entry->entry) { - MEM_freeN(entry->entry); - } } static void filelist_entry_free(FileDirEntry *entry) @@ -1440,8 +1535,6 @@ static void filelist_direntryarr_free(FileDirEntryArr *array) #endif array->nbr_entries = FILEDIR_NBR_ENTRIES_UNSET; array->nbr_entries_filtered = FILEDIR_NBR_ENTRIES_UNSET; - array->entry_idx_start = -1; - array->entry_idx_end = -1; } static void filelist_intern_entry_free(FileListInternEntry *entry) @@ -1475,6 +1568,26 @@ static void filelist_intern_free(FileListIntern *filelist_intern) MEM_SAFE_FREE(filelist_intern->filtered); } +/** + * \return the number of main files removed. + */ +static int filelist_intern_free_main_files(FileListIntern *filelist_intern) +{ + int removed_counter = 0; + LISTBASE_FOREACH_MUTABLE (FileListInternEntry *, entry, &filelist_intern->entries) { + if (!filelist_intern_entry_is_main_file(entry)) { + continue; + } + + BLI_remlink(&filelist_intern->entries, entry); + filelist_intern_entry_free(entry); + removed_counter++; + } + + MEM_SAFE_FREE(filelist_intern->filtered); + return removed_counter; +} + static void filelist_cache_preview_runf(TaskPool *__restrict pool, void *taskdata) { FileListEntryCache *cache = BLI_task_pool_user_data(pool); @@ -1489,7 +1602,9 @@ static void filelist_cache_preview_runf(TaskPool *__restrict pool, void *taskdat if (preview->in_memory_preview) { if (BKE_previewimg_is_finished(preview->in_memory_preview, ICON_SIZE_PREVIEW)) { ImBuf *imbuf = BKE_previewimg_to_imbuf(preview->in_memory_preview, ICON_SIZE_PREVIEW); - preview->icon_id = BKE_icon_imbuf_create(imbuf); + if (imbuf) { + preview->icon_id = BKE_icon_imbuf_create(imbuf); + } done = true; } } @@ -1529,6 +1644,7 @@ static void filelist_cache_preview_runf(TaskPool *__restrict pool, void *taskdat /* That way task freeing function won't free th preview, since it does not own it anymore. */ atomic_cas_ptr((void **)&preview_taskdata->preview, preview, NULL); BLI_thread_queue_push(cache->previews_done, preview); + atomic_fetch_and_sub_z(&cache->previews_todo_count, 1); } // printf("%s: End (%d)...\n", __func__, threadid); @@ -1555,6 +1671,7 @@ static void filelist_cache_preview_ensure_running(FileListEntryCache *cache) if (!cache->previews_pool) { cache->previews_pool = BLI_task_pool_create_background(cache, TASK_PRIORITY_LOW); cache->previews_done = BLI_thread_queue_init(); + cache->previews_todo_count = 0; IMB_thumb_locks_acquire(); } @@ -1588,6 +1705,7 @@ static void filelist_cache_previews_free(FileListEntryCache *cache) BLI_task_pool_free(cache->previews_pool); cache->previews_pool = NULL; cache->previews_done = NULL; + cache->previews_todo_count = 0; IMB_thumb_locks_release(); } @@ -1634,7 +1752,7 @@ static void filelist_cache_previews_push(FileList *filelist, FileDirEntry *entry preview->flags = entry->typeflag; preview->in_memory_preview = intern_entry->local_data.preview_image; preview->icon_id = 0; - // printf("%s: %d - %s - %p\n", __func__, preview->index, preview->path, preview->img); + // printf("%s: %d - %s\n", __func__, preview->index, preview->path); filelist_cache_preview_ensure_running(cache); @@ -1662,13 +1780,14 @@ static void filelist_cache_init(FileListEntryCache *cache, size_t cache_size) copy_vn_i(cache->misc_entries_indices, cache_size, -1); cache->misc_cursor = 0; - /* XXX This assumes uint is 32 bits and uuid is 128 bits (char[16]), be careful! */ - cache->uuids = BLI_ghash_new_ex( - BLI_ghashutil_uinthash_v4_p, BLI_ghashutil_uinthash_v4_cmp, __func__, cache_size * 2); + cache->uids = BLI_ghash_new_ex( + BLI_ghashutil_inthash_p, BLI_ghashutil_intcmp, __func__, cache_size * 2); cache->size = cache_size; cache->flags = FLC_IS_INIT; + cache->previews_todo_count = 0; + /* We cannot translate from non-main thread, so init translated strings once from here. */ IMB_thumb_ensure_translations(); } @@ -1688,7 +1807,7 @@ static void filelist_cache_free(FileListEntryCache *cache) BLI_ghash_free(cache->misc_entries, NULL, NULL); MEM_freeN(cache->misc_entries_indices); - BLI_ghash_free(cache->uuids, NULL, NULL); + BLI_ghash_free(cache->uids, NULL, NULL); for (entry = cache->cached_entries.first; entry; entry = entry_next) { entry_next = entry->next; @@ -1721,7 +1840,7 @@ static void filelist_cache_clear(FileListEntryCache *cache, size_t new_size) } copy_vn_i(cache->misc_entries_indices, new_size, -1); - BLI_ghash_clear_ex(cache->uuids, NULL, NULL, new_size * 2); + BLI_ghash_clear_ex(cache->uids, NULL, NULL, new_size * 2); cache->size = new_size; @@ -1738,8 +1857,7 @@ FileList *filelist_new(short type) filelist_cache_init(&p->filelist_cache, FILELIST_ENTRYCACHESIZE_DEFAULT); - p->selection_state = BLI_ghash_new( - BLI_ghashutil_uinthash_v4_p, BLI_ghashutil_uinthash_v4_cmp, __func__); + p->selection_state = BLI_ghash_new(BLI_ghashutil_inthash_p, BLI_ghashutil_intcmp, __func__); p->filelist.nbr_entries = FILEDIR_NBR_ENTRIES_UNSET; filelist_settype(p, type); @@ -1765,9 +1883,17 @@ void filelist_settype(FileList *filelist, short type) filelist->read_job_fn = filelist_readjob_lib; filelist->filter_fn = is_filtered_lib; break; + case FILE_ASSET_LIBRARY: + filelist->check_dir_fn = filelist_checkdir_lib; + filelist->read_job_fn = filelist_readjob_asset_library; + filelist->prepare_filter_fn = prepare_filter_asset_library; + filelist->filter_fn = is_filtered_asset_library; + filelist->tags |= FILELIST_TAGS_USES_MAIN_DATA; + break; case FILE_MAIN_ASSET: filelist->check_dir_fn = filelist_checkdir_main_assets; filelist->read_job_fn = filelist_readjob_main_assets; + filelist->prepare_filter_fn = prepare_filter_asset_library; filelist->filter_fn = is_filtered_main_assets; filelist->tags |= FILELIST_TAGS_USES_MAIN_DATA | FILELIST_TAGS_NO_THREADS; break; @@ -1781,13 +1907,23 @@ void filelist_settype(FileList *filelist, short type) filelist->flags |= FL_FORCE_RESET; } -void filelist_clear_ex(struct FileList *filelist, const bool do_cache, const bool do_selection) +static void filelist_clear_asset_library(FileList *filelist) +{ + /* The AssetLibraryService owns the AssetLibrary pointer, so no need for us to free it. */ + filelist->asset_library = NULL; + file_delete_asset_catalog_filter_settings(&filelist->filter_data.asset_catalog_filter); +} + +void filelist_clear_ex(struct FileList *filelist, + const bool do_asset_library, + const bool do_cache, + const bool do_selection) { if (!filelist) { return; } - filelist_filter_clear(filelist); + filelist_tag_needs_filtering(filelist); if (do_cache) { filelist_cache_clear(&filelist->filelist_cache, filelist->filelist_cache.size); @@ -1798,13 +1934,65 @@ void filelist_clear_ex(struct FileList *filelist, const bool do_cache, const boo filelist_direntryarr_free(&filelist->filelist); if (do_selection && filelist->selection_state) { - BLI_ghash_clear(filelist->selection_state, MEM_freeN, NULL); + BLI_ghash_clear(filelist->selection_state, NULL, NULL); + } + + if (do_asset_library) { + filelist_clear_asset_library(filelist); } } -void filelist_clear(struct FileList *filelist) +static void filelist_clear_main_files(FileList *filelist, + const bool do_asset_library, + const bool do_cache, + const bool do_selection) { - filelist_clear_ex(filelist, true, true); + if (!filelist || !(filelist->tags & FILELIST_TAGS_USES_MAIN_DATA)) { + return; + } + + filelist_tag_needs_filtering(filelist); + + if (do_cache) { + filelist_cache_clear(&filelist->filelist_cache, filelist->filelist_cache.size); + } + + const int removed_files = filelist_intern_free_main_files(&filelist->filelist_intern); + + filelist->filelist.nbr_entries -= removed_files; + filelist->filelist.nbr_entries_filtered = FILEDIR_NBR_ENTRIES_UNSET; + BLI_assert(filelist->filelist.nbr_entries > FILEDIR_NBR_ENTRIES_UNSET); + + if (do_selection && filelist->selection_state) { + BLI_ghash_clear(filelist->selection_state, NULL, NULL); + } + + if (do_asset_library) { + filelist_clear_asset_library(filelist); + } +} + +void filelist_clear(FileList *filelist) +{ + filelist_clear_ex(filelist, true, true, true); +} + +/** + * A "smarter" version of #filelist_clear() that calls partial clearing based on the filelist + * force-reset flags. + */ +void filelist_clear_from_reset_tag(FileList *filelist) +{ + /* Do a full clear if needed. */ + if (filelist->flags & FL_FORCE_RESET) { + filelist_clear(filelist); + return; + } + + if (filelist->flags & FL_FORCE_RESET_MAIN_FILES) { + filelist_clear_main_files(filelist, false, true, false); + return; + } } void filelist_free(struct FileList *filelist) @@ -1815,21 +2003,26 @@ void filelist_free(struct FileList *filelist) } /* No need to clear cache & selection_state, we free them anyway. */ - filelist_clear_ex(filelist, false, false); + filelist_clear_ex(filelist, true, false, false); filelist_cache_free(&filelist->filelist_cache); if (filelist->selection_state) { - BLI_ghash_free(filelist->selection_state, MEM_freeN, NULL); + BLI_ghash_free(filelist->selection_state, NULL, NULL); filelist->selection_state = NULL; } - MEM_SAFE_FREE(filelist->asset_library); + MEM_SAFE_FREE(filelist->asset_library_ref); memset(&filelist->filter_data, 0, sizeof(filelist->filter_data)); 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) { @@ -1843,13 +2036,23 @@ BlendHandle *filelist_lib(struct FileList *filelist) return filelist->libfiledata; } -static const char *fileentry_uiname(const char *root, - const char *relpath, - const eFileSel_File_Types typeflag, - char *buff) +static char *fileentry_uiname(const char *root, + const char *relpath, + const eFileSel_File_Types typeflag, + char *buff) { char *name = NULL; + if (typeflag & FILE_TYPE_FTFONT && !(typeflag & FILE_TYPE_BLENDERLIB)) { + char abspath[FILE_MAX_LIBEXTRA]; + BLI_join_dirfile(abspath, sizeof(abspath), root, relpath); + name = BLF_display_name_from_file(abspath); + if (name) { + /* Allocated string, so no need to BLI_strdup.*/ + return name; + } + } + if (typeflag & FILE_TYPE_BLENDERLIB) { char abspath[FILE_MAX_LIBEXTRA]; char *group; @@ -1871,7 +2074,7 @@ static const char *fileentry_uiname(const char *root, } BLI_assert(name); - return name; + return BLI_strdup(name); } const char *filelist_dir(struct FileList *filelist) @@ -1889,7 +2092,7 @@ bool filelist_is_dir(struct FileList *filelist, const char *path) */ void filelist_setdir(struct FileList *filelist, char *r_dir) { - const bool allow_invalid = filelist->asset_library != NULL; + const bool allow_invalid = filelist->asset_library_ref != NULL; BLI_assert(strlen(r_dir) < FILE_MAX_LIBEXTRA); BLI_path_normalize_dir(BKE_main_blendfile_path_from_global(), r_dir); @@ -1913,7 +2116,7 @@ void filelist_setrecursion(struct FileList *filelist, const int recursion_level) bool filelist_needs_force_reset(FileList *filelist) { - return (filelist->flags & FL_FORCE_RESET) != 0; + return (filelist->flags & (FL_FORCE_RESET | FL_FORCE_RESET_MAIN_FILES)) != 0; } void filelist_tag_force_reset(FileList *filelist) @@ -1921,6 +2124,14 @@ void filelist_tag_force_reset(FileList *filelist) filelist->flags |= FL_FORCE_RESET; } +void filelist_tag_force_reset_mainfiles(FileList *filelist) +{ + if (!(filelist->tags & FILELIST_TAGS_USES_MAIN_DATA)) { + return; + } + filelist->flags |= FL_FORCE_RESET_MAIN_FILES; +} + bool filelist_is_ready(struct FileList *filelist) { return (filelist->flags & FL_IS_READY) != 0; @@ -1957,16 +2168,12 @@ static FileDirEntry *filelist_file_create_entry(FileList *filelist, const int in FileListInternEntry *entry = filelist->filelist_intern.filtered[index]; FileListEntryCache *cache = &filelist->filelist_cache; FileDirEntry *ret; - FileDirEntryRevision *rev; ret = MEM_callocN(sizeof(*ret), __func__); - rev = MEM_callocN(sizeof(*rev), __func__); - rev->size = (uint64_t)entry->st.st_size; + ret->size = (uint64_t)entry->st.st_size; + ret->time = (int64_t)entry->st.st_mtime; - rev->time = (int64_t)entry->st.st_mtime; - - ret->entry = rev; ret->relpath = BLI_strdup(entry->relpath); if (entry->free_name) { ret->name = BLI_strdup(entry->name); @@ -1975,8 +2182,7 @@ static FileDirEntry *filelist_file_create_entry(FileList *filelist, const int in else { ret->name = entry->name; } - ret->description = BLI_strdupcat(filelist->filelist.root, entry->relpath); - memcpy(ret->uuid, entry->uuid, sizeof(ret->uuid)); + ret->uid = entry->uid; ret->blentype = entry->blentype; ret->typeflag = entry->typeflag; ret->attributes = entry->attributes; @@ -1992,7 +2198,9 @@ static FileDirEntry *filelist_file_create_entry(FileList *filelist, const int in if (entry->local_data.preview_image && BKE_previewimg_is_finished(entry->local_data.preview_image, ICON_SIZE_PREVIEW)) { ImBuf *ibuf = BKE_previewimg_to_imbuf(entry->local_data.preview_image, ICON_SIZE_PREVIEW); - ret->preview_icon_id = BKE_icon_imbuf_create(ibuf); + if (ibuf) { + ret->preview_icon_id = BKE_icon_imbuf_create(ibuf); + } } BLI_addtail(&cache->cached_entries, ret); return ret; @@ -2034,11 +2242,11 @@ FileDirEntry *filelist_file_ex(struct FileList *filelist, const int index, const ret = filelist_file_create_entry(filelist, index); old_index = cache->misc_entries_indices[cache->misc_cursor]; if ((old = BLI_ghash_popkey(cache->misc_entries, POINTER_FROM_INT(old_index), NULL))) { - BLI_ghash_remove(cache->uuids, old->uuid, NULL, NULL); + BLI_ghash_remove(cache->uids, POINTER_FROM_UINT(old->uid), NULL, NULL); filelist_file_release_entry(filelist, old); } BLI_ghash_insert(cache->misc_entries, POINTER_FROM_INT(index), ret); - BLI_ghash_insert(cache->uuids, ret->uuid, ret); + BLI_ghash_insert(cache->uids, POINTER_FROM_UINT(ret->uid), ret); cache->misc_entries_indices[cache->misc_cursor] = index; cache->misc_cursor = (cache->misc_cursor + 1) % cache_size; @@ -2057,19 +2265,21 @@ FileDirEntry *filelist_file(struct FileList *filelist, int index) return filelist_file_ex(filelist, index, true); } -int filelist_file_findpath(struct FileList *filelist, const char *filename) +/** + * Find a file from a file name, or more precisely, its file-list relative path, inside the + * filtered items. \return The index of the found file or -1. + */ +int filelist_file_find_path(struct FileList *filelist, const char *filename) { - int fidx = -1; - if (filelist->filelist.nbr_entries_filtered == FILEDIR_NBR_ENTRIES_UNSET) { - return fidx; + return -1; } - /* XXX TODO Cache could probably use a ghash on paths too? Not really urgent though. - * This is only used to find again renamed entry, - * annoying but looks hairy to get rid of it currently. */ + /* XXX TODO: Cache could probably use a ghash on paths too? Not really urgent though. + * This is only used to find again renamed entry, + * annoying but looks hairy to get rid of it currently. */ - for (fidx = 0; fidx < filelist->filelist.nbr_entries_filtered; fidx++) { + for (int fidx = 0; fidx < filelist->filelist.nbr_entries_filtered; fidx++) { FileListInternEntry *entry = filelist->filelist_intern.filtered[fidx]; if (STREQ(entry->relpath, filename)) { return fidx; @@ -2080,38 +2290,53 @@ int filelist_file_findpath(struct FileList *filelist, const char *filename) } /** - * Get the ID a file represents (if any). For #FILE_MAIN, #FILE_MAIN_ASSET. + * Find a file representing \a id. + * \return The index of the found file or -1. */ -ID *filelist_file_get_id(const FileDirEntry *file) -{ - return file->id; -} - -FileDirEntry *filelist_entry_find_uuid(struct FileList *filelist, const int uuid[4]) +int filelist_file_find_id(const FileList *filelist, const ID *id) { if (filelist->filelist.nbr_entries_filtered == FILEDIR_NBR_ENTRIES_UNSET) { - return NULL; + return -1; } - if (filelist->filelist_cache.uuids) { - FileDirEntry *entry = BLI_ghash_lookup(filelist->filelist_cache.uuids, uuid); - if (entry) { - return entry; + for (int fidx = 0; fidx < filelist->filelist.nbr_entries_filtered; fidx++) { + FileListInternEntry *entry = filelist->filelist_intern.filtered[fidx]; + if (entry->local_data.id == id) { + return fidx; } } - { - int fidx; + return -1; +} - for (fidx = 0; fidx < filelist->filelist.nbr_entries_filtered; fidx++) { - FileListInternEntry *entry = filelist->filelist_intern.filtered[fidx]; - if (memcmp(entry->uuid, uuid, sizeof(entry->uuid)) == 0) { - return filelist_file(filelist, fidx); - } - } - } +/** + * Get the ID a file represents (if any). For #FILE_MAIN, #FILE_MAIN_ASSET. + */ +ID *filelist_file_get_id(const FileDirEntry *file) +{ + return file->id; +} - return NULL; +#define FILE_UID_UNSET 0 + +static FileUID filelist_uid_generate(FileList *filelist) +{ + /* Using an atomic operation to avoid having to lock thread... + * Note that we do not really need this here currently, since there is a single listing thread, + * but better remain consistent about threading! */ + return atomic_add_and_fetch_uint32(&filelist->filelist_intern.curr_uid, 1); +} + +bool filelist_uid_is_set(const FileUID uid) +{ + FileUID unset_uid; + filelist_uid_unset(&unset_uid); + return unset_uid != uid; +} + +void filelist_uid_unset(FileUID *r_uid) +{ + *r_uid = FILE_UID_UNSET; } void filelist_file_cache_slidingwindow_set(FileList *filelist, size_t window_size) @@ -2147,7 +2372,7 @@ static bool filelist_file_cache_block_create(FileList *filelist, /* That entry might have already been requested and stored in misc cache... */ if ((entry = BLI_ghash_popkey(cache->misc_entries, POINTER_FROM_INT(idx), NULL)) == NULL) { entry = filelist_file_create_entry(filelist, idx); - BLI_ghash_insert(cache->uuids, entry->uuid, entry); + BLI_ghash_insert(cache->uids, POINTER_FROM_UINT(entry->uid), entry); } cache->block_entries[cursor] = entry; } @@ -2173,7 +2398,7 @@ static void filelist_file_cache_block_release(struct FileList *filelist, __func__, cursor /*, cache->block_entries[cursor], cache->block_entries[cursor]->relpath*/); #endif - BLI_ghash_remove(cache->uuids, entry->uuid, NULL, NULL); + BLI_ghash_remove(cache->uids, POINTER_FROM_UINT(entry->uid), NULL, NULL); filelist_file_release_entry(filelist, entry); #ifndef NDEBUG cache->block_entries[cursor] = NULL; @@ -2305,7 +2530,7 @@ bool filelist_file_cache_block(struct FileList *filelist, const int index) if (start_index < cache->block_start_index) { /* Add (request) needed entries before already cached ones. */ - /* Note: We need some index black magic to wrap around (cycle) + /* NOTE: We need some index black magic to wrap around (cycle) * inside our cache_size array... */ int size1 = cache->block_start_index - start_index; int size2 = 0; @@ -2337,7 +2562,7 @@ bool filelist_file_cache_block(struct FileList *filelist, const int index) // printf("\tstart-extended...\n"); if (end_index > cache->block_end_index) { /* Add (request) needed entries after already cached ones. */ - /* Note: We need some index black magic to wrap around (cycle) + /* NOTE: We need some index black magic to wrap around (cycle) * inside our cache_size array... */ int size1 = end_index - cache->block_end_index; int size2 = 0; @@ -2408,7 +2633,8 @@ void filelist_cache_previews_set(FileList *filelist, const bool use_previews) if (use_previews && (filelist->flags & FL_IS_READY)) { cache->flags |= FLC_PREVIEWS_ACTIVE; - BLI_assert((cache->previews_pool == NULL) && (cache->previews_done == NULL)); + BLI_assert((cache->previews_pool == NULL) && (cache->previews_done == NULL) && + (cache->previews_todo_count == 0)); // printf("%s: Init Previews...\n", __func__); @@ -2481,6 +2707,18 @@ bool filelist_cache_previews_running(FileList *filelist) return (cache->previews_pool != NULL); } +bool filelist_cache_previews_done(FileList *filelist) +{ + FileListEntryCache *cache = &filelist->filelist_cache; + if ((cache->flags & FLC_PREVIEWS_ACTIVE) == 0) { + /* There are no previews. */ + return false; + } + + return (cache->previews_pool == NULL) || (cache->previews_done == NULL) || + (cache->previews_todo_count == (size_t)BLI_thread_queue_len(cache->previews_done)); +} + /* would recognize .blend as well */ static bool file_is_blend_backup(const char *str) { @@ -2623,9 +2861,10 @@ int ED_file_extension_icon(const char *path) } } -int filelist_needs_reading(struct FileList *filelist) +int filelist_needs_reading(FileList *filelist) { - return (filelist->filelist.nbr_entries == FILEDIR_NBR_ENTRIES_UNSET); + return (filelist->filelist.nbr_entries == FILEDIR_NBR_ENTRIES_UNSET) || + filelist_needs_force_reset(filelist); } uint filelist_entry_select_set(const FileList *filelist, @@ -2635,7 +2874,7 @@ uint filelist_entry_select_set(const FileList *filelist, FileCheckType check) { /* Default NULL pointer if not found is fine here! */ - void **es_p = BLI_ghash_lookup_p(filelist->selection_state, entry->uuid); + void **es_p = BLI_ghash_lookup_p(filelist->selection_state, POINTER_FROM_UINT(entry->uid)); uint entry_flag = es_p ? POINTER_AS_UINT(*es_p) : 0; const uint org_entry_flag = entry_flag; @@ -2663,13 +2902,12 @@ uint filelist_entry_select_set(const FileList *filelist, *es_p = POINTER_FROM_UINT(entry_flag); } else { - BLI_ghash_remove(filelist->selection_state, entry->uuid, MEM_freeN, NULL); + BLI_ghash_remove(filelist->selection_state, POINTER_FROM_UINT(entry->uid), NULL, NULL); } } else if (entry_flag) { - void *key = MEM_mallocN(sizeof(entry->uuid), __func__); - memcpy(key, entry->uuid, sizeof(entry->uuid)); - BLI_ghash_insert(filelist->selection_state, key, POINTER_FROM_UINT(entry_flag)); + BLI_ghash_insert( + filelist->selection_state, POINTER_FROM_UINT(entry->uid), POINTER_FROM_UINT(entry_flag)); } } @@ -2707,7 +2945,8 @@ uint filelist_entry_select_get(FileList *filelist, FileDirEntry *entry, FileChec if (((check == CHECK_ALL)) || ((check == CHECK_DIRS) && (entry->typeflag & FILE_TYPE_DIR)) || ((check == CHECK_FILES) && !(entry->typeflag & FILE_TYPE_DIR))) { /* Default NULL pointer if not found is fine here! */ - return POINTER_AS_UINT(BLI_ghash_lookup(filelist->selection_state, entry->uuid)); + return POINTER_AS_UINT( + BLI_ghash_lookup(filelist->selection_state, POINTER_FROM_UINT(entry->uid))); } return 0; @@ -2732,7 +2971,7 @@ bool filelist_entry_is_selected(FileList *filelist, const int index) /* BLI_ghash_lookup returns NULL if not found, which gets mapped to 0, which gets mapped to * "not selected". */ const uint selection_state = POINTER_AS_UINT( - BLI_ghash_lookup(filelist->selection_state, intern_entry->uuid)); + BLI_ghash_lookup(filelist->selection_state, POINTER_FROM_UINT(intern_entry->uid))); return selection_state != 0; } @@ -2884,76 +3123,129 @@ static int filelist_readjob_list_dir(const char *root, return nbr_entries; } -static int filelist_readjob_list_lib(const char *root, ListBase *entries, const bool skip_currpar) +typedef enum ListLibOptions { + /* Will read both the groups + actual ids from the library. Reduces the amount of times that + * a library needs to be opened. */ + LIST_LIB_RECURSIVE = (1 << 0), + + /* Will only list assets. */ + LIST_LIB_ASSETS_ONLY = (1 << 1), + + /* Add given root as result. */ + LIST_LIB_ADD_PARENT = (1 << 2), +} ListLibOptions; + +static FileListInternEntry *filelist_readjob_list_lib_group_create(const int idcode, + const char *group_name) +{ + FileListInternEntry *entry = MEM_callocN(sizeof(*entry), __func__); + entry->relpath = BLI_strdup(group_name); + entry->typeflag |= FILE_TYPE_BLENDERLIB | FILE_TYPE_DIR; + entry->blentype = idcode; + return entry; +} + +static void filelist_readjob_list_lib_add_datablocks(ListBase *entries, + LinkNode *datablock_infos, + const bool prefix_relpath_with_group_name, + const int idcode, + const char *group_name) +{ + for (LinkNode *ln = datablock_infos; ln; ln = ln->next) { + struct BLODataBlockInfo *info = ln->link; + FileListInternEntry *entry = MEM_callocN(sizeof(*entry), __func__); + if (prefix_relpath_with_group_name) { + entry->relpath = BLI_sprintfN("%s/%s", group_name, info->name); + } + else { + entry->relpath = BLI_strdup(info->name); + } + entry->typeflag |= FILE_TYPE_BLENDERLIB; + if (info && info->asset_data) { + entry->typeflag |= FILE_TYPE_ASSET; + /* Moves ownership! */ + entry->imported_asset_data = info->asset_data; + } + entry->blentype = idcode; + BLI_addtail(entries, entry); + } +} + +static int filelist_readjob_list_lib(const char *root, + ListBase *entries, + const ListLibOptions options) { - FileListInternEntry *entry; - LinkNode *ln, *names = NULL, *datablock_infos = NULL; - int i, nitems, idcode = 0, nbr_entries = 0; char dir[FILE_MAX_LIBEXTRA], *group; - bool ok; struct BlendHandle *libfiledata = NULL; - /* name test */ - ok = BLO_library_path_explode(root, dir, &group, NULL); - if (!ok) { - return nbr_entries; + /* Check if the given root is actually a library. All folders are passed to + * `filelist_readjob_list_lib` and based on the number of found entries `filelist_readjob_do` + * will do a dir listing only when this function does not return any entries. */ + /* TODO: We should consider introducing its own function to detect if it is a lib and + * call it directly from `filelist_readjob_do` to increase readability. */ + const bool is_lib = BLO_library_path_explode(root, dir, &group, NULL); + if (!is_lib) { + return 0; } - /* there we go */ + /* Open the library file. */ BlendFileReadReport bf_reports = {.reports = NULL}; libfiledata = BLO_blendhandle_from_file(dir, &bf_reports); if (libfiledata == NULL) { - return nbr_entries; - } - - /* memory for strings is passed into filelist[i].entry->relpath - * and freed in filelist_entry_free. */ - if (group) { - idcode = groupname_to_code(group); - datablock_infos = BLO_blendhandle_get_datablock_info(libfiledata, idcode, &nitems); - } - else { - names = BLO_blendhandle_get_linkable_groups(libfiledata); - nitems = BLI_linklist_count(names); + return 0; } - BLO_blendhandle_close(libfiledata); - - if (!skip_currpar) { - entry = MEM_callocN(sizeof(*entry), __func__); + /* Add current parent when requested. */ + int parent_len = 0; + if (options & LIST_LIB_ADD_PARENT) { + FileListInternEntry *entry = MEM_callocN(sizeof(*entry), __func__); entry->relpath = BLI_strdup(FILENAME_PARENT); entry->typeflag |= (FILE_TYPE_BLENDERLIB | FILE_TYPE_DIR); BLI_addtail(entries, entry); - nbr_entries++; + parent_len = 1; } - for (i = 0, ln = (datablock_infos ? datablock_infos : names); i < nitems; i++, ln = ln->next) { - struct BLODataBlockInfo *info = datablock_infos ? ln->link : NULL; - const char *blockname = info ? info->name : ln->link; - - entry = MEM_callocN(sizeof(*entry), __func__); - entry->relpath = BLI_strdup(blockname); - entry->typeflag |= FILE_TYPE_BLENDERLIB; - if (info && info->asset_data) { - entry->typeflag |= FILE_TYPE_ASSET; - /* Moves ownership! */ - entry->imported_asset_data = info->asset_data; - } - if (!(group && idcode)) { - entry->typeflag |= FILE_TYPE_DIR; - entry->blentype = groupname_to_code(blockname); - } - else { - entry->blentype = idcode; + int group_len = 0; + int datablock_len = 0; + const bool group_came_from_path = group != NULL; + if (group_came_from_path) { + const int idcode = groupname_to_code(group); + LinkNode *datablock_infos = BLO_blendhandle_get_datablock_info( + libfiledata, idcode, options & LIST_LIB_ASSETS_ONLY, &datablock_len); + filelist_readjob_list_lib_add_datablocks(entries, datablock_infos, false, idcode, group); + BLI_linklist_freeN(datablock_infos); + } + else { + LinkNode *groups = BLO_blendhandle_get_linkable_groups(libfiledata); + group_len = BLI_linklist_count(groups); + + for (LinkNode *ln = groups; ln; ln = ln->next) { + const char *group_name = ln->link; + const int idcode = groupname_to_code(group_name); + FileListInternEntry *group_entry = filelist_readjob_list_lib_group_create(idcode, + group_name); + BLI_addtail(entries, group_entry); + + if (options & LIST_LIB_RECURSIVE) { + int group_datablock_len; + LinkNode *group_datablock_infos = BLO_blendhandle_get_datablock_info( + libfiledata, idcode, options & LIST_LIB_ASSETS_ONLY, &group_datablock_len); + filelist_readjob_list_lib_add_datablocks( + entries, group_datablock_infos, true, idcode, group_name); + BLI_linklist_freeN(group_datablock_infos); + datablock_len += group_datablock_len; + } } - BLI_addtail(entries, entry); - nbr_entries++; + + BLI_linklist_freeN(groups); } - BLI_linklist_freeN(datablock_infos ? datablock_infos : names); + BLO_blendhandle_close(libfiledata); - return nbr_entries; + /* Return the number of items added to entries. */ + int added_entries_len = group_len + datablock_len + parent_len; + return added_entries_len; } #if 0 @@ -2967,7 +3259,7 @@ static void filelist_readjob_main_recursive(Main *bmain, FileList *filelist) ListBase *lb; int a, fake, idcode, ok, totlib, totbl; - // filelist->type = FILE_MAIN; /* XXX TODO: add modes to filebrowser */ + // filelist->type = FILE_MAIN; /* XXX TODO: add modes to file-browser */ BLI_assert(filelist->filelist.entries == NULL); @@ -3064,7 +3356,7 @@ static void filelist_readjob_main_recursive(Main *bmain, FileList *filelist) ok = 1; if (ok) { if (!(filelist->filter_data.flags & FLF_HIDE_DOT) || id->name[2] != '.') { - if (id->lib == NULL) { + if (!ID_IS_LINKED(id)) { files->entry->relpath = BLI_strdup(id->name + 2); } else { @@ -3073,7 +3365,7 @@ static void filelist_readjob_main_recursive(Main *bmain, FileList *filelist) files->entry->relpath = BLI_strdup(relname); } // files->type |= S_IFREG; -# if 0 /* XXX TODO show the selection status of the objects */ +# if 0 /* XXX TODO: show the selection status of the objects. */ if (!filelist->has_func) { /* F4 DATA BROWSE */ if (idcode == ID_OB) { if ( ((Object *)id)->flag & SELECT) { @@ -3130,14 +3422,91 @@ static void filelist_readjob_main_recursive(Main *bmain, FileList *filelist) } #endif -static void filelist_readjob_do(const bool do_lib, - FileList *filelist, - const char *main_name, - const short *stop, - short *do_update, - float *progress, - ThreadMutex *lock) +typedef struct FileListReadJob { + ThreadMutex lock; + char main_name[FILE_MAX]; + Main *current_main; + struct FileList *filelist; + /** Set to request a partial read that only adds files representing #Main data (IDs). Used when + * #Main may have received changes of interest (e.g. asset removed or renamed). */ + bool only_main_data; + + /** Shallow copy of #filelist for thread-safe access. + * + * The job system calls #filelist_readjob_update which moves any read file from #tmp_filelist + * into #filelist in a thread-safe way. + * + * #tmp_filelist also keeps an `AssetLibrary *` so that it can be loaded in the same thread, and + * moved to #filelist once all categories are loaded. + * + * NOTE: #tmp_filelist is freed in #filelist_readjob_free, so any copied pointers need to be set + * to NULL to avoid double-freeing them. */ + struct FileList *tmp_filelist; +} FileListReadJob; + +static void filelist_readjob_append_entries(FileListReadJob *job_params, + ListBase *from_entries, + int nbr_from_entries, + short *do_update) +{ + BLI_assert(BLI_listbase_count(from_entries) == nbr_from_entries); + if (nbr_from_entries <= 0) { + *do_update = false; + return; + } + + FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */ + BLI_mutex_lock(&job_params->lock); + BLI_movelisttolist(&filelist->filelist.entries, from_entries); + filelist->filelist.nbr_entries += nbr_from_entries; + BLI_mutex_unlock(&job_params->lock); + + *do_update = true; +} + +static bool filelist_readjob_should_recurse_into_entry(const int max_recursion, + const bool is_lib, + const int current_recursion_level, + FileListInternEntry *entry) { + if (max_recursion == 0) { + /* Recursive loading is disabled. */ + return false; + } + if (!is_lib && current_recursion_level > max_recursion) { + /* No more levels of recursion left. */ + return false; + } + /* Show entries when recursion is set to `Blend file` even when `current_recursion_level` exceeds + * `max_recursion`. */ + if (!is_lib && (current_recursion_level >= max_recursion) && + ((entry->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) == 0)) { + return false; + } + if (entry->typeflag & FILE_TYPE_BLENDERLIB) { + /* Libraries are already loaded recursively when recursive loaded is used. No need to add + * them another time. This loading is done with the `LIST_LIB_RECURSIVE` option. */ + return false; + } + if (!(entry->typeflag & FILE_TYPE_DIR)) { + /* Cannot recurse into regular file entries. */ + return false; + } + if (FILENAME_IS_CURRPAR(entry->relpath)) { + /* Don't schedule go to parent entry, (`..`) */ + return false; + } + + return true; +} + +static void filelist_readjob_recursive_dir_add_items(const bool do_lib, + FileListReadJob *job_params, + const short *stop, + short *do_update, + float *progress) +{ + FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */ ListBase entries = {0}; BLI_Stack *todo_dirs; TodoDir *td_dir; @@ -3147,13 +3516,6 @@ static void filelist_readjob_do(const bool do_lib, const int max_recursion = filelist->max_recursion; int nbr_done_dirs = 0, nbr_todo_dirs = 1; - // BLI_assert(filelist->filtered == NULL); - BLI_assert(BLI_listbase_is_empty(&filelist->filelist.entries) && - (filelist->filelist.nbr_entries == FILEDIR_NBR_ENTRIES_UNSET)); - - /* A valid, but empty directory from now. */ - filelist->filelist.nbr_entries = 0; - todo_dirs = BLI_stack_new(sizeof(*td_dir), __func__); td_dir = BLI_stack_push_r(todo_dirs); td_dir->level = 1; @@ -3161,13 +3523,12 @@ static void filelist_readjob_do(const bool do_lib, BLI_strncpy(dir, filelist->filelist.root, sizeof(dir)); BLI_strncpy(filter_glob, filelist->filter_data.filter_glob, sizeof(filter_glob)); - BLI_path_normalize_dir(main_name, dir); + BLI_path_normalize_dir(job_params->main_name, dir); td_dir->dir = BLI_strdup(dir); while (!BLI_stack_is_empty(todo_dirs) && !(*stop)) { FileListInternEntry *entry; int nbr_entries = 0; - bool is_lib = do_lib; char *subdir; char rel_subdir[FILE_MAX_LIBEXTRA]; @@ -3190,65 +3551,59 @@ static void filelist_readjob_do(const bool do_lib, BLI_path_normalize_dir(root, rel_subdir); BLI_path_rel(rel_subdir, root); + bool is_lib = false; if (do_lib) { - nbr_entries = filelist_readjob_list_lib(subdir, &entries, skip_currpar); + ListLibOptions list_lib_options = 0; + if (!skip_currpar) { + list_lib_options |= LIST_LIB_ADD_PARENT; + } + + /* Libraries are loaded recursively when max_recursion is set. It doesn't check if there is + * still a recursion level over. */ + if (max_recursion > 0) { + list_lib_options |= LIST_LIB_RECURSIVE; + } + /* Only load assets when browsing an asset library. For normal file browsing we return all + * entries. `FLF_ASSETS_ONLY` filter can be enabled/disabled by the user.*/ + if (filelist->asset_library_ref) { + list_lib_options |= LIST_LIB_ASSETS_ONLY; + } + nbr_entries = filelist_readjob_list_lib(subdir, &entries, list_lib_options); + if (nbr_entries > 0) { + is_lib = true; + } } - if (!nbr_entries) { - is_lib = false; + + if (!is_lib) { nbr_entries = filelist_readjob_list_dir( - subdir, &entries, filter_glob, do_lib, main_name, skip_currpar); + subdir, &entries, filter_glob, do_lib, job_params->main_name, skip_currpar); } for (entry = entries.first; entry; entry = entry->next) { - BLI_join_dirfile(dir, sizeof(dir), rel_subdir, entry->relpath); - - /* Generate our entry uuid. Abusing uuid as an uint32, shall be more than enough here, - * things would crash way before we overflow that counter! - * Using an atomic operation to avoid having to lock thread... - * Note that we do not really need this here currently, - * since there is a single listing thread, but better - * remain consistent about threading! */ - *((uint32_t *)entry->uuid) = atomic_add_and_fetch_uint32( - (uint32_t *)filelist->filelist_intern.curr_uuid, 1); + entry->uid = filelist_uid_generate(filelist); - /* Only thing we change in direntry here, so we need to free it first. */ + /* When loading entries recursive, the rel_path should be relative from the root dir. + * we combine the relative path to the subdir with the relative path of the entry. */ + BLI_join_dirfile(dir, sizeof(dir), rel_subdir, entry->relpath); MEM_freeN(entry->relpath); entry->relpath = BLI_strdup(dir + 2); /* + 2 to remove '//' * added by BLI_path_rel to rel_subdir. */ - entry->name = BLI_strdup(fileentry_uiname(root, entry->relpath, entry->typeflag, dir)); + entry->name = fileentry_uiname(root, entry->relpath, entry->typeflag, dir); entry->free_name = true; - /* Here we decide whether current filedirentry is to be listed too, or not. */ - if (max_recursion && (is_lib || (recursion_level <= max_recursion))) { - if (((entry->typeflag & FILE_TYPE_DIR) == 0) || FILENAME_IS_CURRPAR(entry->relpath)) { - /* Skip... */ - } - else if (!is_lib && (recursion_level >= max_recursion) && - ((entry->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) == 0)) { - /* Do not recurse in real directories in this case, only in .blend libs. */ - } - else { - /* We have a directory we want to list, add it to todo list! */ - BLI_join_dirfile(dir, sizeof(dir), root, entry->relpath); - BLI_path_normalize_dir(main_name, dir); - td_dir = BLI_stack_push_r(todo_dirs); - td_dir->level = recursion_level + 1; - td_dir->dir = BLI_strdup(dir); - nbr_todo_dirs++; - } + if (filelist_readjob_should_recurse_into_entry( + max_recursion, is_lib, recursion_level, entry)) { + /* We have a directory we want to list, add it to todo list! */ + BLI_join_dirfile(dir, sizeof(dir), root, entry->relpath); + BLI_path_normalize_dir(job_params->main_name, dir); + td_dir = BLI_stack_push_r(todo_dirs); + td_dir->level = recursion_level + 1; + td_dir->dir = BLI_strdup(dir); + nbr_todo_dirs++; } } - if (nbr_entries) { - BLI_mutex_lock(lock); - - *do_update = true; - - BLI_movelisttolist(&filelist->filelist.entries, &entries); - filelist->filelist.nbr_entries += nbr_entries; - - BLI_mutex_unlock(lock); - } + filelist_readjob_append_entries(job_params, &entries, nbr_entries, do_update); nbr_done_dirs++; *progress = (float)nbr_done_dirs / (float)nbr_todo_dirs; @@ -3265,63 +3620,97 @@ static void filelist_readjob_do(const bool do_lib, BLI_stack_free(todo_dirs); } -static void filelist_readjob_dir(Main *UNUSED(current_main), - FileList *filelist, - const char *main_name, +static void filelist_readjob_do(const bool do_lib, + FileListReadJob *job_params, + const short *stop, + short *do_update, + float *progress) +{ + FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */ + + // BLI_assert(filelist->filtered == NULL); + BLI_assert(BLI_listbase_is_empty(&filelist->filelist.entries) && + (filelist->filelist.nbr_entries == FILEDIR_NBR_ENTRIES_UNSET)); + + /* A valid, but empty directory from now. */ + filelist->filelist.nbr_entries = 0; + + filelist_readjob_recursive_dir_add_items(do_lib, job_params, stop, do_update, progress); +} + +static void filelist_readjob_dir(FileListReadJob *job_params, short *stop, short *do_update, - float *progress, - ThreadMutex *lock) + float *progress) { - filelist_readjob_do(false, filelist, main_name, stop, do_update, progress, lock); + filelist_readjob_do(false, job_params, stop, do_update, progress); } -static void filelist_readjob_lib(Main *UNUSED(current_main), - FileList *filelist, - const char *main_name, +static void filelist_readjob_lib(FileListReadJob *job_params, short *stop, short *do_update, - float *progress, - ThreadMutex *lock) + float *progress) { - filelist_readjob_do(true, filelist, main_name, stop, do_update, progress, lock); + filelist_readjob_do(true, job_params, stop, do_update, progress); } -static void filelist_readjob_main(Main *current_main, - FileList *filelist, - const char *main_name, - short *stop, - short *do_update, - float *progress, - ThreadMutex *lock) +static void filelist_asset_library_path(const FileListReadJob *job_params, + char r_library_root_path[FILE_MAX]) { - /* TODO! */ - filelist_readjob_dir(current_main, filelist, main_name, stop, do_update, progress, lock); + if (job_params->filelist->type == FILE_MAIN_ASSET) { + /* For the "Current File" library (#FILE_MAIN_ASSET) we get the asset library root path based + * on main. */ + BKE_asset_library_find_suitable_root_path_from_main(job_params->current_main, + r_library_root_path); + } + else { + BLI_strncpy(r_library_root_path, job_params->tmp_filelist->filelist.root, FILE_MAX); + } } /** - * \warning Acts on main, so NOT thread-safe! + * Load asset library data, which currently means loading the asset catalogs for the library. */ -static void filelist_readjob_main_assets(Main *current_main, - FileList *filelist, - const char *UNUSED(main_name), - short *UNUSED(stop), - short *do_update, - float *UNUSED(progress), - ThreadMutex *UNUSED(lock)) +static void filelist_readjob_load_asset_library_data(FileListReadJob *job_params, short *do_update) { - BLI_assert(BLI_listbase_is_empty(&filelist->filelist.entries) && - (filelist->filelist.nbr_entries == FILEDIR_NBR_ENTRIES_UNSET)); + FileList *tmp_filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */ - /* A valid, but empty directory from now. */ - filelist->filelist.nbr_entries = 0; + *do_update = false; + + if (job_params->filelist->asset_library_ref == NULL) { + return; + } + if (tmp_filelist->asset_library != NULL) { + /* Asset library already loaded. */ + return; + } + + char library_root_path[FILE_MAX]; + filelist_asset_library_path(job_params, library_root_path); + + /* Load asset catalogs, into the temp filelist for thread-safety. + * #filelist_readjob_endjob() will move it into the real filelist. */ + tmp_filelist->asset_library = BKE_asset_library_load(library_root_path); + *do_update = true; +} + +static void filelist_readjob_main_assets_add_items(FileListReadJob *job_params, + short *UNUSED(stop), + short *do_update, + float *UNUSED(progress)) +{ + FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */ FileListInternEntry *entry; ListBase tmp_entries = {0}; ID *id_iter; int nbr_entries = 0; - FOREACH_MAIN_ID_BEGIN (current_main, id_iter) { + /* Make sure no IDs are added/removed/reallocated in the main thread while this is running in + * parallel. */ + BKE_main_lock(job_params->current_main); + + FOREACH_MAIN_ID_BEGIN (job_params->current_main, id_iter) { if (!id_iter->asset_data) { continue; } @@ -3334,8 +3723,7 @@ static void filelist_readjob_main_assets(Main *current_main, entry->free_name = false; entry->typeflag |= FILE_TYPE_BLENDERLIB | FILE_TYPE_ASSET; entry->blentype = GS(id_iter->name); - *((uint32_t *)entry->uuid) = atomic_add_and_fetch_uint32( - (uint32_t *)filelist->filelist_intern.curr_uuid, 1); + entry->uid = filelist_uid_generate(filelist); entry->local_data.preview_image = BKE_asset_metadata_preview_get_from_id(id_iter->asset_data, id_iter); entry->local_data.id = id_iter; @@ -3344,25 +3732,93 @@ static void filelist_readjob_main_assets(Main *current_main, } FOREACH_MAIN_ID_END; + BKE_main_unlock(job_params->current_main); + if (nbr_entries) { *do_update = true; BLI_movelisttolist(&filelist->filelist.entries, &tmp_entries); filelist->filelist.nbr_entries += nbr_entries; - filelist->filelist.nbr_entries_filtered = filelist->filelist.entry_idx_start = - filelist->filelist.entry_idx_end = -1; + filelist->filelist.nbr_entries_filtered = -1; } } -typedef struct FileListReadJob { - ThreadMutex lock; - char main_name[FILE_MAX]; - Main *current_main; - struct FileList *filelist; - /** XXX We may use a simpler struct here... just a linked list and root path? */ - struct FileList *tmp_filelist; -} FileListReadJob; +/** + * Check if \a bmain is stored within the root path of \a filelist. This means either directly or + * in some nested directory. In other words, it checks if the \a filelist root path is contained in + * the path to \a bmain. + * This is irrespective of the recursion level displayed, it basically assumes unlimited recursion + * levels. + */ +static bool filelist_contains_main(const FileList *filelist, const Main *bmain) +{ + const char *main_path = BKE_main_blendfile_path(bmain); + return main_path[0] && BLI_path_contains(filelist->filelist.root, main_path); +} + +static void filelist_readjob_asset_library(FileListReadJob *job_params, + short *stop, + short *do_update, + float *progress) +{ + FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */ + + BLI_assert(BLI_listbase_is_empty(&filelist->filelist.entries) && + (filelist->filelist.nbr_entries == FILEDIR_NBR_ENTRIES_UNSET)); + + /* A valid, but empty file-list from now. */ + filelist->filelist.nbr_entries = 0; + + /* NOP if already read. */ + filelist_readjob_load_asset_library_data(job_params, do_update); + + if (filelist_contains_main(filelist, job_params->current_main)) { + filelist_readjob_main_assets_add_items(job_params, stop, do_update, progress); + } + if (!job_params->only_main_data) { + filelist_readjob_recursive_dir_add_items(true, job_params, stop, do_update, progress); + } +} + +static void filelist_readjob_main(FileListReadJob *job_params, + short *stop, + short *do_update, + float *progress) +{ + /* TODO! */ + filelist_readjob_dir(job_params, stop, do_update, progress); +} + +static void filelist_readjob_main_assets(FileListReadJob *job_params, + short *stop, + short *do_update, + float *progress) +{ + FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */ + BLI_assert(BLI_listbase_is_empty(&filelist->filelist.entries) && + (filelist->filelist.nbr_entries == FILEDIR_NBR_ENTRIES_UNSET)); + + filelist_readjob_load_asset_library_data(job_params, do_update); + + /* A valid, but empty file-list from now. */ + filelist->filelist.nbr_entries = 0; + + filelist_readjob_main_assets_add_items(job_params, stop, do_update, progress); +} +/** + * Check if the read-job is requesting a partial reread of the file list only. + */ +static bool filelist_readjob_is_partial_read(const FileListReadJob *read_job) +{ + return read_job->only_main_data; +} + +/** + * \note This may trigger partial filelist reading. If the #FL_FORCE_RESET_MAIN_FILES flag is set, + * some current entries are kept and we just call the readjob to update the main files (see + * #FileListReadJob.only_main_data). + */ static void filelist_readjob_startjob(void *flrjv, short *stop, short *do_update, float *progress) { FileListReadJob *flrj = flrjv; @@ -3381,26 +3837,29 @@ static void filelist_readjob_startjob(void *flrjv, short *stop, short *do_update flrj->tmp_filelist->filelist_intern.filtered = NULL; BLI_listbase_clear(&flrj->tmp_filelist->filelist_intern.entries); - memset(flrj->tmp_filelist->filelist_intern.curr_uuid, - 0, - sizeof(flrj->tmp_filelist->filelist_intern.curr_uuid)); + if (filelist_readjob_is_partial_read(flrj)) { + /* Don't unset the current UID on partial read, would give duplicates otherwise. */ + } + else { + filelist_uid_unset(&flrj->tmp_filelist->filelist_intern.curr_uid); + } flrj->tmp_filelist->libfiledata = NULL; memset(&flrj->tmp_filelist->filelist_cache, 0, sizeof(flrj->tmp_filelist->filelist_cache)); flrj->tmp_filelist->selection_state = NULL; - flrj->tmp_filelist->asset_library = NULL; + flrj->tmp_filelist->asset_library_ref = NULL; + flrj->tmp_filelist->filter_data.asset_catalog_filter = NULL; BLI_mutex_unlock(&flrj->lock); - flrj->tmp_filelist->read_job_fn(flrj->current_main, - flrj->tmp_filelist, - flrj->main_name, - stop, - do_update, - progress, - &flrj->lock); + flrj->tmp_filelist->read_job_fn(flrj, stop, do_update, progress); } +/** + * \note This may update for a partial filelist reading job. If the #FL_FORCE_RESET_MAIN_FILES flag + * is set, some current entries are kept and we just call the readjob to update the main + * files (see #FileListReadJob.only_main_data). + */ static void filelist_readjob_update(void *flrjv) { FileListReadJob *flrj = flrjv; @@ -3420,11 +3879,21 @@ static void filelist_readjob_update(void *flrjv) flrj->tmp_filelist->filelist.nbr_entries = 0; } + if (flrj->tmp_filelist->asset_library) { + flrj->filelist->asset_library = flrj->tmp_filelist->asset_library; + } + + /* Important for partial reads: Copy increased UID counter back to the real list. */ + if (flrj->tmp_filelist->filelist_intern.curr_uid > fl_intern->curr_uid) { + fl_intern->curr_uid = flrj->tmp_filelist->filelist_intern.curr_uid; + } + BLI_mutex_unlock(&flrj->lock); if (new_nbr_entries) { - /* Do not clear selection cache, we can assume already 'selected' uuids are still valid! */ - filelist_clear_ex(flrj->filelist, true, false); + /* Do not clear selection cache, we can assume already 'selected' UIDs are still valid! Keep + * the asset library data we just read. */ + filelist_clear_ex(flrj->filelist, false, true, false); flrj->filelist->flags |= (FL_NEED_SORTING | FL_NEED_FILTERING); } @@ -3466,7 +3935,7 @@ static void filelist_readjob_free(void *flrjv) MEM_freeN(flrj); } -void filelist_readjob_start(FileList *filelist, const bContext *C) +void filelist_readjob_start(FileList *filelist, const int space_notifier, const bContext *C) { Main *bmain = CTX_data_main(C); wmJob *wm_job; @@ -3481,8 +3950,11 @@ void filelist_readjob_start(FileList *filelist, const bContext *C) flrj->filelist = filelist; flrj->current_main = bmain; BLI_strncpy(flrj->main_name, BKE_main_blendfile_path(bmain), sizeof(flrj->main_name)); + if ((filelist->flags & FL_FORCE_RESET_MAIN_FILES) && !(filelist->flags & FL_FORCE_RESET)) { + flrj->only_main_data = true; + } - filelist->flags &= ~(FL_FORCE_RESET | FL_IS_READY); + filelist->flags &= ~(FL_FORCE_RESET | FL_FORCE_RESET_MAIN_FILES | FL_IS_READY); filelist->flags |= FL_IS_PENDING; /* Init even for single threaded execution. Called functions use it. */ @@ -3498,22 +3970,19 @@ void filelist_readjob_start(FileList *filelist, const bContext *C) filelist_readjob_endjob(flrj); filelist_readjob_free(flrj); - WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST | NA_JOB_FINISHED, NULL); + WM_event_add_notifier(C, space_notifier | NA_JOB_FINISHED, NULL); return; } /* setup job */ wm_job = WM_jobs_get(CTX_wm_manager(C), CTX_wm_window(C), - CTX_data_scene(C), + filelist, "Listing Dirs...", WM_JOB_PROGRESS, WM_JOB_TYPE_FILESEL_READDIR); WM_jobs_customdata_set(wm_job, flrj, filelist_readjob_free); - WM_jobs_timer(wm_job, - 0.01, - NC_SPACE | ND_SPACE_FILE_LIST, - NC_SPACE | ND_SPACE_FILE_LIST | NA_JOB_FINISHED); + WM_jobs_timer(wm_job, 0.01, space_notifier, space_notifier | NA_JOB_FINISHED); WM_jobs_callbacks( wm_job, filelist_readjob_startjob, NULL, filelist_readjob_update, filelist_readjob_endjob); @@ -3521,12 +3990,12 @@ void filelist_readjob_start(FileList *filelist, const bContext *C) WM_jobs_start(CTX_wm_manager(C), wm_job); } -void filelist_readjob_stop(wmWindowManager *wm, Scene *owner_scene) +void filelist_readjob_stop(FileList *filelist, wmWindowManager *wm) { - WM_jobs_kill_type(wm, owner_scene, WM_JOB_TYPE_FILESEL_READDIR); + WM_jobs_kill_type(wm, filelist, WM_JOB_TYPE_FILESEL_READDIR); } -int filelist_readjob_running(wmWindowManager *wm, Scene *owner_scene) +int filelist_readjob_running(FileList *filelist, wmWindowManager *wm) { - return WM_jobs_test(wm, owner_scene, WM_JOB_TYPE_FILESEL_READDIR); + return WM_jobs_test(wm, filelist, WM_JOB_TYPE_FILESEL_READDIR); } diff --git a/source/blender/editors/space_file/filelist.h b/source/blender/editors/space_file/filelist.h index 9eb70dd8437..0048a349dca 100644 --- a/source/blender/editors/space_file/filelist.h +++ b/source/blender/editors/space_file/filelist.h @@ -27,14 +27,17 @@ extern "C" { #endif +struct AssetLibraryReference; struct BlendHandle; struct FileList; -struct FileSelectAssetLibraryUID; struct FileSelection; +struct bUUID; struct wmWindowManager; struct FileDirEntry; +typedef uint32_t FileUID; + typedef enum FileSelType { FILE_SEL_REMOVE = 0, FILE_SEL_ADD = 1, @@ -69,21 +72,31 @@ 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_tag_needs_filtering(struct FileList *filelist); void filelist_filter(struct FileList *filelist); void filelist_setlibrary(struct FileList *filelist, - const struct FileSelectAssetLibraryUID *asset_library); + const struct AssetLibraryReference *asset_library_ref); void filelist_init_icons(void); void filelist_free_icons(void); struct ImBuf *filelist_getimage(struct FileList *filelist, const int index); struct ImBuf *filelist_file_getimage(const FileDirEntry *file); +struct ImBuf *filelist_geticon_image_ex(const FileDirEntry *file); struct ImBuf *filelist_geticon_image(struct FileList *filelist, const int index); int filelist_geticon(struct FileList *filelist, const int index, const bool is_main); struct FileList *filelist_new(short type); void filelist_settype(struct FileList *filelist, short type); void filelist_clear(struct FileList *filelist); -void filelist_clear_ex(struct FileList *filelist, const bool do_cache, const bool do_selection); +void filelist_clear_ex(struct FileList *filelist, + const bool do_asset_library, + const bool do_cache, + const bool do_selection); +void filelist_clear_from_reset_tag(struct FileList *filelist); void filelist_free(struct FileList *filelist); const char *filelist_dir(struct FileList *filelist); @@ -95,14 +108,17 @@ int filelist_needs_reading(struct FileList *filelist); FileDirEntry *filelist_file(struct FileList *filelist, int index); FileDirEntry *filelist_file_ex(struct FileList *filelist, int index, bool use_request); -int filelist_file_findpath(struct FileList *filelist, const char *file); +int filelist_file_find_path(struct FileList *filelist, const char *file); +int filelist_file_find_id(const struct FileList *filelist, const struct ID *id); struct ID *filelist_file_get_id(const struct FileDirEntry *file); -FileDirEntry *filelist_entry_find_uuid(struct FileList *filelist, const int uuid[4]); +bool filelist_uid_is_set(const FileUID uid); +void filelist_uid_unset(FileUID *r_uid); void filelist_file_cache_slidingwindow_set(struct FileList *filelist, size_t window_size); bool filelist_file_cache_block(struct FileList *filelist, const int index); bool filelist_needs_force_reset(struct FileList *filelist); void filelist_tag_force_reset(struct FileList *filelist); +void filelist_tag_force_reset_mainfiles(struct FileList *filelist); bool filelist_pending(struct FileList *filelist); bool filelist_needs_reset_on_main_changes(const struct FileList *filelist); bool filelist_is_ready(struct FileList *filelist); @@ -136,17 +152,22 @@ 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); -void filelist_readjob_start(struct FileList *filelist, const struct bContext *C); -void filelist_readjob_stop(struct wmWindowManager *wm, struct Scene *owner_scene); -int filelist_readjob_running(struct wmWindowManager *wm, struct Scene *owner_scene); +void filelist_readjob_start(struct FileList *filelist, + int space_notifier, + const struct bContext *C); +void filelist_readjob_stop(struct FileList *filelist, struct wmWindowManager *wm); +int filelist_readjob_running(struct FileList *filelist, struct wmWindowManager *wm); bool filelist_cache_previews_update(struct FileList *filelist); void filelist_cache_previews_set(struct FileList *filelist, const bool use_previews); bool filelist_cache_previews_running(struct FileList *filelist); +bool filelist_cache_previews_done(struct FileList *filelist); #ifdef __cplusplus } diff --git a/source/blender/editors/space_file/filesel.c b/source/blender/editors/space_file/filesel.c index 8e3fc36aa71..11757975a62 100644 --- a/source/blender/editors/space_file/filesel.c +++ b/source/blender/editors/space_file/filesel.c @@ -118,24 +118,22 @@ static void fileselect_ensure_updated_asset_params(SpaceFile *sfile) asset_params = sfile->asset_params = MEM_callocN(sizeof(*asset_params), "FileAssetSelectParams"); asset_params->base_params.details_flags = U_default.file_space_data.details_flags; - asset_params->asset_library.type = FILE_ASSET_LIBRARY_LOCAL; - asset_params->asset_library.custom_library_index = -1; - asset_params->import_type = FILE_ASSET_IMPORT_APPEND; + asset_params->asset_library_ref.type = ASSET_LIBRARY_LOCAL; + asset_params->asset_library_ref.custom_library_index = -1; + asset_params->import_type = FILE_ASSET_IMPORT_APPEND_REUSE; } 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; - base_params->recursion_level = 1; + /* Asset libraries include all sub-directories, so enable maximal recursion. */ + base_params->recursion_level = FILE_SELECT_MAX_RECURSIONS; /* 'SMALL' size by default. More reasonable since this is typically used as regular editor, * space is more of an issue here. */ base_params->thumbnail_size = 96; @@ -378,7 +376,7 @@ FileSelectParams *ED_fileselect_ensure_active_params(SpaceFile *sfile) return &sfile->asset_params->base_params; } - BLI_assert(!"Invalid browse mode set in file space."); + BLI_assert_msg(0, "Invalid browse mode set in file space."); return NULL; } @@ -399,7 +397,7 @@ FileSelectParams *ED_fileselect_get_active_params(const SpaceFile *sfile) return (FileSelectParams *)sfile->asset_params; } - BLI_assert(!"Invalid browse mode set in file space."); + BLI_assert_msg(0, "Invalid browse mode set in file space."); return NULL; } @@ -415,31 +413,32 @@ FileAssetSelectParams *ED_fileselect_get_asset_params(const SpaceFile *sfile) static void fileselect_refresh_asset_params(FileAssetSelectParams *asset_params) { - FileSelectAssetLibraryUID *library = &asset_params->asset_library; + AssetLibraryReference *library = &asset_params->asset_library_ref; FileSelectParams *base_params = &asset_params->base_params; bUserAssetLibrary *user_library = NULL; /* Ensure valid repository, or fall-back to local one. */ - if (library->type == FILE_ASSET_LIBRARY_CUSTOM) { + if (library->type == ASSET_LIBRARY_CUSTOM) { BLI_assert(library->custom_library_index >= 0); user_library = BKE_preferences_asset_library_find_from_index(&U, library->custom_library_index); if (!user_library) { - library->type = FILE_ASSET_LIBRARY_LOCAL; + library->type = ASSET_LIBRARY_LOCAL; } } switch (library->type) { - case FILE_ASSET_LIBRARY_LOCAL: + case ASSET_LIBRARY_LOCAL: base_params->dir[0] = '\0'; break; - case FILE_ASSET_LIBRARY_CUSTOM: + case ASSET_LIBRARY_CUSTOM: BLI_assert(user_library); BLI_strncpy(base_params->dir, user_library->path, sizeof(base_params->dir)); break; } - base_params->type = (library->type == FILE_ASSET_LIBRARY_LOCAL) ? FILE_MAIN_ASSET : FILE_LOADLIB; + base_params->type = (library->type == ASSET_LIBRARY_LOCAL) ? FILE_MAIN_ASSET : + FILE_ASSET_LIBRARY; } void fileselect_refresh_params(SpaceFile *sfile) @@ -450,11 +449,25 @@ void fileselect_refresh_params(SpaceFile *sfile) } } +bool ED_fileselect_is_file_browser(const SpaceFile *sfile) +{ + return (sfile->browse_mode == FILE_BROWSE_MODE_FILES); +} + 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)) { @@ -470,6 +483,18 @@ struct ID *ED_fileselect_active_asset_get(const SpaceFile *sfile) return filelist_file_get_id(file); } +void ED_fileselect_activate_asset_catalog(const SpaceFile *sfile, const bUUID catalog_id) +{ + if (!ED_fileselect_is_asset_browser(sfile)) { + return; + } + + FileAssetSelectParams *params = ED_fileselect_get_asset_params(sfile); + params->asset_catalog_visibility = FILE_SHOW_ASSETS_FROM_CATALOG; + params->catalog_id = catalog_id; + WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, NULL); +} + static void on_reload_activate_by_id(SpaceFile *sfile, onReloadFnData custom_data) { ID *asset_id = (ID *)custom_data; @@ -501,20 +526,57 @@ void ED_fileselect_activate_by_id(SpaceFile *sfile, ID *asset_id, const bool def const FileDirEntry *file = filelist_file_ex(files, file_index, false); if (filelist_file_get_id(file) != asset_id) { - filelist_entry_select_set(files, file, FILE_SEL_REMOVE, FILE_SEL_SELECTED, CHECK_ALL); continue; } params->active_file = file_index; filelist_entry_select_set(files, file, FILE_SEL_ADD, FILE_SEL_SELECTED, CHECK_ALL); - - /* Keep looping to deselect the other files. */ + break; } WM_main_add_notifier(NC_ASSET | NA_ACTIVATED, NULL); WM_main_add_notifier(NC_ASSET | NA_SELECTED, NULL); } +static void on_reload_select_by_relpath(SpaceFile *sfile, onReloadFnData custom_data) +{ + const char *relative_path = custom_data; + ED_fileselect_activate_by_relpath(sfile, relative_path); +} + +void ED_fileselect_activate_by_relpath(SpaceFile *sfile, const char *relative_path) +{ + /* If there are filelist operations running now ("pending" true) or soon ("force reset" true), + * there is a fair chance that the to-be-activated file at relative_path will only be present + * after these operations have completed. Defer activation until then. */ + struct FileList *files = sfile->files; + if (files == NULL || filelist_pending(files) || filelist_needs_force_reset(files)) { + /* Casting away the constness of `relative_path` is safe here, because eventually it just ends + * up in another call to this function, and then it's a const char* again. */ + file_on_reload_callback_register(sfile, on_reload_select_by_relpath, (char *)relative_path); + return; + } + + FileSelectParams *params = ED_fileselect_get_active_params(sfile); + const int num_files_filtered = filelist_files_ensure(files); + + for (int file_index = 0; file_index < num_files_filtered; ++file_index) { + const FileDirEntry *file = filelist_file(files, file_index); + + if (STREQ(file->relpath, relative_path)) { + params->active_file = file_index; + filelist_entry_select_set(files, file, FILE_SEL_ADD, FILE_SEL_SELECTED, CHECK_ALL); + } + } + WM_main_add_notifier(NC_SPACE | ND_SPACE_FILE_PARAMS, NULL); +} + +void ED_fileselect_deselect_all(SpaceFile *sfile) +{ + file_select_deselect_all(sfile, FILE_SEL_SELECTED); + WM_main_add_notifier(NC_SPACE | ND_SPACE_FILE_PARAMS, NULL); +} + /* The subset of FileSelectParams.flag items we store into preferences. Note that FILE_SORT_ALPHA * may also be remembered, but only conditionally. */ #define PARAMS_FLAGS_REMEMBERED (FILE_HIDE_DOT) @@ -858,20 +920,8 @@ FileAttributeColumnType file_attribute_column_type_find_isect(const View2D *v2d, float file_string_width(const char *str) { const uiStyle *style = UI_style_get(); - float width; - UI_fontstyle_set(&style->widget); - if (style->widget.kerning == 1) { /* for BLF_width */ - BLF_enable(style->widget.uifont_id, BLF_KERNING_DEFAULT); - } - - width = BLF_width(style->widget.uifont_id, str, BLF_DRAW_STR_DUMMY_MAX); - - if (style->widget.kerning == 1) { - BLF_disable(style->widget.uifont_id, BLF_KERNING_DEFAULT); - } - - return width; + return BLF_width(style->widget.uifont_id, str, BLF_DRAW_STR_DUMMY_MAX); } float file_font_pointsize(void) @@ -941,6 +991,8 @@ static void file_attribute_columns_init(const FileSelectParams *params, FileLayo void ED_fileselect_init_layout(struct SpaceFile *sfile, ARegion *region) { FileSelectParams *params = ED_fileselect_get_active_params(sfile); + /* Request a slightly more compact layout for asset browsing. */ + const bool compact = ED_fileselect_is_asset_browser(sfile); FileLayout *layout = NULL; View2D *v2d = ®ion->v2d; int numfiles; @@ -960,12 +1012,13 @@ void ED_fileselect_init_layout(struct SpaceFile *sfile, ARegion *region) layout->textheight = textheight; if (params->display == FILE_IMGDISPLAY) { + const float pad_fac = compact ? 0.15f : 0.3f; layout->prv_w = ((float)params->thumbnail_size / 20.0f) * UI_UNIT_X; layout->prv_h = ((float)params->thumbnail_size / 20.0f) * UI_UNIT_Y; - layout->tile_border_x = 0.3f * UI_UNIT_X; - layout->tile_border_y = 0.3f * UI_UNIT_X; - layout->prv_border_x = 0.3f * UI_UNIT_X; - layout->prv_border_y = 0.3f * UI_UNIT_Y; + layout->tile_border_x = pad_fac * UI_UNIT_X; + layout->tile_border_y = pad_fac * UI_UNIT_X; + layout->prv_border_x = pad_fac * UI_UNIT_X; + layout->prv_border_y = pad_fac * UI_UNIT_Y; layout->tile_w = layout->prv_w + 2 * layout->prv_border_x; layout->tile_h = layout->prv_h + 2 * layout->prv_border_y + textheight; layout->width = (int)(BLI_rctf_size_x(&v2d->cur) - 2 * layout->tile_border_x); @@ -1047,7 +1100,7 @@ FileLayout *ED_fileselect_get_layout(struct SpaceFile *sfile, ARegion *region) * Support updating the directory even when this isn't the active space * needed so RNA properties update function isn't context sensitive, see T70255. */ -void ED_file_change_dir_ex(bContext *C, bScreen *screen, ScrArea *area) +void ED_file_change_dir_ex(bContext *C, ScrArea *area) { /* May happen when manipulating non-active spaces. */ if (UNLIKELY(area->spacetype != SPACE_FILE)) { @@ -1057,10 +1110,7 @@ void ED_file_change_dir_ex(bContext *C, bScreen *screen, ScrArea *area) FileSelectParams *params = ED_fileselect_get_active_params(sfile); if (params) { wmWindowManager *wm = CTX_wm_manager(C); - Scene *scene = WM_windows_scene_get_from_screen(wm, screen); - if (LIKELY(scene != NULL)) { - ED_fileselect_clear(wm, scene, sfile); - } + ED_fileselect_clear(wm, sfile); /* Clear search string, it is very rare to want to keep that filter while changing dir, * and usually very annoying to keep it actually! */ @@ -1085,9 +1135,17 @@ void ED_file_change_dir_ex(bContext *C, bScreen *screen, ScrArea *area) void ED_file_change_dir(bContext *C) { - bScreen *screen = CTX_wm_screen(C); ScrArea *area = CTX_wm_area(C); - ED_file_change_dir_ex(C, screen, area); + ED_file_change_dir_ex(C, area); +} + +void file_select_deselect_all(SpaceFile *sfile, uint flag) +{ + FileSelection sel; + sel.first = 0; + sel.last = filelist_files_ensure(sfile->files) - 1; + + filelist_entries_select_index_range_set(sfile->files, &sel, FILE_SEL_REMOVE, flag, CHECK_ALL); } int file_select_match(struct SpaceFile *sfile, const char *pattern, char *matched_file) @@ -1183,11 +1241,11 @@ int autocomplete_file(struct bContext *C, char *str, void *UNUSED(arg_v)) return match; } -void ED_fileselect_clear(wmWindowManager *wm, Scene *owner_scene, SpaceFile *sfile) +void ED_fileselect_clear(wmWindowManager *wm, SpaceFile *sfile) { /* only NULL in rare cases - T29734. */ if (sfile->files) { - filelist_readjob_stop(wm, owner_scene); + filelist_readjob_stop(sfile->files, wm); filelist_freelib(sfile->files); filelist_clear(sfile->files); } @@ -1197,7 +1255,7 @@ void ED_fileselect_clear(wmWindowManager *wm, Scene *owner_scene, SpaceFile *sfi WM_main_add_notifier(NC_SPACE | ND_SPACE_FILE_LIST, NULL); } -void ED_fileselect_exit(wmWindowManager *wm, Scene *owner_scene, SpaceFile *sfile) +void ED_fileselect_exit(wmWindowManager *wm, SpaceFile *sfile) { if (!sfile) { return; @@ -1224,13 +1282,72 @@ void ED_fileselect_exit(wmWindowManager *wm, Scene *owner_scene, SpaceFile *sfil folder_history_list_free(sfile); if (sfile->files) { - ED_fileselect_clear(wm, owner_scene, sfile); + ED_fileselect_clear(wm, sfile); filelist_free(sfile->files); MEM_freeN(sfile->files); sfile->files = NULL; } } +void file_params_smoothscroll_timer_clear(wmWindowManager *wm, wmWindow *win, SpaceFile *sfile) +{ + WM_event_remove_timer(wm, win, sfile->smoothscroll_timer); + sfile->smoothscroll_timer = NULL; +} + +/** + * Set the renaming-state to #FILE_PARAMS_RENAME_POSTSCROLL_PENDING and trigger the smooth-scroll + * timer. To be used right after a file was renamed. + * Note that the caller is responsible for setting the correct rename-file info + * (#FileSelectParams.renamefile or #FileSelectParams.rename_id). + */ +void file_params_invoke_rename_postscroll(wmWindowManager *wm, wmWindow *win, SpaceFile *sfile) +{ + FileSelectParams *params = ED_fileselect_get_active_params(sfile); + + params->rename_flag = FILE_PARAMS_RENAME_POSTSCROLL_PENDING; + + if (sfile->smoothscroll_timer != NULL) { + file_params_smoothscroll_timer_clear(wm, win, sfile); + } + sfile->smoothscroll_timer = WM_event_add_timer(wm, win, TIMER1, 1.0 / 1000.0); + sfile->scroll_offset = 0; +} + +/** + * To be executed whenever renaming ends (successfully or not). + */ +void file_params_rename_end(wmWindowManager *wm, + wmWindow *win, + SpaceFile *sfile, + FileDirEntry *rename_file) +{ + FileSelectParams *params = ED_fileselect_get_active_params(sfile); + + filelist_entry_select_set( + sfile->files, rename_file, FILE_SEL_REMOVE, FILE_SEL_EDITING, CHECK_ALL); + + /* Ensure smooth-scroll timer is active, even if not needed, because that way rename state is + * handled properly. */ + file_params_invoke_rename_postscroll(wm, win, sfile); + /* Also always activate the rename file, even if renaming was canceled. */ + file_params_renamefile_activate(sfile, params); +} + +void file_params_renamefile_clear(FileSelectParams *params) +{ + params->renamefile[0] = '\0'; + params->rename_id = NULL; + params->rename_flag = 0; +} + +static int file_params_find_renamed(const FileSelectParams *params, struct FileList *filelist) +{ + /* Find the file either through the local ID/asset it represents or its relative path. */ + return (params->rename_id != NULL) ? filelist_file_find_id(filelist, params->rename_id) : + filelist_file_find_path(filelist, params->renamefile); +} + /** * Helper used by both main update code, and smooth-scroll timer, * to try to enable rename editing from #FileSelectParams.renamefile name. @@ -1244,28 +1361,33 @@ void file_params_renamefile_activate(SpaceFile *sfile, FileSelectParams *params) return; } - BLI_assert(params->renamefile[0] != '\0'); + BLI_assert(params->renamefile[0] != '\0' || params->rename_id != NULL); - const int idx = filelist_file_findpath(sfile->files, params->renamefile); + const int idx = file_params_find_renamed(params, sfile->files); if (idx >= 0) { FileDirEntry *file = filelist_file(sfile->files, idx); BLI_assert(file != NULL); + params->active_file = idx; + filelist_entry_select_set(sfile->files, file, FILE_SEL_ADD, FILE_SEL_SELECTED, CHECK_ALL); + if ((params->rename_flag & FILE_PARAMS_RENAME_PENDING) != 0) { filelist_entry_select_set(sfile->files, file, FILE_SEL_ADD, FILE_SEL_EDITING, CHECK_ALL); params->rename_flag = FILE_PARAMS_RENAME_ACTIVE; } else if ((params->rename_flag & FILE_PARAMS_RENAME_POSTSCROLL_PENDING) != 0) { - filelist_entry_select_set(sfile->files, file, FILE_SEL_ADD, FILE_SEL_HIGHLIGHTED, CHECK_ALL); - params->renamefile[0] = '\0'; + file_select_deselect_all(sfile, FILE_SEL_SELECTED); + filelist_entry_select_set( + sfile->files, file, FILE_SEL_ADD, FILE_SEL_SELECTED | FILE_SEL_HIGHLIGHTED, CHECK_ALL); + params->active_file = idx; + file_params_renamefile_clear(params); params->rename_flag = FILE_PARAMS_RENAME_POSTSCROLL_ACTIVE; } } /* File listing is now async, only reset renaming if matching entry is not found * when file listing is not done. */ else if (filelist_is_ready(sfile->files)) { - params->renamefile[0] = '\0'; - params->rename_flag = 0; + file_params_renamefile_clear(params); } } diff --git a/source/blender/editors/space_file/fsmenu.c b/source/blender/editors/space_file/fsmenu.c index 2d1151c8f4d..97f22ca7d89 100644 --- a/source/blender/editors/space_file/fsmenu.c +++ b/source/blender/editors/space_file/fsmenu.c @@ -769,21 +769,22 @@ void fsmenu_read_system(struct FSMenu *fsmenu, int read_bookmarks) FS_INSERT_LAST); const char *home = BLI_getenv("HOME"); - + if (home) { # define FS_MACOS_PATH(path, name, icon) \ BLI_snprintf(line, sizeof(line), path, home); \ fsmenu_insert_entry(fsmenu, FS_CATEGORY_OTHER, line, name, icon, FS_INSERT_LAST); - FS_MACOS_PATH("%s/", NULL, ICON_HOME) - FS_MACOS_PATH("%s/Desktop/", N_("Desktop"), ICON_DESKTOP) - FS_MACOS_PATH("%s/Documents/", N_("Documents"), ICON_DOCUMENTS) - FS_MACOS_PATH("%s/Downloads/", N_("Downloads"), ICON_IMPORT) - FS_MACOS_PATH("%s/Movies/", N_("Movies"), ICON_FILE_MOVIE) - FS_MACOS_PATH("%s/Music/", N_("Music"), ICON_FILE_SOUND) - FS_MACOS_PATH("%s/Pictures/", N_("Pictures"), ICON_FILE_IMAGE) - FS_MACOS_PATH("%s/Library/Fonts/", N_("Fonts"), ICON_FILE_FONT) + FS_MACOS_PATH("%s/", NULL, ICON_HOME) + FS_MACOS_PATH("%s/Desktop/", N_("Desktop"), ICON_DESKTOP) + FS_MACOS_PATH("%s/Documents/", N_("Documents"), ICON_DOCUMENTS) + FS_MACOS_PATH("%s/Downloads/", N_("Downloads"), ICON_IMPORT) + FS_MACOS_PATH("%s/Movies/", N_("Movies"), ICON_FILE_MOVIE) + FS_MACOS_PATH("%s/Music/", N_("Music"), ICON_FILE_SOUND) + FS_MACOS_PATH("%s/Pictures/", N_("Pictures"), ICON_FILE_IMAGE) + FS_MACOS_PATH("%s/Library/Fonts/", N_("Fonts"), ICON_FILE_FONT) # undef FS_MACOS_PATH + } /* Get mounted volumes better method OSX 10.6 and higher, see: * https://developer.apple.com/library/mac/#documentation/CoreFOundation/Reference/CFURLRef/Reference/reference.html @@ -958,7 +959,7 @@ void fsmenu_read_system(struct FSMenu *fsmenu, int read_bookmarks) found = 1; } if (endmntent(fp) == 0) { - fprintf(stderr, "could not close the list of mounted filesystems\n"); + fprintf(stderr, "could not close the list of mounted file-systems\n"); } } /* Check gvfs shares. */ @@ -969,7 +970,7 @@ void fsmenu_read_system(struct FSMenu *fsmenu, int read_bookmarks) BLI_join_dirfile(name, sizeof(name), xdg_runtime_dir, "gvfs/"); const uint dir_len = BLI_filelist_dir_contents(name, &dir); for (uint i = 0; i < dir_len; i++) { - if ((dir[i].type & S_IFDIR)) { + if (dir[i].type & S_IFDIR) { const char *dirname = dir[i].relname; if (dirname[0] != '.') { /* Dir names contain a lot of unwanted text. diff --git a/source/blender/editors/space_file/space_file.c b/source/blender/editors/space_file/space_file.c index 0418bb87768..b115c63a569 100644 --- a/source/blender/editors/space_file/space_file.c +++ b/source/blender/editors/space_file/space_file.c @@ -32,6 +32,7 @@ #include "BKE_appdir.h" #include "BKE_context.h" #include "BKE_global.h" +#include "BKE_main.h" #include "BKE_screen.h" #include "RNA_access.h" @@ -42,6 +43,7 @@ #include "WM_message.h" #include "WM_types.h" +#include "ED_asset.h" #include "ED_fileselect.h" #include "ED_screen.h" #include "ED_space_api.h" @@ -175,10 +177,7 @@ static void file_free(SpaceLink *sl) MEM_SAFE_FREE(sfile->asset_params); MEM_SAFE_FREE(sfile->runtime); - if (sfile->layout) { - MEM_freeN(sfile->layout); - sfile->layout = NULL; - } + MEM_SAFE_FREE(sfile->layout); } /* spacetype; init callback, area size changes, screen set, etc */ @@ -206,7 +205,7 @@ static void file_exit(wmWindowManager *wm, ScrArea *area) sfile->previews_timer = NULL; } - ED_fileselect_exit(wm, NULL, sfile); + ED_fileselect_exit(wm, sfile); } static SpaceLink *file_duplicate(SpaceLink *sl) @@ -305,15 +304,6 @@ static void file_ensure_valid_region_state(bContext *C, } } -/** - * Tag the space to recreate the file-list. - */ -static void file_tag_reset_list(ScrArea *area, SpaceFile *sfile) -{ - filelist_tag_force_reset(sfile->files); - ED_area_tag_refresh(area); -} - static void file_refresh(const bContext *C, ScrArea *area) { wmWindowManager *wm = CTX_wm_manager(C); @@ -328,7 +318,7 @@ static void file_refresh(const bContext *C, ScrArea *area) if (sfile->files && (sfile->tags & FILE_TAG_REBUILD_MAIN_FILES) && filelist_needs_reset_on_main_changes(sfile->files)) { - filelist_tag_force_reset(sfile->files); + filelist_tag_force_reset_mainfiles(sfile->files); } sfile->tags &= ~FILE_TAG_REBUILD_MAIN_FILES; @@ -336,11 +326,18 @@ static void file_refresh(const bContext *C, ScrArea *area) sfile->files = filelist_new(params->type); params->highlight_file = -1; /* added this so it opens nicer (ton) */ } + + if (ED_fileselect_is_asset_browser(sfile)) { + /* Ask the asset code for appropriate ID filter flags for the supported assets, and mask others + * out. */ + params->filter_id &= ED_asset_types_supported_as_filter_flags(); + } + filelist_settype(sfile->files, params->type); filelist_setdir(sfile->files, params->dir); filelist_setrecursion(sfile->files, params->recursion_level); filelist_setsorting(sfile->files, params->sort, params->flag & FILE_SORT_INVERT); - filelist_setlibrary(sfile->files, asset_params ? &asset_params->asset_library : NULL); + filelist_setlibrary(sfile->files, asset_params ? &asset_params->asset_library_ref : NULL); filelist_setfilter_options( sfile->files, (params->flag & FILE_FILTER) != 0, @@ -351,6 +348,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); @@ -360,13 +361,13 @@ static void file_refresh(const bContext *C, ScrArea *area) sfile->recentnr = fsmenu_get_active_indices(fsmenu, FS_CATEGORY_RECENT, params->dir); if (filelist_needs_force_reset(sfile->files)) { - filelist_readjob_stop(wm, CTX_data_scene(C)); - filelist_clear(sfile->files); + filelist_readjob_stop(sfile->files, wm); + filelist_clear_from_reset_tag(sfile->files); } if (filelist_needs_reading(sfile->files)) { if (!filelist_pending(sfile->files)) { - filelist_readjob_start(sfile->files, C); + filelist_readjob_start(sfile->files, NC_SPACE | ND_SPACE_FILE_LIST, C); } } @@ -423,16 +424,15 @@ static void file_on_reload_callback_call(SpaceFile *sfile) static void file_reset_filelist_showing_main_data(ScrArea *area, SpaceFile *sfile) { if (sfile->files && filelist_needs_reset_on_main_changes(sfile->files)) { - /* Full refresh of the file list if local asset data was changed. Refreshing this view - * is cheap and users expect this to be updated immediately. */ - file_tag_reset_list(area, sfile); + filelist_tag_force_reset_mainfiles(sfile->files); + ED_area_tag_refresh(area); } } -static void file_listener(const wmSpaceTypeListenerParams *params) +static void file_listener(const wmSpaceTypeListenerParams *listener_params) { - ScrArea *area = params->area; - wmNotifier *wmn = params->notifier; + ScrArea *area = listener_params->area; + wmNotifier *wmn = listener_params->notifier; SpaceFile *sfile = (SpaceFile *)area->spacedata.first; /* context changes */ @@ -469,10 +469,19 @@ static void file_listener(const wmSpaceTypeListenerParams *params) break; case NC_ID: { switch (wmn->action) { - case NA_RENAME: + case NA_RENAME: { + const ID *active_file_id = ED_fileselect_active_asset_get(sfile); + /* If a renamed ID is active in the file browser, update scrolling to keep it in view. */ + if (active_file_id && (wmn->reference == active_file_id)) { + FileSelectParams *params = ED_fileselect_get_active_params(sfile); + params->rename_id = active_file_id; + file_params_invoke_rename_postscroll(G_MAIN->wm.first, listener_params->window, sfile); + } + /* Force list to update sorting (with a full reset for now). */ file_reset_filelist_showing_main_data(area, sfile); break; + } } break; } @@ -508,10 +517,10 @@ static void file_main_region_init(wmWindowManager *wm, ARegion *region) WM_event_add_keymap_handler_v2d_mask(®ion->handlers, keymap); } -static void file_main_region_listener(const wmRegionListenerParams *params) +static void file_main_region_listener(const wmRegionListenerParams *listener_params) { - ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + ARegion *region = listener_params->region; + wmNotifier *wmn = listener_params->notifier; /* context changes */ switch (wmn->category) { @@ -568,6 +577,16 @@ static void file_main_region_message_subscribe(const wmRegionMessageSubscribePar /* All properties for this space type. */ WM_msg_subscribe_rna(mbus, &ptr, NULL, &msg_sub_value_area_tag_refresh, __func__); } + + /* Experimental Asset Browser features option. */ + { + PointerRNA ptr; + RNA_pointer_create(NULL, &RNA_PreferencesExperimental, &U.experimental, &ptr); + PropertyRNA *prop = RNA_struct_find_property(&ptr, "use_extended_asset_browser"); + + /* All properties for this space type. */ + WM_msg_subscribe_rna(mbus, &ptr, prop, &msg_sub_value_area_tag_refresh, __func__); + } } static bool file_main_region_needs_refresh_before_draw(SpaceFile *sfile) @@ -637,10 +656,10 @@ static void file_main_region_draw(const bContext *C, ARegion *region) /* on first read, find active file */ if (params->highlight_file == -1) { wmEvent *event = CTX_wm_window(C)->eventstate; - file_highlight_set(sfile, region, event->x, event->y); + file_highlight_set(sfile, region, event->xy[0], event->xy[1]); } - if (!file_draw_hint_if_invalid(sfile, region)) { + if (!file_draw_hint_if_invalid(C, sfile, region)) { file_draw_list(C, region); } @@ -669,13 +688,13 @@ static void file_operatortypes(void) WM_operatortype_append(FILE_OT_previous); WM_operatortype_append(FILE_OT_next); WM_operatortype_append(FILE_OT_refresh); + WM_operatortype_append(FILE_OT_asset_library_refresh); WM_operatortype_append(FILE_OT_bookmark_add); WM_operatortype_append(FILE_OT_bookmark_delete); WM_operatortype_append(FILE_OT_bookmark_cleanup); WM_operatortype_append(FILE_OT_bookmark_move); WM_operatortype_append(FILE_OT_reset_recent); WM_operatortype_append(FILE_OT_hidedot); - WM_operatortype_append(FILE_OT_associate_blend); WM_operatortype_append(FILE_OT_filenum); WM_operatortype_append(FILE_OT_directory_new); WM_operatortype_append(FILE_OT_delete); @@ -716,14 +735,24 @@ static void file_tools_region_draw(const bContext *C, ARegion *region) ED_region_panels(C, region); } -static void file_tools_region_listener(const wmRegionListenerParams *UNUSED(params)) +static void file_tools_region_listener(const wmRegionListenerParams *listener_params) { + const wmNotifier *wmn = listener_params->notifier; + ARegion *region = listener_params->region; + + switch (wmn->category) { + case NC_SCENE: + if (ELEM(wmn->data, ND_MODE)) { + ED_region_tag_redraw(region); + } + break; + } } -static void file_tool_props_region_listener(const wmRegionListenerParams *params) +static void file_tool_props_region_listener(const wmRegionListenerParams *listener_params) { - const wmNotifier *wmn = params->notifier; - ARegion *region = params->region; + const wmNotifier *wmn = listener_params->notifier; + ARegion *region = listener_params->region; switch (wmn->category) { case NC_ID: @@ -732,6 +761,11 @@ static void file_tool_props_region_listener(const wmRegionListenerParams *params ED_region_tag_redraw(region); } break; + case NC_SCENE: + if (ELEM(wmn->data, ND_MODE)) { + ED_region_tag_redraw(region); + } + break; } } @@ -789,10 +823,10 @@ static void file_execution_region_draw(const bContext *C, ARegion *region) ED_region_panels(C, region); } -static void file_ui_region_listener(const wmRegionListenerParams *params) +static void file_ui_region_listener(const wmRegionListenerParams *listener_params) { - ARegion *region = params->region; - wmNotifier *wmn = params->notifier; + ARegion *region = listener_params->region; + wmNotifier *wmn = listener_params->notifier; /* context changes */ switch (wmn->category) { @@ -806,10 +840,7 @@ static void file_ui_region_listener(const wmRegionListenerParams *params) } } -static bool filepath_drop_poll(bContext *C, - wmDrag *drag, - const wmEvent *UNUSED(event), - const char **UNUSED(r_tooltip)) +static bool filepath_drop_poll(bContext *C, wmDrag *drag, const wmEvent *UNUSED(event)) { if (drag->type == WM_DRAG_PATH) { SpaceFile *sfile = CTX_wm_space_file(C); @@ -830,7 +861,7 @@ static void file_dropboxes(void) { ListBase *lb = WM_dropboxmap_find("Window", SPACE_EMPTY, RGN_TYPE_WINDOW); - WM_dropbox_add(lb, "FILE_OT_filepath_drop", filepath_drop_poll, filepath_drop_copy, NULL); + WM_dropbox_add(lb, "FILE_OT_filepath_drop", filepath_drop_poll, filepath_drop_copy, NULL, NULL); } static int file_space_subtype_get(ScrArea *area) @@ -849,16 +880,17 @@ static void file_space_subtype_item_extend(bContext *UNUSED(C), EnumPropertyItem **item, int *totitem) { - if (U.experimental.use_asset_browser) { - RNA_enum_items_add(item, totitem, rna_enum_space_file_browse_mode_items); - } - else { - RNA_enum_items_add_value( - item, totitem, rna_enum_space_file_browse_mode_items, FILE_BROWSE_MODE_FILES); - } + RNA_enum_items_add(item, totitem, rna_enum_space_file_browse_mode_items); } -static const char *file_context_dir[] = {"active_file", "id", NULL}; +const char *file_context_dir[] = { + "active_file", + "selected_files", + "asset_library_ref", + "selected_asset_files", + "id", + NULL, +}; static int /*eContextResult*/ file_context(const bContext *C, const char *member, @@ -889,6 +921,48 @@ static int /*eContextResult*/ file_context(const bContext *C, CTX_data_pointer_set(result, &screen->id, &RNA_FileSelectEntry, file); return CTX_RESULT_OK; } + if (CTX_data_equals(member, "selected_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); + 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, "asset_library_ref")) { + FileAssetSelectParams *asset_params = ED_fileselect_get_asset_params(sfile); + if (!asset_params) { + return CTX_RESULT_NO_DATA; + } + + CTX_data_pointer_set( + 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) { @@ -992,6 +1066,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"); |