From 7d80fde033dd7ca2496f6433038dca80a54d9647 Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Thu, 16 May 2024 00:11:47 +0200 Subject: [PATCH] UI: Popup version of the asset shelf Developed as part of the brush assets project, see #106303. No user visible changes at this point. Makes it possible to display asset shelves as popups. These popup asset shelves use static storage for their settings, mainly to remember the active catalog and filter string, while keeping them separate from the permanent asset shelf region. Further, the popup can be displayed in any editor, making asset selectors possible to add anywhere in the UI. When an asset is chosen, an operator passed to the asset shelf as bl_activate_operator is called, with an asset weak-reference to refer to the activated asset stored in the operator properties. Adds UILayout.template_asset_shelf_popover() to insert asset shelf popup buttons, taking an asset shelf idname and some normal UI parameters. --- source/blender/editors/asset/CMakeLists.txt | 1 + .../blender/editors/asset/ED_asset_shelf.hh | 19 +- .../editors/asset/intern/asset_shelf.cc | 41 +++- .../editors/asset/intern/asset_shelf.hh | 2 + .../asset/intern/asset_shelf_asset_view.cc | 21 +- .../intern/asset_shelf_catalog_selector.cc | 29 ++- .../editors/asset/intern/asset_shelf_popup.cc | 208 ++++++++++++++++++ .../blender/editors/include/UI_interface_c.hh | 23 ++ .../blender/editors/interface/CMakeLists.txt | 2 + .../editors/interface/interface_handlers.cc | 4 + .../editors/interface/interface_intern.hh | 4 + .../regions/interface_region_menu_popup.cc | 10 + .../regions/interface_region_popup.cc | 4 + .../interface_template_asset_shelf_popover.cc | 67 ++++++ source/blender/makesdna/DNA_screen_types.h | 1 + source/blender/makesrna/intern/rna_ui_api.cc | 33 +++ 16 files changed, 441 insertions(+), 28 deletions(-) create mode 100644 source/blender/editors/asset/intern/asset_shelf_popup.cc create mode 100644 source/blender/editors/interface/templates/interface_template_asset_shelf_popover.cc 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