diff --git a/doc/classes/EditorContextMenuPlugin.xml b/doc/classes/EditorContextMenuPlugin.xml
index ee67b8f97f9..62f2845f198 100644
--- a/doc/classes/EditorContextMenuPlugin.xml
+++ b/doc/classes/EditorContextMenuPlugin.xml
@@ -85,11 +85,30 @@
Context menu of FileSystem dock. [method _popup_menu] and option callback will be called with list of paths of the currently selected files.
+
+ 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.
+
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.
-
- 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.
+
+ 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.
+
+
+ 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.
+
+
+ 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.
diff --git a/editor/gui/editor_scene_tabs.cpp b/editor/gui/editor_scene_tabs.cpp
index ea95b3c9fe8..748903a2d46 100644
--- a/editor/gui/editor_scene_tabs.cpp
+++ b/editor/gui/editor_scene_tabs.cpp
@@ -38,6 +38,7 @@
#include "editor/editor_undo_redo_manager.h"
#include "editor/gui/editor_run_bar.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"
@@ -205,7 +206,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) {
@@ -214,6 +221,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();
@@ -420,6 +433,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);
diff --git a/editor/gui/editor_scene_tabs.h b/editor/gui/editor_scene_tabs.h
index ac9e6b8c432..f9b94f34102 100644
--- a/editor/gui/editor_scene_tabs.h
+++ b/editor/gui/editor_scene_tabs.h
@@ -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 &p_preview, const Ref &p_small_preview, const Variant &p_udata);
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 9d54e72b3f7..4256e74619a 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -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 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 &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();
diff --git a/editor/plugins/editor_context_menu_plugin.cpp b/editor/plugins/editor_context_menu_plugin.cpp
index d419872a4d9..7af5029c7b3 100644
--- a/editor/plugins/editor_context_menu_plugin.cpp
+++ b/editor/plugins/editor_context_menu_plugin.cpp
@@ -86,8 +86,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 &p_plugin) {
@@ -105,6 +108,15 @@ void EditorContextMenuPluginManager::remove_plugin(const Ref &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 &p_paths) {
bool separator_added = false;
const int icon_size = p_popup->get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
diff --git a/editor/plugins/editor_context_menu_plugin.h b/editor/plugins/editor_context_menu_plugin.h
index 86c67dedda1..8f323239db8 100644
--- a/editor/plugins/editor_context_menu_plugin.h
+++ b/editor/plugins/editor_context_menu_plugin.h
@@ -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 &p_plugin);
void remove_plugin(const Ref &p_plugin);
+ bool has_plugins_for_slot(ContextMenuSlot p_slot);
void add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector &p_paths);
Callable match_custom_shortcut(ContextMenuSlot p_slot, const Ref &p_event);
bool activate_custom_option(ContextMenuSlot p_slot, int p_option, const Variant &p_arg);
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index 1af0fd692d3..d2b7fc8fc9f 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -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"
@@ -1724,6 +1725,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);
+ }
+ }
}
}
@@ -2313,6 +2319,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());