Browse Source

Add more menus support to EditorContextMenuPlugin

kobewi 8 months ago
parent
commit
ba54a2805a

+ 21 - 2
doc/classes/EditorContextMenuPlugin.xml

@@ -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>

+ 14 - 0
editor/gui/editor_scene_tabs.cpp

@@ -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);

+ 3 - 0
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<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_udata);
 

+ 29 - 0
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<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();

+ 13 - 1
editor/plugins/editor_context_menu_plugin.cpp

@@ -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));

+ 4 - 0
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<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);

+ 9 - 0
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"
@@ -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());