Add more menus support to EditorContextMenuPlugin

This commit is contained in:
kobewi 2024-12-18 14:42:10 +01:00
parent fafc07335b
commit ba54a2805a
7 changed files with 93 additions and 3 deletions

View file

@ -85,11 +85,30 @@
<constant name="CONTEXT_SLOT_FILESYSTEM" value="1" enum="ContextMenuSlot">
Context menu of FileSystem dock. [method _popup_menu] and option callback will be called with list of paths of the currently selected files.
</constant>
<constant name="CONTEXT_SLOT_SCRIPT_EDITOR" value="2" enum="ContextMenuSlot">
Context menu of Script editor's script tabs. [method _popup_menu] will be called with the path to the currently edited script, while option callback will receive reference to that script.
</constant>
<constant name="CONTEXT_SLOT_FILESYSTEM_CREATE" value="3" enum="ContextMenuSlot">
The "Create..." submenu of FileSystem dock's context menu. [method _popup_menu] and option callback will be called with list of paths of the currently selected files.
</constant>
<constant name="CONTEXT_SLOT_SCRIPT_EDITOR" value="2" enum="ContextMenuSlot">
Context menu of Scene dock. [method _popup_menu] will be called with the path to the currently edited script, while option callback will receive reference to that script.
<constant name="CONTEXT_SLOT_SCRIPT_EDITOR_CODE" value="4" enum="ContextMenuSlot">
Context menu of Script editor's code editor. [method _popup_menu] will be called with the path to the [CodeEdit] node. You can fetch it using this code:
[codeblock]
func _popup_menu(paths):
var code_edit = Engine.get_main_loop().root.get_node(paths[0]);
[/codeblock]
The option callback will receive reference to that node. You can use [CodeEdit] methods to perform symbol lookups etc.
</constant>
<constant name="CONTEXT_SLOT_SCENE_TABS" value="5" enum="ContextMenuSlot">
Context menu of scene tabs. [method _popup_menu] will be called with the path of the clicked scene, or empty [PackedStringArray] if the menu was opened on empty space. The option callback will receive the path of the clicked scene, or empty [String] if none was clicked.
</constant>
<constant name="CONTEXT_SLOT_2D_EDITOR" value="6" enum="ContextMenuSlot">
Context menu of 2D editor's basic right-click menu. [method _popup_menu] will be called with paths to all [CanvasItem] nodes under the cursor. You can fetch them using this code:
[codeblock]
func _popup_menu(paths):
var canvas_item = Engine.get_main_loop().root.get_node(paths[0]); # Replace 0 with the desired index.
[/codeblock]
The paths array is empty if there weren't any nodes under cursor. The option callback will receive a typed array of [CanvasItem] nodes.
</constant>
</constants>
</class>

View file

@ -36,6 +36,7 @@
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/inspector_dock.h"
#include "editor/plugins/editor_context_menu_plugin.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
@ -195,7 +196,13 @@ void EditorSceneTabs::_update_context_menu() {
scene_tabs_context_menu->add_item(TTR("Close Tabs to the Right"), EditorNode::FILE_CLOSE_RIGHT);
_disable_menu_option_if(EditorNode::FILE_CLOSE_RIGHT, EditorNode::get_editor_data().get_edited_scene_count() == tab_id + 1);
scene_tabs_context_menu->add_item(TTR("Close All Tabs"), EditorNode::FILE_CLOSE_ALL);
const PackedStringArray paths = { EditorNode::get_editor_data().get_scene_path(tab_id) };
EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(scene_tabs_context_menu, EditorContextMenuPlugin::CONTEXT_SLOT_SCENE_TABS, paths);
} else {
EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(scene_tabs_context_menu, EditorContextMenuPlugin::CONTEXT_SLOT_SCENE_TABS, {});
}
last_hovered_tab = tab_id;
}
void EditorSceneTabs::_disable_menu_option_if(int p_option, bool p_condition) {
@ -204,6 +211,12 @@ void EditorSceneTabs::_disable_menu_option_if(int p_option, bool p_condition) {
}
}
void EditorSceneTabs::_custom_menu_option(int p_option) {
if (p_option >= EditorContextMenuPlugin::BASE_ID) {
EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_SCENE_TABS, p_option, last_hovered_tab >= 0 ? EditorNode::get_editor_data().get_scene_path(last_hovered_tab) : String());
}
}
void EditorSceneTabs::update_scene_tabs() {
static bool menu_initialized = false;
tab_preview_panel->hide();
@ -410,6 +423,7 @@ EditorSceneTabs::EditorSceneTabs() {
scene_tabs_context_menu = memnew(PopupMenu);
tabbar_container->add_child(scene_tabs_context_menu);
scene_tabs_context_menu->connect(SceneStringName(id_pressed), callable_mp(EditorNode::get_singleton(), &EditorNode::trigger_menu_option).bind(false));
scene_tabs_context_menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorSceneTabs::_custom_menu_option));
scene_tab_add = memnew(Button);
scene_tab_add->set_flat(true);

View file

