WIP: Brush assets project #106303

Draft
Julian Eisel wants to merge 381 commits from brush-assets-project into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
19 changed files with 475 additions and 43 deletions
Showing only changes of commit 1f83f277dc - Show all commits

View File

@ -235,9 +235,18 @@ class _draw_tool_settings_context_mode:
return False return False
paint = context.tool_settings.sculpt paint = context.tool_settings.sculpt
layout.template_ID_preview(paint, "brush", rows=3, cols=8, hide_buttons=True)
brush = paint.brush brush = paint.brush
brush_name = brush.name if brush else None
preview_icon_id = brush.preview.icon_id if brush and brush.preview else 0
fallback_icon = 'BRUSH_DATA' if not preview_icon_id else 'NONE'
layout.template_asset_shelf_popover(
BrushAssetShelf.get_shelf_name_from_mode(context.object.mode),
name=brush_name,
icon=fallback_icon,
icon_value=preview_icon_id,
)
if brush is None: if brush is None:
return False return False
@ -9023,6 +9032,23 @@ class BrushAssetShelf:
# also activates the item. # also activates the item.
layout.menu_contents("VIEW3D_MT_brush_context_menu") layout.menu_contents("VIEW3D_MT_brush_context_menu")
# Not nice, but needed unfortunately.
@staticmethod
def get_shelf_name_from_mode(obmode):
mode_map = {
'SCULPT': "VIEW3D_AST_brush_sculpt",
'SCULPT_CURVES': "VIEW3D_AST_brush_sculpt_curves",
'VERTEX_PAINT': "VIEW3D_AST_brush_vertex_paint",
'WEIGHT_PAINT': "VIEW3D_AST_brush_weight_paint",
'TEXTURE_PAINT': "VIEW3D_AST_brush_texture_paint",
'PAINT_GPENCIL': "VIEW3D_AST_brush_gpencil_paint",
'PAINT_GREASE_PENCIL': "VIEW3D_AST_brush_grease_pencil_paint",
'SCULPT_GPENCIL': "VIEW3D_AST_brush_gpencil_sculpt",
'VERTEX_GPENCIL': "VIEW3D_AST_brush_gpencil_vertex",
'WEIGHT_GPENCIL': "VIEW3D_AST_brush_gpencil_weight",
}
return mode_map[obmode]
class View3DAssetShelf(BrushAssetShelf): class View3DAssetShelf(BrushAssetShelf):
bl_space_type = "VIEW_3D" bl_space_type = "VIEW_3D"

View File

