diff --git a/source/blender/editors/asset/CMakeLists.txt b/source/blender/editors/asset/CMakeLists.txt index 1ad84a1c0d2..3e387636b46 100644 --- a/source/blender/editors/asset/CMakeLists.txt +++ b/source/blender/editors/asset/CMakeLists.txt @@ -34,6 +34,7 @@ set(SRC intern/asset_shelf.cc intern/asset_shelf_asset_view.cc intern/asset_shelf_catalog_selector.cc + intern/asset_shelf_popup.cc intern/asset_shelf_regiondata.cc intern/asset_shelf_settings.cc intern/asset_temp_id_consumer.cc diff --git a/source/blender/editors/asset/ED_asset_shelf.hh b/source/blender/editors/asset/ED_asset_shelf.hh index a9a3751d0a6..0c4d8817197 100644 --- a/source/blender/editors/asset/ED_asset_shelf.hh +++ b/source/blender/editors/asset/ED_asset_shelf.hh @@ -21,6 +21,7 @@ struct BlendDataReader; struct BlendWriter; struct Main; struct SpaceType; +struct uiBlock; struct RegionPollParams; struct wmWindowManager; @@ -73,15 +74,25 @@ void header_regiontype_register(ARegionType *region_type, const int space_type); void type_register(std::unique_ptr type); void type_unregister(const AssetShelfType &shelf_type); /** - * Poll an asset shelf type for display as a permanent region in a space of a given type (the - * type's #bl_space_type). + * Poll an asset shelf type for display as a popup. Doesn't check for space-type (the type's + * #bl_space_type) since popups should ignore this to allow displaying in any space. + * + * Permanent/non-popup asset shelf regions should use #type_poll_for_space_type() instead. */ -bool type_poll(const bContext &C, const AssetShelfType *shelf_type, const int space_type); - +bool type_poll_for_popup(const bContext &C, const AssetShelfType *shelf_type); AssetShelfType *type_find_from_idname(const StringRef 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); diff --git a/source/blender/editors/asset/intern/asset_shelf.cc b/source/blender/editors/asset/intern/asset_shelf.cc index 685e1b85f84..b95dd3b477c 100644 --- a/source/blender/editors/asset/intern/asset_shelf.cc +++ b/source/blender/editors/asset/intern/asset_shelf.cc @@ -76,14 +76,11 @@ void type_unregister(const AssetShelfType &shelf_type) shelf_types.remove(it - shelf_types.begin()); } -bool type_poll(const bContext &C, const AssetShelfType *shelf_type, const int space_type) +static bool type_poll_no_spacetype_check(const bContext &C, const AssetShelfType *shelf_type) { if (!shelf_type) { return false; } - if (shelf_type->space_type && (space_type != shelf_type->space_type)) { - return false; - } #ifndef NDEBUG const Vector> &shelf_types = static_shelf_types(); @@ -98,6 +95,31 @@ bool type_poll(const bContext &C, const AssetShelfType *shelf_type, const int sp return !shelf_type->poll || shelf_type->poll(&C, shelf_type); } +bool type_poll_for_popup(const bContext &C, const AssetShelfType *shelf_type) +{ + return type_poll_no_spacetype_check(C, shelf_type); +} + +/** + * Poll an asset shelf type for display as a permanent region in a space of a given type (the + * type's #bl_space_type). + * + * Popup asset shelves should use #type_poll_for_popup() instead. + */ +static bool type_poll_for_non_popup(const bContext &C, + const AssetShelfType *shelf_type, + const int space_type) +{ + if (!shelf_type) { + return false; + } + if (shelf_type->space_type && (space_type != shelf_type->space_type)) { + return false; + } + + return type_poll_no_spacetype_check(C, shelf_type); +} + AssetShelfType *type_find_from_idname(const StringRef idname) { for (const std::unique_ptr &shelf_type : static_shelf_types()) { @@ -186,7 +208,8 @@ static AssetShelf *update_active_shelf(const bContext &C, /* Case 1: */ if (shelf_regiondata.active_shelf && - type_poll(C, ensure_shelf_has_type(*shelf_regiondata.active_shelf), space_type)) + type_poll_for_non_popup( + C, ensure_shelf_has_type(*shelf_regiondata.active_shelf), space_type)) { /* 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); @@ -200,7 +223,7 @@ static AssetShelf *update_active_shelf(const bContext &C, continue; } - if (type_poll(C, ensure_shelf_has_type(*shelf), space_type)) { + if (type_poll_for_non_popup(C, ensure_shelf_has_type(*shelf), space_type)) { /* Found a valid previously activated shelf, reactivate it. */ activate_shelf(shelf_regiondata, *shelf); return shelf; @@ -209,7 +232,7 @@ static AssetShelf *update_active_shelf(const bContext &C, /* Case 3: */ for (const std::unique_ptr &shelf_type : static_shelf_types()) { - if (type_poll(C, shelf_type.get(), space_type)) { + if (type_poll_for_non_popup(C, shelf_type.get(), space_type)) { AssetShelf *new_shelf = create_shelf_from_type(*shelf_type); BLI_addhead(&shelf_regiondata.shelves, new_shelf); /* Moves ownership to the regiondata. */ @@ -258,7 +281,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? */ for (const std::unique_ptr &shelf_type : static_shelf_types()) { - if (type_poll(*C, shelf_type.get(), space_link->spacetype)) { + if (type_poll_for_non_popup(*C, shelf_type.get(), space_link->spacetype)) { return true; } } @@ -842,6 +865,8 @@ void type_unlink(const Main &bmain, const AssetShelfType &shelf_type) } } } + + type_popup_unlink(shelf_type); } /** \} */ diff --git a/source/blender/editors/asset/intern/asset_shelf.hh b/source/blender/editors/asset/intern/asset_shelf.hh index 234e345ad55..9270d631861 100644 --- a/source/blender/editors/asset/intern/asset_shelf.hh +++ b/source/blender/editors/asset/intern/asset_shelf.hh @@ -46,6 +46,8 @@ void send_redraw_notifier(const bContext &C); AssetShelfType *ensure_shelf_has_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 * #regiondata_free(). diff --git a/source/blender/editors/asset/intern/asset_shelf_asset_view.cc b/source/blender/editors/asset/intern/asset_shelf_asset_view.cc index 34599b015d9..7536fb9a7bb 100644 --- a/source/blender/editors/asset/intern/asset_shelf_asset_view.cc +++ b/source/blender/editors/asset/intern/asset_shelf_asset_view.cc @@ -44,12 +44,13 @@ class AssetView : public ui::AbstractGridView { * end of the string, for `fnmatch()` to work. */ char search_string[sizeof(AssetShelfSettings::search_string) + 2] = ""; std::optional catalog_filter_ = std::nullopt; + bool is_popup_ = false; friend class AssetViewItem; friend class AssetDragController; public: - AssetView(const AssetLibraryReference &library_ref, const AssetShelf &shelf); + AssetView(const AssetLibraryReference &library_ref, const AssetShelf &shelf, bool is_popup); void build_items() override; bool begin_filtering(const bContext &C) const override; @@ -70,6 +71,7 @@ class AssetViewItem : public ui::PreviewGridItem { void disable_asset_drag(); void build_grid_tile(uiLayout &layout) const override; void build_context_menu(bContext &C, uiLayout &column) const override; + void on_activate(bContext &C) override; std::optional should_be_active() const override; bool is_filtered_visible() const override; @@ -86,8 +88,10 @@ class AssetDragController : public ui::AbstractViewItemDragController { void *create_drag_data() const override; }; -AssetView::AssetView(const AssetLibraryReference &library_ref, const AssetShelf &shelf) - : library_ref_(library_ref), shelf_(shelf) +AssetView::AssetView(const AssetLibraryReference &library_ref, + const AssetShelf &shelf, + const bool is_popup) + : library_ref_(library_ref), shelf_(shelf), is_popup_(is_popup) { if (shelf.settings.search_string[0]) { BLI_strncpy_ensure_pad( @@ -236,6 +240,14 @@ void AssetViewItem::build_context_menu(bContext &C, uiLayout &column) const } } +void AssetViewItem::on_activate(bContext & /*C*/) +{ + const AssetView &asset_view = dynamic_cast(this->get_view()); + if (asset_view.is_popup_) { + UI_popup_menu_close_from_but(reinterpret_cast(this->view_item_button())); + } +} + std::optional AssetViewItem::should_be_active() const { const AssetView &asset_view = dynamic_cast(this->get_view()); @@ -290,7 +302,8 @@ void build_asset_view(uiLayout &layout, BLI_assert(tile_width != 0); BLI_assert(tile_height != 0); - std::unique_ptr asset_view = std::make_unique(library_ref, shelf); + const bool is_popup = region.regiontype == RGN_TYPE_TEMPORARY; + std::unique_ptr asset_view = std::make_unique(library_ref, shelf, is_popup); asset_view->set_catalog_filter(catalog_filter_from_shelf_settings(shelf.settings, *library)); asset_view->set_tile_size(tile_width, tile_height); diff --git a/source/blender/editors/asset/intern/asset_shelf_catalog_selector.cc b/source/blender/editors/asset/intern/asset_shelf_catalog_selector.cc index 9446f75cb2e..4a9c4c6e60a 100644 --- a/source/blender/editors/asset/intern/asset_shelf_catalog_selector.cc +++ b/source/blender/editors/asset/intern/asset_shelf_catalog_selector.cc @@ -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) { - const AssetLibraryReference *library_ref = CTX_wm_asset_library_ref(C); AssetShelf *shelf = active_shelf_from_context(C); if (!shelf) { return; } 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); - - 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); + 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 catalog tree view", diff --git a/source/blender/editors/asset/intern/asset_shelf_popup.cc b/source/blender/editors/asset/intern/asset_shelf_popup.cc new file mode 100644 index 00000000000..c4c989a4a4e --- /dev/null +++ b/source/blender/editors/asset/intern/asset_shelf_popup.cc @@ -0,0 +1,208 @@ +/* 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 popup_shelves; + + ~StaticPopupShelves() + { + for (AssetShelf *shelf : popup_shelves) { + MEM_delete(shelf); + } + } + + static Vector &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) +{ + Vector &popup_shelves = StaticPopupShelves::shelves(); + + for (AssetShelf *shelf : popup_shelves) { + if (STREQ(shelf->idname, shelf_type.idname)) { + if (type_poll_for_popup(*C, ensure_shelf_has_type(*shelf))) { + return shelf; + } + break; + } + } + + if (type_poll_for_popup(*C, &shelf_type)) { + AssetShelf *new_shelf = create_shelf_from_type(shelf_type); + new_shelf->settings.display_flag |= ASSETSHELF_SHOW_NAMES; + 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(RPT_("No applicable assets found"), + ICON_INFO); + item.disable_interaction(); + return; + } + + auto &all_item = this->add_tree_item(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( + 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(*library, shelf)); + + ui::TreeViewBuilder::build_tree_view(*tree_view, layout); +} + +uiBlock *popup_block_create(const bContext *C, ARegion *region, AssetShelfType *shelf_type) +{ + bScreen *screen = CTX_wm_screen(C); + 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); + UI_block_bounds_set_normal(block, 0.3f * U.widget_unit); + UI_block_direction_set(block, UI_DIR_DOWN); + + AssetShelf *shelf = get_shelf_for_popup(C, *shelf_type); + if (!shelf) { + BLI_assert_unreachable(); + return block; + } + + const uiStyle *style = UI_style_get_dpi(); + + const int layout_width = UI_UNIT_X * 40; + const int left_col_width = 10 * UI_UNIT_X; + const int right_col_width = layout_width - left_col_width; + uiLayout *layout = UI_block_layout( + block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 0, 0, layout_width, 0, 0, style); + + PointerRNA library_ref_ptr = RNA_pointer_create( + &screen->id, &RNA_AssetLibraryReference, &shelf->settings.asset_library_reference); + uiLayoutSetContextPointer(layout, "asset_library_reference", &library_ref_ptr); + + uiLayout *row = uiLayoutRow(layout, false); + uiLayout *catalogs_col = uiLayoutColumn(row, false); + uiLayoutSetUnitsX(catalogs_col, left_col_width / UI_UNIT_X); + uiLayoutSetFixedSize(catalogs_col, true); + library_selector_draw(C, catalogs_col, *shelf); + catalog_tree_draw(*catalogs_col, *shelf); + + uiLayout *right_col = uiLayoutColumn(row, false); + uiLayout *sub = uiLayoutRow(right_col, false); + /* Same as file/asset browser header. */ + PointerRNA shelf_ptr = RNA_pointer_create(&screen->id, &RNA_AssetShelf, shelf); + uiItemR(sub, &shelf_ptr, "search_filter", UI_ITEM_R_IMMEDIATE, "", ICON_VIEWZOOM); + + uiLayout *asset_view_col = uiLayoutColumn(right_col, false); + uiLayoutSetUnitsX(asset_view_col, right_col_width / UI_UNIT_X); + uiLayoutSetFixedSize(asset_view_col, true); + build_asset_view(*asset_view_col, shelf->settings.asset_library_reference, *shelf, *C, *region); + + return block; +} + +} // namespace blender::ed::asset::shelf diff --git a/source/blender/editors/include/UI_interface_c.hh b/source/blender/editors/include/UI_interface_c.hh index e09d38f0ee7..7d9bc98d949 100644 --- a/source/blender/editors/include/UI_interface_c.hh +++ b/source/blender/editors/include/UI_interface_c.hh @@ -710,6 +710,18 @@ uiLayout *UI_popup_menu_layout(uiPopupMenu *pup); 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); +/** + * 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. * E.g. WM might need to do this for exiting files correctly. @@ -1496,6 +1508,10 @@ uiBut *uiDefIconMenuBut(uiBlock *block, short height, 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, uiBlockCreateFunc func, void *arg, @@ -2711,6 +2727,13 @@ void uiTemplateAssetView(uiLayout *layout, const char *drag_opname, PointerRNA *r_drag_op_properties); +namespace blender::ui { + +void template_asset_shelf_popover( + uiLayout &layout, const bContext &C, StringRefNull asset_shelf_id, StringRef name, int icon); + +} + void uiTemplateLightLinkingCollection(uiLayout *layout, uiLayout *context_layout, PointerRNA *ptr, diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt index b7184589fdf..ecdb3ff924a 100644 --- a/source/blender/editors/interface/CMakeLists.txt +++ b/source/blender/editors/interface/CMakeLists.txt @@ -5,6 +5,7 @@ set(INC . ../include + ../asset ../../asset_system ../../blenkernel ../../blenloader @@ -64,6 +65,7 @@ set(SRC regions/interface_regions.cc interface_string_search.cc interface_style.cc + templates/interface_template_asset_shelf_popover.cc templates/interface_template_asset_view.cc templates/interface_template_attribute_search.cc templates/interface_template_bone_collection_tree.cc diff --git a/source/blender/editors/interface/interface_handlers.cc b/source/blender/editors/interface/interface_handlers.cc index 275618577b6..9d2fb624998 100644 --- a/source/blender/editors/interface/interface_handlers.cc +++ b/source/blender/editors/interface/interface_handlers.cc @@ -4048,6 +4048,8 @@ static void ui_do_but_textedit( if (changed || (retval == WM_UI_HANDLER_BREAK)) { ED_region_tag_redraw(data->region); + /* In case of popup regions, tag for popup refreshing too (contents may have changed). */ + ED_region_tag_refresh_ui(data->region); } } @@ -11509,6 +11511,8 @@ static int ui_handle_menus_recursive(bContext *C, if (!menu->retvalue) { 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) { ui_mouse_motion_towards_reinit(menu, event->xy); diff --git a/source/blender/editors/interface/interface_intern.hh b/source/blender/editors/interface/interface_intern.hh index 846b1b68619..1c8baabdbf2 100644 --- a/source/blender/editors/interface/interface_intern.hh +++ b/source/blender/editors/interface/interface_intern.hh @@ -984,6 +984,10 @@ uiBlock *ui_popup_block_refresh(bContext *C, ARegion *butregion, 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, ARegion *butregion, uiBut *but, diff --git a/source/blender/editors/interface/regions/interface_region_menu_popup.cc b/source/blender/editors/interface/regions/interface_region_menu_popup.cc index 9e1a658fc0f..8cb725f8a51 100644 --- a/source/blender/editors/interface/regions/interface_region_menu_popup.cc +++ b/source/blender/editors/interface/regions/interface_region_menu_popup.cc @@ -765,4 +765,14 @@ bool UI_popup_block_name_exists(const bScreen *screen, const blender::StringRef 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); +} + /** \} */ diff --git a/source/blender/editors/interface/regions/interface_region_popup.cc b/source/blender/editors/interface/regions/interface_region_popup.cc index f917e5c4e8d..10d2cba13e7 100644 --- a/source/blender/editors/interface/regions/interface_region_popup.cc +++ b/source/blender/editors/interface/regions/interface_region_popup.cc @@ -926,6 +926,10 @@ uiPopupBlockHandle *ui_popup_block_create(bContext *C, CTX_wm_region_popup_set(C, region_popup_prev); } + if (block->flag & UI_BLOCK_KEEP_OPEN) { + handle->can_refresh = true; + } + /* keep centered on window resizing */ if (block->bounds_type == UI_BLOCK_BOUNDS_POPUP_CENTER) { type.listener = ui_block_region_popup_window_listener; diff --git a/source/blender/editors/interface/templates/interface_template_asset_shelf_popover.cc b/source/blender/editors/interface/templates/interface_template_asset_shelf_popover.cc new file mode 100644 index 00000000000..421a5d00547 --- /dev/null +++ b/source/blender/editors/interface/templates/interface_template_asset_shelf_popover.cc @@ -0,0 +1,67 @@ +/* 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" + +namespace blender::ui { + +static uiBlock *asset_shelf_block_fn(bContext *C, ARegion *region, void *arg_shelf_type) +{ + AssetShelfType *shelf_type = reinterpret_cast(arg_shelf_type); + return ed::asset::shelf::popup_block_create(C, region, shelf_type); +} + +void template_asset_shelf_popover(uiLayout &layout, + const bContext &C, + const StringRefNull asset_shelf_id, + const StringRef name, + const BIFIconID icon) +{ + AssetShelfType *shelf_type = ed::asset::shelf::type_find_from_idname(asset_shelf_id); + if (!shelf_type) { + RNA_warning("Asset shelf type not found: %s", asset_shelf_id.c_str()); + return; + } + + const ARegion *region = CTX_wm_region(&C); + const bool use_big_size = !RGN_TYPE_IS_HEADER_ANY(region->regiontype); + const bool use_preview_icon = use_big_size; + const short width = [&]() -> short { + if (use_big_size) { + return UI_UNIT_X * 6; + } + return UI_UNIT_X * (name.is_empty() ? 1.6f : 7); + }(); + 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"); + if (use_preview_icon) { + ui_def_but_icon(but, icon, UI_HAS_ICON | UI_BUT_ICON_PREVIEW); + } + else { + ui_def_but_icon(but, icon, UI_HAS_ICON); + UI_but_drawflag_enable(but, UI_BUT_ICON_LEFT); + } + + if (ed::asset::shelf::type_poll_for_popup(C, shelf_type) == false) { + UI_but_flag_enable(but, UI_BUT_DISABLED); + } +} + +} // namespace blender::ui diff --git a/source/blender/makesdna/DNA_screen_types.h b/source/blender/makesdna/DNA_screen_types.h index f50a9a10625..6d610fc1614 100644 --- a/source/blender/makesdna/DNA_screen_types.h +++ b/source/blender/makesdna/DNA_screen_types.h @@ -833,6 +833,7 @@ typedef struct AssetShelf { AssetShelfSettings settings; + /** Only for the permanent asset shelf regions, not asset shelves in temporary popups. */ short preferred_row_count; char _pad[6]; } AssetShelf; diff --git a/source/blender/makesrna/intern/rna_ui_api.cc b/source/blender/makesrna/intern/rna_ui_api.cc index 86fffd5c54d..b0f260e0834 100644 --- a/source/blender/makesrna/intern/rna_ui_api.cc +++ b/source/blender/makesrna/intern/rna_ui_api.cc @@ -963,6 +963,20 @@ static int rna_ui_get_enum_icon(bContext *C, 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; + } + + blender::ui::template_asset_shelf_popover(*layout, *C, asset_shelf_id, name, icon); +} + #else 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); 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); + + 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