From 7f9d51853c978fffa66b3fdf01dde72251b60f1c Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 6 Sep 2023 18:16:45 +0200 Subject: [PATCH] UI: support searching in menus The basic idea is very simple. Whenever a supported menu is open, one can just start typing and this opens a search that contains all the (nested) menu entries. The main downside is that this collides with accelerator keys. Those are the underlined characters in each menu. For now, we just enable this new searching behavior in a few selected menus: Node Add Menu, View3D Add Menu and Modifier Add Menu. This new functionality can be enabled for a menu by setting `bl_options = {'SEARCH_ON_KEY_PRESS'}` to true in the menu type. The status bar shows `Type to search...` when a menu is opened that supports search. Pull Request: https://projects.blender.org/blender/blender/pulls/110855 --- .../startup/bl_ui/properties_data_modifier.py | 1 + scripts/startup/bl_ui/space_node.py | 12 +--- scripts/startup/bl_ui/space_view3d.py | 1 + source/blender/blenkernel/BKE_screen.h | 4 ++ .../blender/editors/include/UI_interface_c.hh | 14 ++++- source/blender/editors/interface/interface.cc | 9 ++- .../editors/interface/interface_handlers.cc | 60 ++++++++++++++----- .../editors/interface/interface_intern.hh | 3 + .../editors/interface/interface_layout.cc | 5 +- .../interface/interface_region_menu_popup.cc | 11 ++++ .../interface/interface_region_popup.cc | 9 +++ .../interface_template_search_menu.cc | 49 +++++++++------ source/blender/makesrna/intern/rna_ui.cc | 15 +++++ .../windowmanager/intern/wm_operators.cc | 54 +++++++++++++++-- 14 files changed, 196 insertions(+), 51 deletions(-) diff --git a/scripts/startup/bl_ui/properties_data_modifier.py b/scripts/startup/bl_ui/properties_data_modifier.py index 4486ced6d6c..218b927612e 100644 --- a/scripts/startup/bl_ui/properties_data_modifier.py +++ b/scripts/startup/bl_ui/properties_data_modifier.py @@ -29,6 +29,7 @@ class DATA_PT_modifiers(ModifierButtonsPanel, Panel): class OBJECT_MT_modifier_add(Menu): bl_label = "Add Modifier" + bl_options = {'SEARCH_ON_KEY_PRESS'} def draw(self, context): layout = self.layout diff --git a/scripts/startup/bl_ui/space_node.py b/scripts/startup/bl_ui/space_node.py index 8d4155b3595..e13f0255787 100644 --- a/scripts/startup/bl_ui/space_node.py +++ b/scripts/startup/bl_ui/space_node.py @@ -225,6 +225,7 @@ class NODE_MT_add(bpy.types.Menu): bl_space_type = 'NODE_EDITOR' bl_label = "Add" bl_translation_context = i18n_contexts.operator_default + bl_options = {'SEARCH_ON_KEY_PRESS'} def draw(self, context): import nodeitems_utils @@ -234,27 +235,16 @@ class NODE_MT_add(bpy.types.Menu): snode = context.space_data if snode.tree_type == 'GeometryNodeTree': - props = layout.operator("node.add_search", text="Search...", icon='VIEWZOOM') - layout.separator() layout.menu_contents("NODE_MT_geometry_node_add_all") elif snode.tree_type == 'CompositorNodeTree': - props = layout.operator("node.add_search", text="Search...", icon='VIEWZOOM') - layout.separator() layout.menu_contents("NODE_MT_compositor_node_add_all") elif snode.tree_type == 'ShaderNodeTree': - props = layout.operator("node.add_search", text="Search...", icon='VIEWZOOM') - layout.separator() layout.menu_contents("NODE_MT_shader_node_add_all") elif snode.tree_type == 'TextureNodeTree': props = layout.operator("node.add_search", text="Search...", icon='VIEWZOOM') layout.separator() layout.menu_contents("NODE_MT_texture_node_add_all") elif nodeitems_utils.has_node_categories(context): - props = layout.operator("node.add_search", text="Search...", icon='VIEWZOOM') - props.use_transform = True - - layout.separator() - # Actual node sub-menus are defined by draw functions from node categories. nodeitems_utils.draw_node_categories_menu(self, context) diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index cd8bb74f2ee..12f0c616b94 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -2448,6 +2448,7 @@ class VIEW3D_MT_grease_pencil_add(Menu): class VIEW3D_MT_add(Menu): bl_label = "Add" bl_translation_context = i18n_contexts.operator_default + bl_options = {'SEARCH_ON_KEY_PRESS'} def draw(self, context): layout = self.layout diff --git a/source/blender/blenkernel/BKE_screen.h b/source/blender/blenkernel/BKE_screen.h index dda91f24c9c..9d60a9fe199 100644 --- a/source/blender/blenkernel/BKE_screen.h +++ b/source/blender/blenkernel/BKE_screen.h @@ -405,6 +405,10 @@ enum class MenuTypeFlag { * dependent, menu search has to scan it in different contexts. */ ContextDependent = (1 << 0), + /** + * Automatically start searching in the menu when pressing a key. + */ + SearchOnKeyPress = (1 << 1), }; ENUM_OPERATORS(MenuTypeFlag, MenuTypeFlag::ContextDependent) diff --git a/source/blender/editors/include/UI_interface_c.hh b/source/blender/editors/include/UI_interface_c.hh index 3ba1abecc94..f61d1734fe2 100644 --- a/source/blender/editors/include/UI_interface_c.hh +++ b/source/blender/editors/include/UI_interface_c.hh @@ -175,6 +175,8 @@ enum { UI_BLOCK_SEARCH_ONLY = 1 << 25, /** Hack for quick setup (splash screen) to draw text centered. */ UI_BLOCK_QUICK_SETUP = 1 << 26, + /** Don't accelerator keys for the items in the block. */ + UI_BLOCK_NO_ACCELERATOR_KEYS = 1 << 27, }; /** #uiPopupBlockHandle.menuretval */ @@ -247,6 +249,15 @@ enum { UI_BUT_OVERRIDDEN = 1u << 31u, }; +enum { + /** + * This is used when `UI_BUT_ACTIVATE_ON_INIT` is used, which is used to activate e.g. a search + * box as soon as a popup opens. Usually, the text in the search box is selected by default. + * However, sometimes this behavior is not desired, so it can be disabled with this flag. + */ + UI_BUT2_ACTIVATE_ON_INIT_NO_SELECT = 1 << 0, +}; + /** #uiBut.dragflag */ enum { /** By default only the left part of a button triggers dragging. A questionable design to make @@ -887,6 +898,7 @@ bool UI_but_active_drop_color(bContext *C); void UI_but_flag_enable(uiBut *but, int flag); void UI_but_flag_disable(uiBut *but, int flag); bool UI_but_flag_is_set(uiBut *but, int flag); +void UI_but_flag2_enable(uiBut *but, int flag); void UI_but_drawflag_enable(uiBut *but, int flag); void UI_but_drawflag_disable(uiBut *but, int flag); @@ -2404,7 +2416,7 @@ void uiTemplateRunningJobs(uiLayout *layout, bContext *C); void UI_but_func_operator_search(uiBut *but); void uiTemplateOperatorSearch(uiLayout *layout); -void UI_but_func_menu_search(uiBut *but); +void UI_but_func_menu_search(uiBut *but, const char *single_menu_idname = nullptr); void uiTemplateMenuSearch(uiLayout *layout); /** diff --git a/source/blender/editors/interface/interface.cc b/source/blender/editors/interface/interface.cc index 63451deeaed..8958e086550 100644 --- a/source/blender/editors/interface/interface.cc +++ b/source/blender/editors/interface/interface.cc @@ -2001,7 +2001,9 @@ void UI_block_end_ex(const bContext *C, uiBlock *block, const int xy[2], int r_x UI_block_layout_resolve(block, nullptr, nullptr); } ui_block_align_calc(block, CTX_wm_region(C)); - if ((block->flag & UI_BLOCK_LOOP) && (block->flag & UI_BLOCK_NUMSELECT)) { + if ((block->flag & UI_BLOCK_LOOP) && (block->flag & UI_BLOCK_NUMSELECT) && + (block->flag & UI_BLOCK_NO_ACCELERATOR_KEYS) == 0) + { ui_menu_block_set_keyaccels(block); /* could use a different flag to check */ } @@ -5847,6 +5849,11 @@ void UI_but_flag_enable(uiBut *but, int flag) but->flag |= flag; } +void UI_but_flag2_enable(uiBut *but, int flag) +{ + but->flag2 |= flag; +} + void UI_but_flag_disable(uiBut *but, int flag) { but->flag &= ~flag; diff --git a/source/blender/editors/interface/interface_handlers.cc b/source/blender/editors/interface/interface_handlers.cc index e07368d335f..bafa2febca7 100644 --- a/source/blender/editors/interface/interface_handlers.cc +++ b/source/blender/editors/interface/interface_handlers.cc @@ -3480,7 +3480,12 @@ static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data) /* set cursor pos to the end of the text */ but->editstr = data->str; but->pos = len; - but->selsta = 0; + if (bool(but->flag2 & UI_BUT2_ACTIVATE_ON_INIT_NO_SELECT)) { + but->selsta = len; + } + else { + but->selsta = 0; + } but->selend = len; /* Initialize undo history tracking. */ @@ -4316,6 +4321,9 @@ static void ui_block_open_begin(bContext *C, uiBut *but, uiHandleButtonData *dat } else if (menufunc) { data->menu = ui_popup_menu_create(C, data->region, but, menufunc, arg); + if (MenuType *mt = UI_but_menutype_get(but)) { + STRNCPY(data->menu->menu_idname, mt->idname); + } if (but->block->handle) { data->menu->popup = but->block->handle->popup; } @@ -10269,6 +10277,41 @@ float ui_block_calc_pie_segment(uiBlock *block, const float event_xy[2]) return len; } +static int ui_handle_menu_letter_press( + bContext *C, ARegion *region, uiPopupBlockHandle *menu, const wmEvent *event, uiBlock *block) +{ + /* Start menu search on key press if enabled. */ + if (menu->menu_idname[0]) { + MenuType *mt = WM_menutype_find(menu->menu_idname, false); + if (bool(mt->flag & MenuTypeFlag::SearchOnKeyPress)) { + uiAfterFunc *after = ui_afterfunc_new(); + wmOperatorType *ot = WM_operatortype_find("WM_OT_search_single_menu", false); + after->optype = ot; + after->opcontext = WM_OP_INVOKE_DEFAULT; + after->opptr = MEM_cnew(__func__); + WM_operator_properties_create_ptr(after->opptr, ot); + RNA_string_set(after->opptr, "menu_idname", menu->menu_idname); + RNA_string_set(after->opptr, "initial_query", event->utf8_buf); + menu->menuretval = UI_RETURN_OK; + return WM_UI_HANDLER_BREAK; + } + } + + /* Handle accelerator keys that allow "pressing" a menu entry by pressing a single key. */ + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (!(but->flag & UI_BUT_DISABLED) && but->menu_key == event->type) { + if (but->type == UI_BTYPE_BUT) { + UI_but_execute(C, region, but); + } + else { + ui_handle_button_activate_by_type(C, region, but); + } + return WM_UI_HANDLER_BREAK; + } + } + return WM_UI_HANDLER_CONTINUE; +} + static int ui_handle_menu_event(bContext *C, const wmEvent *event, uiPopupBlockHandle *menu, @@ -10709,20 +10752,7 @@ static int ui_handle_menu_event(bContext *C, menu, but, level, is_parent_menu, retval)) { break; } - - for (but = static_cast(block->buttons.first); but; but = but->next) { - if (!(but->flag & UI_BUT_DISABLED) && but->menu_key == event->type) { - if (but->type == UI_BTYPE_BUT) { - UI_but_execute(C, region, but); - } - else { - ui_handle_button_activate_by_type(C, region, but); - } - break; - } - } - - retval = WM_UI_HANDLER_BREAK; + retval = ui_handle_menu_letter_press(C, region, menu, event, block); } break; } diff --git a/source/blender/editors/interface/interface_intern.hh b/source/blender/editors/interface/interface_intern.hh index 5501782e5b9..f52f2d7e237 100644 --- a/source/blender/editors/interface/interface_intern.hh +++ b/source/blender/editors/interface/interface_intern.hh @@ -151,6 +151,7 @@ struct uiBut { /** Pointer back to the layout item holding this button. */ uiLayout *layout = nullptr; int flag = 0; + int flag2 = 0; int drawflag = 0; eButType type = eButType(0); eButPointerType pointype = UI_BUT_POIN_NONE; @@ -848,6 +849,8 @@ struct uiPopupBlockHandle { bool is_grab; int grab_xy_prev[2]; /* #endif */ + + char menu_idname[64]; }; /* -------------------------------------------------------------------- */ diff --git a/source/blender/editors/interface/interface_layout.cc b/source/blender/editors/interface/interface_layout.cc index 68b7b89814f..c3ca040fb0d 100644 --- a/source/blender/editors/interface/interface_layout.cc +++ b/source/blender/editors/interface/interface_layout.cc @@ -5918,9 +5918,12 @@ void UI_menutype_draw(bContext *C, MenuType *mt, uiLayout *layout) printf("%s: opening menu \"%s\"\n", __func__, mt->idname); } + uiBlock *block = uiLayoutGetBlock(layout); + if (bool(mt->flag & MenuTypeFlag::SearchOnKeyPress)) { + UI_block_flag_enable(block, UI_BLOCK_NO_ACCELERATOR_KEYS); + } if (mt->listener) { /* Forward the menu type listener to the block we're drawing in. */ - uiBlock *block = uiLayoutGetBlock(layout); uiBlockDynamicListener *listener = static_cast( MEM_mallocN(sizeof(*listener), __func__)); listener->listener_func = mt->listener; diff --git a/source/blender/editors/interface/interface_region_menu_popup.cc b/source/blender/editors/interface/interface_region_menu_popup.cc index 1d29c2e6cda..650801e8855 100644 --- a/source/blender/editors/interface/interface_region_menu_popup.cc +++ b/source/blender/editors/interface/interface_region_menu_popup.cc @@ -397,6 +397,12 @@ static uiPopupBlockHandle *ui_popup_menu_create( if (but) { pup->slideout = ui_block_is_menu(but->block); pup->but = but; + + if (MenuType *mt = UI_but_menutype_get(but)) { + if (bool(mt->flag & MenuTypeFlag::SearchOnKeyPress)) { + ED_workspace_status_text(C, TIP_("Type to search...")); + } + } } if (!but) { @@ -608,7 +614,12 @@ static void ui_popup_menu_create_from_menutype(bContext *C, ui_item_menutype_func(C, layout, mt); }); + STRNCPY(handle->menu_idname, mt->idname); handle->can_refresh = true; + + if (bool(mt->flag & MenuTypeFlag::SearchOnKeyPress)) { + ED_workspace_status_text(C, TIP_("Type to search...")); + } } int UI_popup_menu_invoke(bContext *C, const char *idname, ReportList *reports) diff --git a/source/blender/editors/interface/interface_region_popup.cc b/source/blender/editors/interface/interface_region_popup.cc index e9ef59b36a4..7a9ac6f01f3 100644 --- a/source/blender/editors/interface/interface_region_popup.cc +++ b/source/blender/editors/interface/interface_region_popup.cc @@ -575,6 +575,11 @@ uiBlock *ui_popup_block_refresh(bContext *C, block = handle_create_func(C, handle, arg); } + /* Don't create accelerator keys if the parent menu does not have them. */ + if (but && but->block->flag & UI_BLOCK_NO_ACCELERATOR_KEYS) { + block->flag |= UI_BLOCK_NO_ACCELERATOR_KEYS; + } + /* callbacks _must_ leave this for us, otherwise we can't call UI_block_update_from_old */ BLI_assert(!block->endblock); @@ -815,6 +820,10 @@ uiPopupBlockHandle *ui_popup_block_create(bContext *C, void ui_popup_block_free(bContext *C, uiPopupBlockHandle *handle) { + /* This disables the status bar text that is set when opening a menu that supports search (see + * #MenuTypeFlag::SearchOnKeyPress). */ + ED_workspace_status_text(C, nullptr); + /* If this popup is created from a popover which does NOT have keep-open flag set, * then close the popover too. We could extend this to other popup types too. */ ARegion *region = handle->popup_create_vars.butregion; diff --git a/source/blender/editors/interface/interface_template_search_menu.cc b/source/blender/editors/interface/interface_template_search_menu.cc index d070c2f5a7a..3743b6914ac 100644 --- a/source/blender/editors/interface/interface_template_search_menu.cc +++ b/source/blender/editors/interface/interface_template_search_menu.cc @@ -428,8 +428,12 @@ static void menu_items_from_all_operators(bContext *C, MenuSearch_Data *data) * - Look up predefined editor-menus. * - Look up key-map items which call menus. */ -static MenuSearch_Data *menu_items_from_ui_create( - bContext *C, wmWindow *win, ScrArea *area_init, ARegion *region_init, bool include_all_areas) +static MenuSearch_Data *menu_items_from_ui_create(bContext *C, + wmWindow *win, + ScrArea *area_init, + ARegion *region_init, + bool include_all_areas, + const char *single_menu_idname) { MemArena *memarena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__); blender::Map menu_display_name_map; @@ -591,11 +595,18 @@ static MenuSearch_Data *menu_items_from_ui_create( region = region_init; } - /* Populate menus from the editors, - * note that we could create a fake header, draw the header and extract the menus - * from the buttons, however this is quite involved and can be avoided as by convention - * each space-type has a single root-menu that headers use. */ - { + if (single_menu_idname) { + if (MenuType *mt = WM_menutype_find(single_menu_idname, false)) { + if (menu_tagged.add(mt)) { + menu_stack.push({mt}); + } + } + } + else { + /* Populate menus from the editors, + * note that we could create a fake header, draw the header and extract the menus + * from the buttons, however this is quite involved and can be avoided as by convention + * each space-type has a single root-menu that headers use. */ const char *idname_array[2] = {nullptr}; int idname_array_len = 0; @@ -798,12 +809,14 @@ static MenuSearch_Data *menu_items_from_ui_create( } UI_block_free(nullptr, block); - /* Add key-map items as a second pass, - * so all menus are accessed from the header & top-bar before key shortcuts are expanded. */ - if (menu_stack.is_empty() && (has_keymap_menu_items == false)) { - has_keymap_menu_items = true; - menu_types_add_from_keymap_items( - C, win, area, region, menu_stack, menu_to_kmi, menu_tagged); + if (single_menu_idname == nullptr) { + /* Add key-map items as a second pass, so all menus are accessed from the header & top-bar + * before key shortcuts are expanded. */ + if (menu_stack.is_empty() && (has_keymap_menu_items == false)) { + has_keymap_menu_items = true; + menu_types_add_from_keymap_items( + C, win, area, region, menu_stack, menu_to_kmi, menu_tagged); + } } } } @@ -887,7 +900,7 @@ static MenuSearch_Data *menu_items_from_ui_create( * - Many operators need options to be set to give useful results, see: #74157. * - User who really prefer to list all operators can use #WM_OT_search_operator. */ - if (U.flag & USER_DEVELOPER_UI) { + if ((U.flag & USER_DEVELOPER_UI) && single_menu_idname == nullptr) { menu_items_from_all_operators(C, data); } @@ -1112,15 +1125,17 @@ static ARegion *ui_search_menu_create_tooltip( /** \name Menu Search Template Public API * \{ */ -void UI_but_func_menu_search(uiBut *but) +void UI_but_func_menu_search(uiBut *but, const char *single_menu_idname) { bContext *C = (bContext *)but->block->evil_C; wmWindow *win = CTX_wm_window(C); ScrArea *area = CTX_wm_area(C); ARegion *region = CTX_wm_region(C); /* When run from top-bar scan all areas in the current window. */ - const bool include_all_areas = (area && (area->spacetype == SPACE_TOPBAR)); - MenuSearch_Data *data = menu_items_from_ui_create(C, win, area, region, include_all_areas); + const bool include_all_areas = (area && (area->spacetype == SPACE_TOPBAR)) && + !single_menu_idname; + MenuSearch_Data *data = menu_items_from_ui_create( + C, win, area, region, include_all_areas, single_menu_idname); UI_but_func_search_set(but, /* Generic callback. */ ui_searchbox_create_menu, diff --git a/source/blender/makesrna/intern/rna_ui.cc b/source/blender/makesrna/intern/rna_ui.cc index ddae5ae6bf7..e53038af73d 100644 --- a/source/blender/makesrna/intern/rna_ui.cc +++ b/source/blender/makesrna/intern/rna_ui.cc @@ -1995,6 +1995,15 @@ static void rna_def_menu(BlenderRNA *brna) PropertyRNA *parm; FunctionRNA *func; + static const EnumPropertyItem menu_flag_items[] = { + {int(MenuTypeFlag::SearchOnKeyPress), + "SEARCH_ON_KEY_PRESS", + 0, + "Search on Key Press", + "Open a menu search when a key pressed while the menu is open"}, + {0, nullptr, 0, nullptr, nullptr}, + }; + srna = RNA_def_struct(brna, "Menu", nullptr); RNA_def_struct_ui_text(srna, "Menu", "Editor menu containing buttons"); RNA_def_struct_sdna(srna, "Menu"); @@ -2059,6 +2068,12 @@ static void rna_def_menu(BlenderRNA *brna) RNA_def_property_string_sdna(prop, nullptr, "type->owner_id"); RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL); + prop = RNA_def_property(srna, "bl_options", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, nullptr, "type->flag"); + RNA_def_property_enum_items(prop, menu_flag_items); + RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL | PROP_ENUM_FLAG); + RNA_def_property_ui_text(prop, "Options", "Options for this menu type"); + RNA_define_verify_sdna(true); } diff --git a/source/blender/windowmanager/intern/wm_operators.cc b/source/blender/windowmanager/intern/wm_operators.cc index d7998d98954..447d540baae 100644 --- a/source/blender/windowmanager/intern/wm_operators.cc +++ b/source/blender/windowmanager/intern/wm_operators.cc @@ -1741,27 +1741,30 @@ static void WM_OT_operator_defaults(wmOperatorType *ot) enum SearchType { SEARCH_TYPE_OPERATOR = 0, SEARCH_TYPE_MENU = 1, + SEARCH_TYPE_SINGLE_MENU = 2, }; struct SearchPopupInit_Data { SearchType search_type; int size[2]; + std::string single_menu_idname; }; +static char g_search_text[256] = ""; + static uiBlock *wm_block_search_menu(bContext *C, ARegion *region, void *userdata) { const SearchPopupInit_Data *init_data = static_cast(userdata); - static char search[256] = ""; uiBlock *block = UI_block_begin(C, region, "_popup", UI_EMBOSS); UI_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_SEARCH_MENU); UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); uiBut *but = uiDefSearchBut(block, - search, + g_search_text, 0, ICON_VIEWZOOM, - sizeof(search), + sizeof(g_search_text), 10, 10, init_data->size[0], @@ -1776,6 +1779,10 @@ static uiBlock *wm_block_search_menu(bContext *C, ARegion *region, void *userdat else if (init_data->search_type == SEARCH_TYPE_MENU) { UI_but_func_menu_search(but); } + else if (init_data->search_type == SEARCH_TYPE_SINGLE_MENU) { + UI_but_func_menu_search(but, init_data->single_menu_idname.c_str()); + UI_but_flag2_enable(but, UI_BUT2_ACTIVATE_ON_INIT_NO_SELECT); + } else { BLI_assert_unreachable(); } @@ -1837,16 +1844,33 @@ static int wm_search_menu_invoke(bContext *C, wmOperator *op, const wmEvent *eve } } - int search_type; + SearchType search_type; if (STREQ(op->type->idname, "WM_OT_search_menu")) { search_type = SEARCH_TYPE_MENU; } + else if (STREQ(op->type->idname, "WM_OT_search_single_menu")) { + search_type = SEARCH_TYPE_SINGLE_MENU; + } else { search_type = SEARCH_TYPE_OPERATOR; } static SearchPopupInit_Data data{}; - data.search_type = SearchType(search_type); + + if (search_type == SEARCH_TYPE_SINGLE_MENU) { + { + char *buffer = RNA_string_get_alloc(op->ptr, "menu_idname", nullptr, 0, nullptr); + data.single_menu_idname = buffer; + MEM_SAFE_FREE(buffer); + } + { + char *buffer = RNA_string_get_alloc(op->ptr, "initial_query", nullptr, 0, nullptr); + STRNCPY(g_search_text, buffer); + MEM_SAFE_FREE(buffer); + } + } + + data.search_type = search_type; data.size[0] = UI_searchbox_size_x() * 2; data.size[1] = UI_searchbox_size_y(); @@ -1877,6 +1901,25 @@ static void WM_OT_search_operator(wmOperatorType *ot) ot->poll = WM_operator_winactive; } +static void WM_OT_search_single_menu(wmOperatorType *ot) +{ + ot->name = "Search Single Menu"; + ot->idname = "WM_OT_search_single_menu"; + ot->description = "Pop-up a search for a menu in current context"; + + ot->invoke = wm_search_menu_invoke; + ot->exec = wm_search_menu_exec; + ot->poll = WM_operator_winactive; + + RNA_def_string(ot->srna, "menu_idname", nullptr, 0, "Menu Name", "Menu to search in"); + RNA_def_string(ot->srna, + "initial_query", + nullptr, + 0, + "Initial Query", + "Query to insert into the search box"); +} + static int wm_call_menu_exec(bContext *C, wmOperator *op) { char idname[BKE_ST_MAXNAME]; @@ -3851,6 +3894,7 @@ void wm_operatortypes_register() WM_operatortype_append(WM_OT_splash_about); WM_operatortype_append(WM_OT_search_menu); WM_operatortype_append(WM_OT_search_operator); + WM_operatortype_append(WM_OT_search_single_menu); WM_operatortype_append(WM_OT_call_menu); WM_operatortype_append(WM_OT_call_menu_pie); WM_operatortype_append(WM_OT_call_panel);