@ -34,6 +34,7 @@ set(SRC
intern/asset_shelf.cc intern/asset_shelf.cc
intern/asset_shelf_asset_view.cc intern/asset_shelf_asset_view.cc
intern/asset_shelf_catalog_selector.cc intern/asset_shelf_catalog_selector.cc
intern/asset_shelf_popup.cc
intern/asset_shelf_regiondata.cc intern/asset_shelf_regiondata.cc
intern/asset_shelf_settings.cc intern/asset_shelf_settings.cc
intern/asset_temp_id_consumer.cc intern/asset_temp_id_consumer.cc

View File

@ -17,9 +17,16 @@ struct bContextDataResult;
struct BlendDataReader; struct BlendDataReader;
struct BlendWriter; struct BlendWriter;
struct Main; struct Main;
struct SpaceType;
struct uiBlock;
struct RegionPollParams; struct RegionPollParams;
struct wmWindowManager; struct wmWindowManager;
namespace blender {
class StringRef;
class StringRefNull;
} // namespace blender
namespace blender::ed::asset::shelf { namespace blender::ed::asset::shelf {
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
@ -58,6 +65,22 @@ void header_regiontype_register(ARegionType *region_type, const int space_type);
/** \} */ /** \} */
/* -------------------------------------------------------------------- */
/** \name Asset shelf type */
bool type_poll(const bContext &C, const SpaceType &space_type, const AssetShelfType *shelf_type);
AssetShelfType *type_find_from_idname(const SpaceType &space_type, StringRefNull idname);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Asset shelf popup */
uiBlock *popup_block_create(const bContext *C, ARegion *region, AssetShelfType *shelf_type);
void type_popup_unlink(const AssetShelfType &shelf_type);
/** \} */
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
void type_unlink(const Main &bmain, const AssetShelfType &shelf_type); void type_unlink(const Main &bmain, const AssetShelfType &shelf_type);

View File

@ -52,9 +52,7 @@ void send_redraw_notifier(const bContext &C)
/** \name Shelf Type /** \name Shelf Type
* \{ */ * \{ */
static bool asset_shelf_type_poll(const bContext &C, bool type_poll(const bContext &C, const SpaceType &space_type, const AssetShelfType *shelf_type)
const SpaceType &space_type,
const AssetShelfType *shelf_type)
{ {
if (!shelf_type) { if (!shelf_type) {
return false; return false;
@ -71,7 +69,17 @@ static bool asset_shelf_type_poll(const bContext &C,
return !shelf_type->poll || shelf_type->poll(&C, shelf_type); return !shelf_type->poll || shelf_type->poll(&C, shelf_type);
} }
static AssetShelfType *asset_shelf_type_ensure(const SpaceType &space_type, AssetShelf &shelf) AssetShelfType *type_find_from_idname(const SpaceType &space_type, StringRefNull idname)
{
for (const std::unique_ptr<AssetShelfType> &shelf_type : space_type.asset_shelf_types) {
if (idname == shelf_type->idname) {
return shelf_type.get();
}
}
return nullptr;
}
AssetShelfType *type_ensure(const SpaceType &space_type, AssetShelf &shelf)
{ {
if (shelf.type) { if (shelf.type) {
return shelf.type; return shelf.type;
@ -87,7 +95,7 @@ static AssetShelfType *asset_shelf_type_ensure(const SpaceType &space_type, Asse
return nullptr; return nullptr;
} }
static AssetShelf *create_shelf_from_type(AssetShelfType &type) AssetShelf *create_shelf_from_type(AssetShelfType &type)
{ {
AssetShelf *shelf = MEM_new<AssetShelf>(__func__); AssetShelf *shelf = MEM_new<AssetShelf>(__func__);
*shelf = dna::shallow_zero_initialize(); *shelf = dna::shallow_zero_initialize();
@ -149,8 +157,7 @@ static AssetShelf *update_active_shelf(const bContext &C,
/* Case 1: */ /* Case 1: */
if (shelf_regiondata.active_shelf && if (shelf_regiondata.active_shelf &&
asset_shelf_type_poll( type_poll(C, space_type, type_ensure(space_type, *shelf_regiondata.active_shelf)))
C, space_type, asset_shelf_type_ensure(space_type, *shelf_regiondata.active_shelf)))
{ {
/* Not a strong precondition, but if this is wrong something weird might be going on. */ /* Not a strong precondition, but if this is wrong something weird might be going on. */
BLI_assert(shelf_regiondata.active_shelf == shelf_regiondata.shelves.first); BLI_assert(shelf_regiondata.active_shelf == shelf_regiondata.shelves.first);
@ -164,7 +171,7 @@ static AssetShelf *update_active_shelf(const bContext &C,
continue; continue;
} }
if (asset_shelf_type_poll(C, space_type, asset_shelf_type_ensure(space_type, *shelf))) { if (type_poll(C, space_type, type_ensure(space_type, *shelf))) {
/* Found a valid previously activated shelf, reactivate it. */ /* Found a valid previously activated shelf, reactivate it. */
activate_shelf(shelf_regiondata, *shelf); activate_shelf(shelf_regiondata, *shelf);
return shelf; return shelf;
@ -173,7 +180,7 @@ static AssetShelf *update_active_shelf(const bContext &C,
/* Case 3: */ /* Case 3: */
for (const std::unique_ptr<AssetShelfType> &shelf_type : space_type.asset_shelf_types) { for (const std::unique_ptr<AssetShelfType> &shelf_type : space_type.asset_shelf_types) {
if (asset_shelf_type_poll(C, space_type, shelf_type.get())) { if (type_poll(C, space_type, shelf_type.get())) {
AssetShelf *new_shelf = create_shelf_from_type(*shelf_type); AssetShelf *new_shelf = create_shelf_from_type(*shelf_type);
BLI_addhead(&shelf_regiondata.shelves, new_shelf); BLI_addhead(&shelf_regiondata.shelves, new_shelf);
/* Moves ownership to the regiondata. */ /* Moves ownership to the regiondata. */
@ -224,7 +231,7 @@ static bool asset_shelf_space_poll(const bContext *C, const SpaceLink *space_lin
/* Is there any asset shelf type registered that returns true for it's poll? */ /* Is there any asset shelf type registered that returns true for it's poll? */
for (const std::unique_ptr<AssetShelfType> &shelf_type : space_type->asset_shelf_types) { for (const std::unique_ptr<AssetShelfType> &shelf_type : space_type->asset_shelf_types) {
if (asset_shelf_type_poll(*C, *space_type, shelf_type.get())) { if (type_poll(*C, *space_type, shelf_type.get())) {
return true; return true;
} }
} }
@ -483,19 +490,13 @@ void region_draw(const bContext *C, ARegion *region)
void region_on_poll_success(const bContext *C, ARegion *region) void region_on_poll_success(const bContext *C, ARegion *region)
{ {
ScrArea *area = CTX_wm_area(C); RegionAssetShelf *shelf_regiondata = RegionAssetShelf::ensure_from_asset_shelf_region(*region);
BLI_assert(region->regiontype == RGN_TYPE_ASSET_SHELF);
if (!region->regiondata) {
region->regiondata = MEM_cnew<RegionAssetShelf>("RegionAssetShelf");
}
RegionAssetShelf *shelf_regiondata = RegionAssetShelf::get_from_asset_shelf_region(*region);
if (!shelf_regiondata) { if (!shelf_regiondata) {
BLI_assert_unreachable(); BLI_assert_unreachable();
return; return;
} }
ScrArea *area = CTX_wm_area(C);
update_active_shelf( update_active_shelf(
*C, *area->type, *shelf_regiondata, /*on_create=*/[&](AssetShelf &new_shelf) { *C, *area->type, *shelf_regiondata, /*on_create=*/[&](AssetShelf &new_shelf) {
/* Update region visibility (`'DEFAULT_VISIBLE'` option). */ /* Update region visibility (`'DEFAULT_VISIBLE'` option). */
@ -812,6 +813,8 @@ void type_unlink(const Main &bmain, const AssetShelfType &shelf_type)
} }
} }
} }
type_popup_unlink(shelf_type);
} }
/** \} */ /** \} */

View File

@ -9,14 +9,19 @@
#pragma once #pragma once
#include "BLI_function_ref.hh" #include "BLI_function_ref.hh"
#include "BLI_string_ref.hh"
struct ARegion; struct ARegion;
struct ARegionType;
struct AssetLibraryReference; struct AssetLibraryReference;
struct AssetShelf; struct AssetShelf;
struct AssetShelfType;
struct AssetShelfSettings; struct AssetShelfSettings;
struct bContext; struct bContext;
struct BlendDataReader; struct BlendDataReader;
struct BlendWriter; struct BlendWriter;
struct RegionAssetShelf;
struct SpaceType;
struct uiLayout; struct uiLayout;
namespace blender::asset_system { namespace blender::asset_system {
@ -39,6 +44,11 @@ AssetShelf *active_shelf_from_context(const bContext *C);
void send_redraw_notifier(const bContext &C); void send_redraw_notifier(const bContext &C);
AssetShelfType *type_ensure(const SpaceType &space_type, AssetShelf &shelf);
AssetShelf *create_shelf_from_type(AssetShelfType &type);
void library_selector_draw(const bContext *C, uiLayout *layout, AssetShelf &shelf);
/** /**
* Deep-copies \a shelf_regiondata into newly allocated memory. Must be freed using * Deep-copies \a shelf_regiondata into newly allocated memory. Must be freed using
* #regiondata_free(). * #regiondata_free().

View File

@ -45,12 +45,13 @@ class AssetView : public ui::AbstractGridView {
* end of the string, for `fnmatch()` to work. */ * end of the string, for `fnmatch()` to work. */
char search_string[sizeof(AssetShelfSettings::search_string) + 2] = ""; char search_string[sizeof(AssetShelfSettings::search_string) + 2] = "";
std::optional<asset_system::AssetCatalogFilter> catalog_filter_ = std::nullopt; std::optional<asset_system::AssetCatalogFilter> catalog_filter_ = std::nullopt;
bool is_popup_ = false;
friend class AssetViewItem; friend class AssetViewItem;
friend class AssetDragController; friend class AssetDragController;
public: public:
AssetView(const AssetLibraryReference &library_ref, const AssetShelf &shelf); AssetView(const AssetLibraryReference &library_ref, const AssetShelf &shelf, bool is_popup);
~AssetView(); ~AssetView();
void build_items() override; void build_items() override;
@ -72,6 +73,7 @@ class AssetViewItem : public ui::PreviewGridItem {
void disable_asset_drag(); void disable_asset_drag();
void build_grid_tile(uiLayout &layout) const override; void build_grid_tile(uiLayout &layout) const override;
void build_context_menu(bContext &C, uiLayout &column) const override; void build_context_menu(bContext &C, uiLayout &column) const override;
void on_activate(bContext &C) override;
std::optional<bool> should_be_active() const override; std::optional<bool> should_be_active() const override;
bool is_filtered_visible() const override; bool is_filtered_visible() const override;
@ -88,8 +90,10 @@ class AssetDragController : public ui::AbstractViewItemDragController {
void *create_drag_data() const override; void *create_drag_data() const override;
}; };
AssetView::AssetView(const AssetLibraryReference &library_ref, const AssetShelf &shelf) AssetView::AssetView(const AssetLibraryReference &library_ref,
: library_ref_(library_ref), shelf_(shelf) const AssetShelf &shelf,
const bool is_popup)
: library_ref_(library_ref), shelf_(shelf), is_popup_(is_popup)
{ {
if (shelf.settings.search_string[0]) { if (shelf.settings.search_string[0]) {
BLI_strncpy_ensure_pad( BLI_strncpy_ensure_pad(
@ -230,6 +234,14 @@ void AssetViewItem::build_context_menu(bContext &C, uiLayout &column) const
} }
} }
void AssetViewItem::on_activate(bContext & /*C*/)
{
const AssetView &asset_view = dynamic_cast<const AssetView &>(get_view());
if (asset_view.is_popup_) {
UI_popup_menu_close_from_but(reinterpret_cast<uiBut *>(view_item_button()));
}
}
std::optional<bool> AssetViewItem::should_be_active() const std::optional<bool> AssetViewItem::should_be_active() const
{ {
const AssetView &asset_view = dynamic_cast<const AssetView &>(get_view()); const AssetView &asset_view = dynamic_cast<const AssetView &>(get_view());
@ -284,7 +296,8 @@ void build_asset_view(uiLayout &layout,
BLI_assert(tile_width != 0); BLI_assert(tile_width != 0);
BLI_assert(tile_height != 0); BLI_assert(tile_height != 0);
std::unique_ptr asset_view = std::make_unique<AssetView>(library_ref, shelf); const bool is_popup = region.regiontype == RGN_TYPE_TEMPORARY;
std::unique_ptr asset_view = std::make_unique<AssetView>(library_ref, shelf, is_popup);
asset_view->set_catalog_filter(catalog_filter_from_shelf_settings(shelf.settings, *library)); asset_view->set_catalog_filter(catalog_filter_from_shelf_settings(shelf.settings, *library));
asset_view->set_tile_size(tile_width, tile_height); asset_view->set_tile_size(tile_width, tile_height);

View File

@ -174,32 +174,37 @@ void AssetCatalogSelectorTree::update_shelf_settings_from_enabled_catalogs()
}); });
} }
void library_selector_draw(const bContext *C, uiLayout *layout, AssetShelf &shelf)
{
uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT);
PointerRNA shelf_ptr = RNA_pointer_create(&CTX_wm_screen(C)->id, &RNA_AssetShelf, &shelf);
uiLayout *row = uiLayoutRow(layout, true);
uiItemR(row, &shelf_ptr, "asset_library_reference", UI_ITEM_NONE, "", ICON_NONE);
if (shelf.settings.asset_library_reference.type != ASSET_LIBRARY_LOCAL) {
uiItemO(row, "", ICON_FILE_REFRESH, "ASSET_OT_library_refresh");
}
}
static void catalog_selector_panel_draw(const bContext *C, Panel *panel) static void catalog_selector_panel_draw(const bContext *C, Panel *panel)
{ {
const AssetLibraryReference *library_ref = CTX_wm_asset_library_ref(C);
AssetShelf *shelf = active_shelf_from_context(C); AssetShelf *shelf = active_shelf_from_context(C);
if (!shelf) { if (!shelf) {
return; return;
} }
uiLayout *layout = panel->layout; uiLayout *layout = panel->layout;
uiBlock *block = uiLayoutGetBlock(layout);
uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT); library_selector_draw(C, layout, *shelf);
PointerRNA shelf_ptr = RNA_pointer_create(&CTX_wm_screen(C)->id, &RNA_AssetShelf, shelf); asset_system::AssetLibrary *library = list::library_get_once_available(
shelf->settings.asset_library_reference);
uiLayout *row = uiLayoutRow(layout, true);
uiItemR(row, &shelf_ptr, "asset_library_reference", UI_ITEM_NONE, "", ICON_NONE);
if (library_ref->type != ASSET_LIBRARY_LOCAL) {
uiItemO(row, "", ICON_FILE_REFRESH, "ASSET_OT_library_refresh");
}
asset_system::AssetLibrary *library = list::library_get_once_available(*library_ref);
if (!library) { if (!library) {
return; return;
} }
uiBlock *block = uiLayoutGetBlock(layout);
ui::AbstractTreeView *tree_view = UI_block_add_view( ui::AbstractTreeView *tree_view = UI_block_add_view(
*block, *block,
"asset catalog tree view", "asset catalog tree view",

View File

@ -0,0 +1,197 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edasset
*/
#include "asset_shelf.hh"
#include "BKE_screen.hh"
#include "BLT_translation.hh"
#include "UI_interface_c.hh"
#include "UI_tree_view.hh"
#include "ED_asset_filter.hh"
#include "ED_asset_list.hh"
#include "ED_asset_shelf.hh"
#include "RNA_access.hh"
#include "RNA_prototypes.h"
namespace blender::ed::asset::shelf {
class StaticPopupShelves {
public:
Vector<AssetShelf *> popup_shelves;
~StaticPopupShelves()
{
for (AssetShelf *shelf : popup_shelves) {
MEM_delete(shelf);
}
}
static Vector<AssetShelf *> &shelves()
{
static StaticPopupShelves storage;
return storage.popup_shelves;
}
};
void type_popup_unlink(const AssetShelfType &shelf_type)
{
for (AssetShelf *shelf : StaticPopupShelves::shelves()) {
if (shelf->type == &shelf_type) {
shelf->type = nullptr;
}
}
}
static AssetShelf *get_shelf_for_popup(const bContext *C, AssetShelfType &shelf_type)
{
const SpaceType *space_type = BKE_spacetype_from_id(shelf_type.space_type);
Vector<AssetShelf *> &popup_shelves = StaticPopupShelves::shelves();
for (AssetShelf *shelf : popup_shelves) {
if (STREQ(shelf->idname, shelf_type.idname)) {
if (type_poll(*C, *space_type, type_ensure(*space_type, *shelf))) {
return shelf;
}
break;
}
}
if (type_poll(*C, *space_type, &shelf_type)) {
AssetShelf *new_shelf = create_shelf_from_type(shelf_type);
popup_shelves.append(new_shelf);
return new_shelf;
}
return nullptr;
}
class AssetCatalogTreeView : public ui::AbstractTreeView {
AssetShelf &shelf_;
asset_system::AssetCatalogTree catalog_tree_;
public:
AssetCatalogTreeView(const asset_system::AssetLibrary &library, AssetShelf &shelf)
: shelf_(shelf)
{
catalog_tree_ = build_filtered_catalog_tree(
library,
shelf_.settings.asset_library_reference,
[this](const asset_system::AssetRepresentation &asset) {
return (!shelf_.type->asset_poll || shelf_.type->asset_poll(shelf_.type, &asset));
});
}
void build_tree() override
{
if (catalog_tree_.is_empty()) {
auto &item = this->add_tree_item<ui::BasicTreeViewItem>(RPT_("No applicable assets found"),
ICON_INFO);
item.disable_interaction();
return;
}
auto &all_item = this->add_tree_item<ui::BasicTreeViewItem>(IFACE_("All"));
all_item.set_on_activate_fn([this](bContext &C, ui::BasicTreeViewItem &) {
settings_set_all_catalog_active(shelf_.settings);
send_redraw_notifier(C);
});
all_item.set_is_active_fn(
[this]() { return settings_is_all_catalog_active(shelf_.settings); });
all_item.uncollapse_by_default();
catalog_tree_.foreach_root_item([&, this](
const asset_system::AssetCatalogTreeItem &catalog_item) {
ui::BasicTreeViewItem &item = this->build_catalog_items_recursive(all_item, catalog_item);
item.uncollapse_by_default();
});
}
ui::BasicTreeViewItem &build_catalog_items_recursive(
ui::TreeViewOrItem &parent_view_item,
const asset_system::AssetCatalogTreeItem &catalog_item) const
{
ui::BasicTreeViewItem &view_item = parent_view_item.add_tree_item<ui::BasicTreeViewItem>(
catalog_item.get_name());
std::string catalog_path = catalog_item.catalog_path().str();
view_item.set_on_activate_fn([this, catalog_path](bContext &C, ui::BasicTreeViewItem &) {
settings_set_active_catalog(shelf_.settings, catalog_path);
send_redraw_notifier(C);
});
view_item.set_is_active_fn([this, catalog_path]() {
return settings_is_active_catalog(shelf_.settings, catalog_path);
});
catalog_item.foreach_child(
[&view_item, this](const asset_system::AssetCatalogTreeItem &child) {
build_catalog_items_recursive(view_item, child);
});
return view_item;
}
};
static void catalog_tree_draw(uiLayout &layout, AssetShelf &shelf)
{
const asset_system::AssetLibrary *library = list::library_get_once_available(
shelf.settings.asset_library_reference);
if (!library) {
return;
}
uiBlock *block = uiLayoutGetBlock(&layout);
ui::AbstractTreeView *tree_view = UI_block_add_view(
*block,
"asset shelf catalog tree view",
std::make_unique<AssetCatalogTreeView>(*library, shelf));
ui::TreeViewBuilder::build_tree_view(*tree_view, layout);
}
uiBlock *popup_block_create(const bContext *C, ARegion *region, AssetShelfType *shelf_type)
{
uiBlock *block = UI_block_begin(C, region, "_popup", UI_EMBOSS);
UI_block_flag_enable(block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_POPOVER);
UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
AssetShelf *shelf = get_shelf_for_popup(C, *shelf_type);
if (!shelf) {
BLI_assert_unreachable();
return block;
}
const uiStyle *style = UI_style_get_dpi();
const float pad = 0.2f * UI_UNIT_Y; /* UI_MENU_PADDING */
uiLayout *layout = UI_block_layout(
block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, pad, 0, UI_UNIT_X * 40, 0, pad / 2, style);
PointerRNA library_ref_ptr = RNA_pointer_create(
&CTX_wm_screen(C)->id, &RNA_AssetLibraryReference, &shelf->settings.asset_library_reference);
uiLayoutSetContextPointer(layout, "asset_library_reference", &library_ref_ptr);
uiLayout *row = uiLayoutRow(layout, false);
uiLayout *sub = uiLayoutRow(row, false);
uiLayoutSetUnitsX(sub, 10);
uiLayoutSetFixedSize(sub, true);
uiLayout *catalogs_col = uiLayoutColumn(sub, false);
library_selector_draw(C, catalogs_col, *shelf);
catalog_tree_draw(*catalogs_col, *shelf);
uiLayout *asset_view_col = uiLayoutColumn(row, false);
build_asset_view(*asset_view_col, shelf->settings.asset_library_reference, *shelf, *C, *region);
return block;
}
} // namespace blender::ed::asset::shelf

View File

@ -25,6 +25,19 @@ RegionAssetShelf *RegionAssetShelf::get_from_asset_shelf_region(const ARegion &r
return static_cast<RegionAssetShelf *>(region.regiondata); return static_cast<RegionAssetShelf *>(region.regiondata);
} }
RegionAssetShelf *RegionAssetShelf::ensure_from_asset_shelf_region(ARegion &region)
{
if (region.regiontype != RGN_TYPE_ASSET_SHELF) {
/* Should only be called on main asset shelf region. */
BLI_assert_unreachable();
return nullptr;
}
if (!region.regiondata) {
region.regiondata = MEM_cnew<RegionAssetShelf>("RegionAssetShelf");
}
return static_cast<RegionAssetShelf *>(region.regiondata);
}
namespace blender::ed::asset::shelf { namespace blender::ed::asset::shelf {
RegionAssetShelf *regiondata_duplicate(const RegionAssetShelf *shelf_regiondata) RegionAssetShelf *regiondata_duplicate(const RegionAssetShelf *shelf_regiondata)

View File

@ -707,6 +707,18 @@ uiLayout *UI_popup_menu_layout(uiPopupMenu *pup);
void UI_popup_menu_reports(bContext *C, ReportList *reports) ATTR_NONNULL(); void UI_popup_menu_reports(bContext *C, ReportList *reports) ATTR_NONNULL();
int UI_popup_menu_invoke(bContext *C, const char *idname, ReportList *reports) ATTR_NONNULL(1, 2); int UI_popup_menu_invoke(bContext *C, const char *idname, ReportList *reports) ATTR_NONNULL(1, 2);
/**
* If \a block is displayed in a popup menu, tag it for closing.
* \param is_cancel: If set to true, the popup will be closed as being cancelled (e.g. when
* pressing escape) as opposed to being handled successfully.
*/
void UI_popup_menu_close(const uiBlock *block, bool is_cancel = false);
/**
* Version of #UI_popup_menu_close() that can be called on a button contained in a popup menu
* block. Convenience since the block may not be available.
*/
void UI_popup_menu_close_from_but(const uiBut *but, bool is_cancel = false);
/** /**
* Allow setting menu return value from externals. * Allow setting menu return value from externals.
* E.g. WM might need to do this for exiting files correctly. * E.g. WM might need to do this for exiting files correctly.
@ -1488,6 +1500,10 @@ uiBut *uiDefIconMenuBut(uiBlock *block,
short height, short height,
const char *tip); const char *tip);
/**
* Note that \a fun can set the #UI_BLOCK_KEEP_OPEN flag to the block it creates, to allow
* refreshing the popup. That is, redrawing the layout, potentially affecting the popup size.
*/
uiBut *uiDefBlockBut(uiBlock *block, uiBut *uiDefBlockBut(uiBlock *block,
uiBlockCreateFunc func, uiBlockCreateFunc func,
void *arg, void *arg,
@ -2703,6 +2719,9 @@ void uiTemplateAssetView(uiLayout *layout,
const char *drag_opname, const char *drag_opname,
PointerRNA *r_drag_op_properties); PointerRNA *r_drag_op_properties);
void uiTemplateAssetShelfPopover(
uiLayout *layout, bContext *C, const char *asset_shelf_id, const char *name, const int icon);
void uiTemplateLightLinkingCollection(uiLayout *layout, void uiTemplateLightLinkingCollection(uiLayout *layout,
uiLayout *context_layout, uiLayout *context_layout,
PointerRNA *ptr, PointerRNA *ptr,

View File

@ -5,6 +5,7 @@
set(INC set(INC
. .
../include ../include
../asset
../../asset_system ../../asset_system
../../blenkernel ../../blenkernel
../../blenloader ../../blenloader
@ -64,6 +65,7 @@ set(SRC
regions/interface_regions.cc regions/interface_regions.cc
interface_string_search.cc interface_string_search.cc
interface_style.cc interface_style.cc
templates/interface_template_asset_shelf_popover.cc
templates/interface_template_asset_view.cc templates/interface_template_asset_view.cc
templates/interface_template_attribute_search.cc templates/interface_template_attribute_search.cc
templates/interface_template_bone_collection_tree.cc templates/interface_template_bone_collection_tree.cc

View File

@ -11503,6 +11503,8 @@ static int ui_handle_menus_recursive(bContext *C,
if (!menu->retvalue) { if (!menu->retvalue) {
ui_handle_viewlist_items_hover(event, menu->region); ui_handle_viewlist_items_hover(event, menu->region);
} }
/* Handle mouse clicks on overlapping view item button. */
ui_handle_view_item_event(C, event, but, menu->region);
if (do_towards_reinit) { if (do_towards_reinit) {
ui_mouse_motion_towards_reinit(menu, event->xy); ui_mouse_motion_towards_reinit(menu, event->xy);

View File

@ -977,6 +977,10 @@ uiBlock *ui_popup_block_refresh(bContext *C,
ARegion *butregion, ARegion *butregion,
uiBut *but); uiBut *but);
/**
* Note that callbacks can set the #UI_BLOCK_KEEP_OPEN flag to the block it creates, to allow
* refreshing the popup. That is, redrawing the layout, potentially affecting the popup size.
*/
uiPopupBlockHandle *ui_popup_block_create(bContext *C, uiPopupBlockHandle *ui_popup_block_create(bContext *C,
ARegion *butregion, ARegion *butregion,
uiBut *but, uiBut *but,

View File

@ -762,4 +762,14 @@ bool UI_popup_block_name_exists(const bScreen *screen, const blender::StringRef
return false; return false;
} }
void UI_popup_menu_close(const uiBlock *block, const bool is_cancel)
{
UI_popup_menu_retval_set(block, is_cancel ? UI_RETURN_CANCEL : UI_RETURN_OK, true);
}
void UI_popup_menu_close_from_but(const uiBut *but, const bool is_cancel)
{
UI_popup_menu_close(but->block, is_cancel);
}
/** \} */ /** \} */

View File

@ -885,6 +885,10 @@ uiPopupBlockHandle *ui_popup_block_create(bContext *C,
uiBlock *block = ui_popup_block_refresh(C, handle, butregion, but); uiBlock *block = ui_popup_block_refresh(C, handle, butregion, but);
handle = block->handle; handle = block->handle;
if (block->flag & UI_BLOCK_KEEP_OPEN) {
handle->can_refresh = true;
}
/* keep centered on window resizing */ /* keep centered on window resizing */
if (block->bounds_type == UI_BLOCK_BOUNDS_POPUP_CENTER) { if (block->bounds_type == UI_BLOCK_BOUNDS_POPUP_CENTER) {
type.listener = ui_block_region_popup_window_listener; type.listener = ui_block_region_popup_window_listener;

View File

@ -0,0 +1,61 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edinterface
*/
#include "BKE_context.hh"
#include "BKE_screen.hh"
#include "RNA_access.hh"
#include "UI_interface_c.hh"
#include "UI_resources.hh"
#include "interface_intern.hh"
#include "ED_asset_shelf.hh"
using namespace blender;
static uiBlock *asset_shelf_block_fn(bContext *C, ARegion *region, void *arg_shelf_type)
{
AssetShelfType *shelf_type = reinterpret_cast<AssetShelfType *>(arg_shelf_type);
return ed::asset::shelf::popup_block_create(C, region, shelf_type);
}
void uiTemplateAssetShelfPopover(uiLayout *layout,
bContext *C,
const char *asset_shelf_id,
const char *name,
const BIFIconID icon)
{
const ScrArea *area = CTX_wm_area(C);
AssetShelfType *shelf_type = ed::asset::shelf::type_find_from_idname(*area->type,
asset_shelf_id);
if (!shelf_type) {
RNA_warning("Asset shelf type not found: %s", asset_shelf_id);
return;
}
const ARegion *region = CTX_wm_region(C);
const bool use_big_size = !RGN_TYPE_IS_HEADER_ANY(region->regiontype);
const short width = [&]() -> short {
if (use_big_size) {
return UI_UNIT_X * 6;
}
return UI_UNIT_X * (name ? 7 : 1.6f);
}();
const short height = UI_UNIT_Y * (use_big_size ? 6 : 1);
uiBlock *block = uiLayoutGetBlock(layout);
uiBut *but = uiDefBlockBut(
block, asset_shelf_block_fn, shelf_type, name, 0, 0, width, height, "Select an asset");
ui_def_but_icon(but, icon, UI_HAS_ICON);
UI_but_drawflag_enable(but, UI_BUT_ICON_LEFT);
if (ed::asset::shelf::type_poll(*C, *area->type, shelf_type) == false) {
UI_but_flag_enable(but, UI_BUT_DISABLED);
}
}

View File

@ -204,7 +204,6 @@ GridViewItemDropTarget::GridViewItemDropTarget(AbstractGridView &view) : view_(v
* side(s) as well. * side(s) as well.
*/ */
class BuildOnlyVisibleButtonsHelper { class BuildOnlyVisibleButtonsHelper {
const View2D &v2d_;
const AbstractGridView &grid_view_; const AbstractGridView &grid_view_;
const GridViewStyle &style_; const GridViewStyle &style_;
const int cols_per_row_ = 0; const int cols_per_row_ = 0;
@ -221,30 +220,34 @@ class BuildOnlyVisibleButtonsHelper {
void fill_layout_after_visible(uiBlock &block) const; void fill_layout_after_visible(uiBlock &block) const;
private: private:
IndexRange get_visible_range() const; IndexRange get_visible_range(const View2D &v2d) const;
void add_spacer_button(uiBlock &block, int row_count) const; void add_spacer_button(uiBlock &block, int row_count) const;
}; };
BuildOnlyVisibleButtonsHelper::BuildOnlyVisibleButtonsHelper(const View2D &v2d, BuildOnlyVisibleButtonsHelper::BuildOnlyVisibleButtonsHelper(const View2D &v2d,
const AbstractGridView &grid_view, const AbstractGridView &grid_view,
const int cols_per_row) const int cols_per_row)
: v2d_(v2d), grid_view_(grid_view), style_(grid_view.get_style()), cols_per_row_(cols_per_row) : grid_view_(grid_view), style_(grid_view.get_style()), cols_per_row_(cols_per_row)
{ {
visible_items_range_ = this->get_visible_range(); visible_items_range_ = this->get_visible_range(v2d);
} }
IndexRange BuildOnlyVisibleButtonsHelper::get_visible_range() const IndexRange BuildOnlyVisibleButtonsHelper::get_visible_range(const View2D &v2d) const
{ {
if ((v2d.flag & V2D_IS_INIT) == 0) {
return IndexRange(0, grid_view_.get_item_count_filtered());
}
int first_idx_in_view = 0; int first_idx_in_view = 0;
const float scroll_ofs_y = std::abs(v2d_.cur.ymax - v2d_.tot.ymax); const float scroll_ofs_y = std::abs(v2d.cur.ymax - v2d.tot.ymax);
if (!IS_EQF(scroll_ofs_y, 0)) { if (!IS_EQF(scroll_ofs_y, 0)) {
const int scrolled_away_rows = int(scroll_ofs_y) / style_.tile_height; const int scrolled_away_rows = int(scroll_ofs_y) / style_.tile_height;
first_idx_in_view = scrolled_away_rows * cols_per_row_; first_idx_in_view = scrolled_away_rows * cols_per_row_;
} }
const int view_height = BLI_rcti_size_y(&v2d_.mask); const int view_height = BLI_rcti_size_y(&v2d.mask);
const int count_rows_in_view = std::max(view_height / style_.tile_height, 1); const int count_rows_in_view = std::max(view_height / style_.tile_height, 1);
const int max_items_in_view = (count_rows_in_view + 1) * cols_per_row_; const int max_items_in_view = (count_rows_in_view + 1) * cols_per_row_;

View File

@ -833,6 +833,7 @@ typedef struct AssetShelf {
AssetShelfSettings settings; AssetShelfSettings settings;
/** Only for the permanent asset shelf regions, not asset shelves in temporary popups. */
short preferred_row_count; short preferred_row_count;
char _pad[6]; char _pad[6];
} AssetShelf; } AssetShelf;
@ -855,6 +856,8 @@ typedef struct RegionAssetShelf {
AssetShelf *active_shelf; /* Non-owning. */ AssetShelf *active_shelf; /* Non-owning. */
#ifdef __cplusplus #ifdef __cplusplus
static RegionAssetShelf *get_from_asset_shelf_region(const ARegion &region); static RegionAssetShelf *get_from_asset_shelf_region(const ARegion &region);
/** Creates the asset shelf region data if necessary, and returns it. */
static RegionAssetShelf *ensure_from_asset_shelf_region(ARegion &region);
#endif #endif
} RegionAssetShelf; } RegionAssetShelf;

View File

@ -963,6 +963,20 @@ static int rna_ui_get_enum_icon(bContext *C,
return icon; return icon;
} }
void rna_uiTemplateAssetShelfPopover(uiLayout *layout,
bContext *C,
const char *asset_shelf_id,
const char *name,
BIFIconID icon,
int icon_value)
{
if (icon_value && !icon) {
icon = icon_value;
}
uiTemplateAssetShelfPopover(layout, C, asset_shelf_id, name, icon);
}
#else #else
static void api_ui_item_common_heading(FunctionRNA *func) static void api_ui_item_common_heading(FunctionRNA *func)
@ -2287,6 +2301,25 @@ void RNA_api_ui_layout(StructRNA *srna)
RNA_def_function_flag(func, FUNC_USE_CONTEXT); RNA_def_function_flag(func, FUNC_USE_CONTEXT);
parm = RNA_def_pointer(func, "node", "Node", "Node", "Display inputs of this node"); parm = RNA_def_pointer(func, "node", "Node", "Node", "Display inputs of this node");
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR);
func = RNA_def_function(srna, "template_asset_shelf_popover", "rna_uiTemplateAssetShelfPopover");
RNA_def_function_ui_description(func, "Create a button to open an asset shelf in a popover");
RNA_def_function_flag(func, FUNC_USE_CONTEXT);
parm = RNA_def_string(func,
"asset_shelf",
nullptr,
0,
"",
"Identifier of the asset shelf to display (`bl_idname`)");
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
parm = RNA_def_string(
func, "name", nullptr, 0, "", "Optional name to indicate the active asset");
RNA_def_property_clear_flag(parm, PROP_NEVER_NULL);
parm = RNA_def_property(func, "icon", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(parm, rna_enum_icon_items);
RNA_def_property_ui_text(parm, "Icon", "Override automatic icon of the item");
parm = RNA_def_property(func, "icon_value", PROP_INT, PROP_UNSIGNED);
RNA_def_property_ui_text(parm, "Icon Value", "Override automatic icon of the item");
} }
#endif #endif