diff --git a/scripts/startup/bl_ui/properties_data_modifier.py b/scripts/startup/bl_ui/properties_data_modifier.py index 196e58c4048..2b840b28bfb 100644 --- a/scripts/startup/bl_ui/properties_data_modifier.py +++ b/scripts/startup/bl_ui/properties_data_modifier.py @@ -52,11 +52,20 @@ class DATA_PT_modifiers(ModifierButtonsPanel, Panel): class OBJECT_MT_modifier_add(ModifierAddMenu, Menu): bl_label = "Add Modifier" + bl_options = {'SEARCH_ON_KEY_PRESS'} def draw(self, context): layout = self.layout ob_type = context.object.type geometry_nodes_supported = ob_type in {'MESH', 'CURVE', 'CURVES', 'FONT', 'SURFACE', 'VOLUME', 'POINTCLOUD'} + + if layout.operator_context == 'EXEC_REGION_WIN': + layout.operator_context = 'INVOKE_REGION_WIN' + layout.operator("WM_OT_search_single_menu", text="Search...", icon='VIEWZOOM').menu_idname = "OBJECT_MT_modifier_add" + layout.separator() + + layout.operator_context = 'EXEC_REGION_WIN' + if geometry_nodes_supported: self.operator_modifier_add(layout, 'NODES') layout.separator() @@ -75,6 +84,7 @@ class OBJECT_MT_modifier_add(ModifierAddMenu, Menu): class OBJECT_MT_modifier_add_edit(ModifierAddMenu, Menu): bl_label = "Edit" + bl_options = {'SEARCH_ON_KEY_PRESS'} def draw(self, context): layout = self.layout @@ -98,6 +108,7 @@ class OBJECT_MT_modifier_add_edit(ModifierAddMenu, Menu): class OBJECT_MT_modifier_add_generate(ModifierAddMenu, Menu): bl_label = "Generate" + bl_options = {'SEARCH_ON_KEY_PRESS'} def draw(self, context): layout = self.layout @@ -139,6 +150,7 @@ class OBJECT_MT_modifier_add_generate(ModifierAddMenu, Menu): class OBJECT_MT_modifier_add_deform(ModifierAddMenu, Menu): bl_label = "Deform" + bl_options = {'SEARCH_ON_KEY_PRESS'} def draw(self, context): layout = self.layout @@ -174,6 +186,7 @@ class OBJECT_MT_modifier_add_deform(ModifierAddMenu, Menu): class OBJECT_MT_modifier_add_physics(ModifierAddMenu, Menu): bl_label = "Physics" + 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 033cd91fc2c..4d98773caca 100644 --- a/scripts/startup/bl_ui/space_node.py +++ b/scripts/startup/bl_ui/space_node.py @@ -226,11 +226,18 @@ 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 layout = self.layout + + if layout.operator_context == 'EXEC_REGION_WIN': + layout.operator_context = 'INVOKE_REGION_WIN' + layout.operator("WM_OT_search_single_menu", text="Search...", icon='VIEWZOOM').menu_idname = "NODE_MT_add" + layout.separator() + layout.operator_context = 'INVOKE_REGION_WIN' snode = context.space_data diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index a7488ccc6ff..34f547c4eb8 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -2275,6 +2275,7 @@ class VIEW3D_MT_select_sculpt_curves(Menu): class VIEW3D_MT_mesh_add(Menu): bl_idname = "VIEW3D_MT_mesh_add" bl_label = "Mesh" + bl_options = {'SEARCH_ON_KEY_PRESS'} def draw(self, _context): layout = self.layout @@ -2301,6 +2302,7 @@ class VIEW3D_MT_mesh_add(Menu): class VIEW3D_MT_curve_add(Menu): bl_idname = "VIEW3D_MT_curve_add" bl_label = "Curve" + bl_options = {'SEARCH_ON_KEY_PRESS'} def draw(self, context): layout = self.layout @@ -2329,6 +2331,7 @@ class VIEW3D_MT_curve_add(Menu): class VIEW3D_MT_surface_add(Menu): bl_idname = "VIEW3D_MT_surface_add" bl_label = "Surface" + bl_options = {'SEARCH_ON_KEY_PRESS'} def draw(self, _context): layout = self.layout @@ -2371,6 +2374,7 @@ class VIEW3D_MT_edit_metaball_context_menu(Menu): class VIEW3D_MT_metaball_add(Menu): bl_idname = "VIEW3D_MT_metaball_add" bl_label = "Metaball" + bl_options = {'SEARCH_ON_KEY_PRESS'} def draw(self, _context): layout = self.layout @@ -2383,6 +2387,7 @@ class TOPBAR_MT_edit_curve_add(Menu): bl_idname = "TOPBAR_MT_edit_curve_add" bl_label = "Add" bl_translation_context = i18n_contexts.operator_default + bl_options = {'SEARCH_ON_KEY_PRESS'} def draw(self, context): layout = self.layout @@ -2400,6 +2405,7 @@ class TOPBAR_MT_edit_curve_add(Menu): class TOPBAR_MT_edit_armature_add(Menu): bl_idname = "TOPBAR_MT_edit_armature_add" bl_label = "Armature" + bl_options = {'SEARCH_ON_KEY_PRESS'} def draw(self, _context): layout = self.layout @@ -2411,6 +2417,7 @@ class TOPBAR_MT_edit_armature_add(Menu): class VIEW3D_MT_armature_add(Menu): bl_idname = "VIEW3D_MT_armature_add" bl_label = "Armature" + bl_options = {'SEARCH_ON_KEY_PRESS'} def draw(self, _context): layout = self.layout @@ -2423,6 +2430,7 @@ class VIEW3D_MT_light_add(Menu): bl_idname = "VIEW3D_MT_light_add" bl_context = i18n_contexts.id_light bl_label = "Light" + bl_options = {'SEARCH_ON_KEY_PRESS'} def draw(self, _context): layout = self.layout @@ -2434,6 +2442,7 @@ class VIEW3D_MT_light_add(Menu): class VIEW3D_MT_lightprobe_add(Menu): bl_idname = "VIEW3D_MT_lightprobe_add" bl_label = "Light Probe" + bl_options = {'SEARCH_ON_KEY_PRESS'} def draw(self, _context): layout = self.layout @@ -2445,6 +2454,7 @@ class VIEW3D_MT_lightprobe_add(Menu): class VIEW3D_MT_camera_add(Menu): bl_idname = "VIEW3D_MT_camera_add" bl_label = "Camera" + bl_options = {'SEARCH_ON_KEY_PRESS'} def draw(self, _context): layout = self.layout @@ -2456,6 +2466,7 @@ class VIEW3D_MT_volume_add(Menu): bl_idname = "VIEW3D_MT_volume_add" bl_label = "Volume" bl_translation_context = i18n_contexts.id_id + bl_options = {'SEARCH_ON_KEY_PRESS'} def draw(self, _context): layout = self.layout @@ -2468,6 +2479,7 @@ class VIEW3D_MT_volume_add(Menu): class VIEW3D_MT_grease_pencil_add(Menu): bl_idname = "VIEW3D_MT_grease_pencil_add" bl_label = "Grease Pencil" + bl_options = {'SEARCH_ON_KEY_PRESS'} def draw(self, _context): layout = self.layout @@ -2479,10 +2491,16 @@ 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 + if layout.operator_context == 'EXEC_REGION_WIN': + layout.operator_context = 'INVOKE_REGION_WIN' + layout.operator("WM_OT_search_single_menu", text="Search...", icon='VIEWZOOM').menu_idname = "VIEW3D_MT_add" + layout.separator() + # NOTE: don't use 'EXEC_SCREEN' or operators won't get the `v3d` context. # NOTE: was `EXEC_AREA`, but this context does not have the `rv3d`, which prevents @@ -2570,6 +2588,7 @@ class VIEW3D_MT_add(Menu): class VIEW3D_MT_image_add(Menu): bl_label = "Add Image" + bl_options = {'SEARCH_ON_KEY_PRESS'} def draw(self, _context): layout = self.layout diff --git a/source/blender/blenkernel/BKE_screen.hh b/source/blender/blenkernel/BKE_screen.hh index 4fe3a1ba27c..a85ea5c9ebf 100644 --- a/source/blender/blenkernel/BKE_screen.hh +++ b/source/blender/blenkernel/BKE_screen.hh @@ -411,6 +411,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/interface/interface_handlers.cc b/source/blender/editors/interface/interface_handlers.cc index 7debac09905..3cddd0941ca 100644 --- a/source/blender/editors/interface/interface_handlers.cc +++ b/source/blender/editors/interface/interface_handlers.cc @@ -10313,9 +10313,9 @@ float ui_block_calc_pie_segment(uiBlock *block, const float event_xy[2]) return len; } -static int ui_handle_menu_letter_press(uiPopupBlockHandle *menu) +static int ui_handle_menu_letter_press_search(uiPopupBlockHandle *menu, const wmEvent *event) { - /* Start menu search on space-bar press if the menu has a name. */ + /* Start menu search if the menu has a name. */ if (menu->menu_idname[0]) { uiAfterFunc *after = ui_afterfunc_new(); wmOperatorType *ot = WM_operatortype_find("WM_OT_search_single_menu", false); @@ -10324,6 +10324,16 @@ static int ui_handle_menu_letter_press(uiPopupBlockHandle *menu) after->opptr = MEM_cnew(__func__); WM_operator_properties_create_ptr(after->opptr, ot); RNA_string_set(after->opptr, "menu_idname", menu->menu_idname); + if (event->type != EVT_SPACEKEY) { + /* Forward all keys except spacebar to the search. */ + const int num_bytes = BLI_str_utf8_size_or_error(event->utf8_buf); + if (num_bytes != -1) { + char buf[sizeof(event->utf8_buf) + 1]; + memcpy(buf, event->utf8_buf, num_bytes); + buf[num_bytes] = '\0'; + RNA_string_set(after->opptr, "initial_query", buf); + } + } menu->menuretval = UI_RETURN_OK; return WM_UI_HANDLER_BREAK; } @@ -10759,19 +10769,37 @@ static int ui_handle_menu_event(bContext *C, case EVT_WKEY: case EVT_XKEY: case EVT_YKEY: - case EVT_ZKEY: { - if (ELEM(event->val, KM_PRESS, KM_DBL_CLICK) && + case EVT_ZKEY: + case EVT_SPACEKEY: { + if (ELEM(event->val, KM_PRESS, KM_DBL_CLICK) && ((event->modifier & (KM_SHIFT | KM_CTRL | KM_OSKEY)) == 0) && /* Only respond to explicit press to avoid the event that opened the menu * activating an item when the key is held. */ (event->flag & WM_EVENT_IS_REPEAT) == 0) { - if (ui_menu_pass_event_to_parent_if_nonactive( - menu, but, level, is_parent_menu, retval)) { + + /* Menu search if spacebar or SearchOnKeyPress. */ + MenuType *mt = WM_menutype_find(menu->menu_idname, false); + if ((mt && bool(mt->flag & MenuTypeFlag::SearchOnKeyPress)) || + event->type == EVT_SPACEKEY) + { + if ((level != 0) && (but == nullptr || !menu->menu_idname[0])) { + /* Search parent if the child is open but not activated or not searchable. */ + menu->menuretval = UI_RETURN_OUT | UI_RETURN_OUT_PARENT; + } + else { + retval = ui_handle_menu_letter_press_search(menu, event); + } break; } - /* Handle accelerator keys. */ + if (ui_menu_pass_event_to_parent_if_nonactive( + menu, but, level, is_parent_menu, retval)) + { + break; + } + + /* 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) { @@ -10780,21 +10808,9 @@ static int ui_handle_menu_event(bContext *C, else { ui_handle_button_activate_by_type(C, region, but); } - break; + return WM_UI_HANDLER_BREAK; } } - retval = WM_UI_HANDLER_BREAK; - } - break; - } - case EVT_SPACEKEY: { - /* Press spacebar to start menu search. */ - if ((level != 0) && (but == nullptr || !menu->menu_idname[0])) { - /* Search parent if the child is open but not activated or not searchable. */ - menu->menuretval = UI_RETURN_OUT | UI_RETURN_OUT_PARENT; - } - else { - retval = ui_handle_menu_letter_press(menu); } } } diff --git a/source/blender/editors/interface/interface_layout.cc b/source/blender/editors/interface/interface_layout.cc index 2a712e52ba1..25c176b3680 100644 --- a/source/blender/editors/interface/interface_layout.cc +++ b/source/blender/editors/interface/interface_layout.cc @@ -5963,6 +5963,9 @@ void UI_menutype_draw(bContext *C, MenuType *mt, uiLayout *layout) } 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. */ ui_block_add_dynamic_listener(block, 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 bae7b2b6585..fd2786be22a 100644 --- a/source/blender/editors/interface/interface_region_menu_popup.cc +++ b/source/blender/editors/interface/interface_region_menu_popup.cc @@ -622,7 +622,10 @@ static void ui_popup_menu_create_from_menutype(bContext *C, STRNCPY(handle->menu_idname, mt->idname); handle->can_refresh = true; - if (mt->idname[0]) { + if (bool(mt->flag & MenuTypeFlag::SearchOnKeyPress)) { + ED_workspace_status_text(C, TIP_("Type to search...")); + } + else if (mt->idname[0]) { ED_workspace_status_text(C, TIP_("Press spacebar to search...")); } } diff --git a/source/blender/makesrna/intern/rna_ui.cc b/source/blender/makesrna/intern/rna_ui.cc index 5a4cf74110f..a53413e323b 100644 --- a/source/blender/makesrna/intern/rna_ui.cc +++ b/source/blender/makesrna/intern/rna_ui.cc @@ -2009,6 +2009,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"); @@ -2073,6 +2082,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); }