@ -57,6 +57,8 @@ class EditorSceneTabs : public MarginContainer {
Panel *tab_preview_panel = nullptr;
TextureRect *tab_preview = nullptr;
int last_hovered_tab = -1;
void _scene_tab_changed(int p_tab);
void _scene_tab_script_edited(int p_tab);
void _scene_tab_closed(int p_tab);
@ -69,6 +71,7 @@ class EditorSceneTabs : public MarginContainer {
void _reposition_active_tab(int p_to_index);
void _update_context_menu();
void _disable_menu_option_if(int p_option, bool p_condition);
void _custom_menu_option(int p_option);
void _tab_preview_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_udata);

View file

@ -43,6 +43,7 @@
#include "editor/gui/editor_toaster.h"
#include "editor/gui/editor_zoom_widget.h"
#include "editor/plugins/animation_player_editor_plugin.h"
#include "editor/plugins/editor_context_menu_plugin.h"
#include "editor/plugins/script_editor_plugin.h"
#include "editor/scene_tree_dock.h"
#include "editor/themes/editor_scale.h"
@ -991,6 +992,19 @@ void CanvasItemEditor::_add_node_pressed(int p_result) {
undo_redo->commit_action();
_reset_create_position();
} break;
default: {
if (p_result >= EditorContextMenuPlugin::BASE_ID) {
TypedArray<Node> nodes;
nodes.resize(selection_results.size());
int i = 0;
for (const _SelectResult &result : selection_results) {
nodes[i] = result.item;
i++;
}
EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_2D_EDITOR, p_result, nodes);
}
}
}
}
@ -2461,6 +2475,21 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) {
}
}
// Context menu plugin receives paths of nodes under cursor. It's a complex operation, so perform it only when necessary.
if (EditorContextMenuPluginManager::get_singleton()->has_plugins_for_slot(EditorContextMenuPlugin::CONTEXT_SLOT_2D_EDITOR)) {
selection_results.clear();
_get_canvas_items_at_pos(transform.affine_inverse().xform(viewport->get_local_mouse_position()), selection_results, true);
PackedStringArray paths;
paths.resize(selection_results.size());
String *paths_write = paths.ptrw();
for (int i = 0; i < paths.size(); i++) {
paths_write[i] = selection_results[i].item->get_path();
}
EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(add_node_menu, EditorContextMenuPlugin::CONTEXT_SLOT_2D_EDITOR, paths);
}
add_node_menu->reset_size();
add_node_menu->set_position(viewport->get_screen_transform().xform(b->get_position()));
add_node_menu->popup();

View file

@ -87,8 +87,11 @@ void EditorContextMenuPlugin::_bind_methods() {
BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCENE_TREE);
BIND_ENUM_CONSTANT(CONTEXT_SLOT_FILESYSTEM);
BIND_ENUM_CONSTANT(CONTEXT_SLOT_FILESYSTEM_CREATE);
BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCRIPT_EDITOR);
BIND_ENUM_CONSTANT(CONTEXT_SLOT_FILESYSTEM_CREATE);
BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCRIPT_EDITOR_CODE);
BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCENE_TABS);
BIND_ENUM_CONSTANT(CONTEXT_SLOT_2D_EDITOR);
}
void EditorContextMenuPluginManager::add_plugin(EditorContextMenuPlugin::ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) {
@ -106,6 +109,15 @@ void EditorContextMenuPluginManager::remove_plugin(const Ref<EditorContextMenuPl
plugin_list.erase(p_plugin);
}
bool EditorContextMenuPluginManager::has_plugins_for_slot(ContextMenuSlot p_slot) {
for (Ref<EditorContextMenuPlugin> &plugin : plugin_list) {
if (plugin->slot == p_slot) {
return true;
}
}
return false;
}
void EditorContextMenuPluginManager::add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector<String> &p_paths) {
bool separator_added = false;
const int icon_size = p_popup->get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));

View file

@ -52,6 +52,9 @@ public:
CONTEXT_SLOT_FILESYSTEM,
CONTEXT_SLOT_SCRIPT_EDITOR,
CONTEXT_SLOT_FILESYSTEM_CREATE,
CONTEXT_SLOT_SCRIPT_EDITOR_CODE,
CONTEXT_SLOT_SCENE_TABS,
CONTEXT_SLOT_2D_EDITOR,
};
inline static constexpr int BASE_ID = 2000;
@ -100,6 +103,7 @@ public:
void add_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin);
void remove_plugin(const Ref<EditorContextMenuPlugin> &p_plugin);
bool has_plugins_for_slot(ContextMenuSlot p_slot);
void add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector<String> &p_paths);
Callable match_custom_shortcut(ContextMenuSlot p_slot, const Ref<InputEvent> &p_event);
bool activate_custom_option(ContextMenuSlot p_slot, int p_option, const Variant &p_arg);

View file

@ -41,6 +41,7 @@
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/gui/editor_toaster.h"
#include "editor/plugins/editor_context_menu_plugin.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/menu_button.h"
#include "scene/gui/rich_text_label.h"
@ -1721,6 +1722,11 @@ void ScriptTextEditor::_edit_option(int p_op) {
_lookup_symbol(text, tx->get_caret_line(0), tx->get_caret_column(0));
}
} break;
default: {
if (p_op >= EditorContextMenuPlugin::BASE_ID) {
EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_SCRIPT_EDITOR_CODE, p_op, tx);
}
}
}
}
@ -2310,6 +2316,9 @@ void ScriptTextEditor::_make_context_menu(bool p_selection, bool p_color, bool p
}
}
const PackedStringArray paths = { code_editor->get_text_editor()->get_path() };
EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(context_menu, EditorContextMenuPlugin::CONTEXT_SLOT_SCRIPT_EDITOR_CODE, paths);
const CodeEdit *tx = code_editor->get_text_editor();
context_menu->set_item_disabled(context_menu->get_item_index(EDIT_UNDO), !tx->has_undo());
context_menu->set_item_disabled(context_menu->get_item_index(EDIT_REDO), !tx->has_redo());