diff options
author | Rémi Verschelde <rverschelde@gmail.com> | 2024-09-11 12:34:53 +0200 |
---|---|---|
committer | Rémi Verschelde <rverschelde@gmail.com> | 2024-09-11 12:34:53 +0200 |
commit | a4e77d94bc36ff3813c72d11b97086c6794c3a58 (patch) | |
tree | fdf30167ae0ea17c5f0a44c69d2442b2d5ea5253 | |
parent | 0d3d4e67f38d661f1ad9bd18c0f9889d44501736 (diff) | |
parent | ecc0ab8061c84811eb729b074fbfd3cfd9f5b9ac (diff) | |
download | redot-engine-a4e77d94bc36ff3813c72d11b97086c6794c3a58.tar.gz |
Merge pull request #96539 from KoBeWi/feat_context_menu_plugin4.x_2-electric_boogaloo
Add EditorContextMenuPluginManager and refactor menu plugins
-rw-r--r-- | doc/classes/EditorContextMenuPlugin.xml | 42 | ||||
-rw-r--r-- | doc/classes/EditorPlugin.xml | 23 | ||||
-rw-r--r-- | editor/editor_data.cpp | 132 | ||||
-rw-r--r-- | editor/editor_data.h | 29 | ||||
-rw-r--r-- | editor/editor_node.cpp | 4 | ||||
-rw-r--r-- | editor/filesystem_dock.cpp | 38 | ||||
-rw-r--r-- | editor/plugins/editor_context_menu_plugin.cpp | 134 | ||||
-rw-r--r-- | editor/plugins/editor_context_menu_plugin.h | 55 | ||||
-rw-r--r-- | editor/plugins/editor_plugin.cpp | 18 | ||||
-rw-r--r-- | editor/plugins/editor_plugin.h | 14 | ||||
-rw-r--r-- | editor/plugins/script_editor_plugin.cpp | 21 | ||||
-rw-r--r-- | editor/scene_tree_dock.cpp | 29 | ||||
-rw-r--r-- | editor/scene_tree_dock.h | 1 |
13 files changed, 288 insertions, 252 deletions
diff --git a/doc/classes/EditorContextMenuPlugin.xml b/doc/classes/EditorContextMenuPlugin.xml index 7eeee3d7fd..71c4ca0f9b 100644 --- a/doc/classes/EditorContextMenuPlugin.xml +++ b/doc/classes/EditorContextMenuPlugin.xml @@ -14,7 +14,7 @@ <return type="void" /> <param index="0" name="paths" type="PackedStringArray" /> <description> - Called when creating a context menu, custom options can be added by using the [method add_context_menu_item] function. + Called when creating a context menu, custom options can be added by using the [method add_context_menu_item] or [method add_context_menu_item_from_shortcut] functions. [param paths] contains currently selected paths (depending on menu), which can be used to conditionally add options. </description> </method> <method name="add_context_menu_item"> @@ -22,14 +22,29 @@ <param index="0" name="name" type="String" /> <param index="1" name="callback" type="Callable" /> <param index="2" name="icon" type="Texture2D" default="null" /> - <param index="3" name="shortcut" type="Shortcut" default="null" /> <description> - Add custom options to the context menu of the currently specified slot. - To trigger a [param shortcut] before the context menu is created, please additionally call the [method add_menu_shortcut] function. + Add custom option to the context menu of the plugin's specified slot. When the option is activated, [param callback] will be called. Callback should take single [Array] argument; array contents depend on context menu slot. [codeblock] func _popup_menu(paths): add_context_menu_item("File Custom options", handle, ICON) [/codeblock] + If you want to assign shortcut to the menu item, use [method add_context_menu_item_from_shortcut] instead. + </description> + </method> + <method name="add_context_menu_item_from_shortcut"> + <return type="void" /> + <param index="0" name="name" type="String" /> + <param index="1" name="shortcut" type="Shortcut" /> + <param index="2" name="icon" type="Texture2D" default="null" /> + <description> + Add custom option to the context menu of the plugin's specified slot. The option will have the [param shortcut] assigned and reuse its callback. The shortcut has to be registered beforehand with [method add_menu_shortcut]. + [codeblock] + func _init(): + add_menu_shortcut(SHORTCUT, handle) + + func _popup_menu(paths): + add_context_menu_item_from_shortcut("File Custom options", SHORTCUT, ICON) + [/codeblock] </description> </method> <method name="add_menu_shortcut"> @@ -37,13 +52,26 @@ <param index="0" name="shortcut" type="Shortcut" /> <param index="1" name="callback" type="Callable" /> <description> - To register the shortcut for the context menu, call this function within the [method Object._init] function, even if the context menu has not been created yet. - Note that this method should only be invoked from [method Object._init]; otherwise, the shortcut will not be registered correctly. + Registers a shortcut associated with the plugin's context menu. This method should be called once (e.g. in plugin's [method Object._init]). [param callback] will be called when user presses the specified [param shortcut] while the menu's context is in effect (e.g. FileSystem dock is focused). Callback should take single [Array] argument; array contents depend on context menu slot. [codeblock] func _init(): - add_menu_shortcut(SHORTCUT, handle); + add_menu_shortcut(SHORTCUT, handle) [/codeblock] </description> </method> </methods> + <constants> + <constant name="CONTEXT_SLOT_SCENE_TREE" value="0" enum="ContextMenuSlot"> + Context menu of Scene dock. [method _popup_menu] will be called with a list of paths to currently selected nodes, while option callback will receive the list of currently selected nodes. + </constant> + <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_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> + </constants> </class> diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index b3191e5378..de49764f0d 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -403,11 +403,11 @@ </method> <method name="add_context_menu_plugin"> <return type="void" /> - <param index="0" name="slot" type="int" enum="EditorPlugin.ContextMenuSlot" /> + <param index="0" name="slot" type="int" enum="EditorContextMenuPlugin.ContextMenuSlot" /> <param index="1" name="plugin" type="EditorContextMenuPlugin" /> <description> - Adds a plugin to the context menu. [param slot] is the position in the context menu where the plugin will be added. - Context menus are supported for three commonly used areas: the file system, scene tree, and editor script list panel. + Adds a plugin to the context menu. [param slot] is the context menu where the plugin will be added. + See [enum EditorContextMenuPlugin.ContextMenuSlot] for available context menus. A plugin instance can belong only to a single context menu slot. </description> </method> <method name="add_control_to_bottom_panel"> @@ -635,10 +635,9 @@ </method> <method name="remove_context_menu_plugin"> <return type="void" /> - <param index="0" name="slot" type="int" enum="EditorPlugin.ContextMenuSlot" /> - <param index="1" name="plugin" type="EditorContextMenuPlugin" /> + <param index="0" name="plugin" type="EditorContextMenuPlugin" /> <description> - Removes a context menu plugin from the specified slot. + Removes the specified context menu plugin. </description> </method> <method name="remove_control_from_bottom_panel"> @@ -891,17 +890,5 @@ <constant name="AFTER_GUI_INPUT_CUSTOM" value="2" enum="AfterGUIInput"> Pass the [InputEvent] to other editor plugins except the main [Node3D] one. This can be used to prevent node selection changes and work with sub-gizmos instead. </constant> - <constant name="CONTEXT_SLOT_SCENE_TREE" value="0" enum="ContextMenuSlot"> - Context menu slot for the SceneTree. - </constant> - <constant name="CONTEXT_SLOT_FILESYSTEM" value="1" enum="ContextMenuSlot"> - Context menu slot for the FileSystem. - </constant> - <constant name="CONTEXT_SLOT_SCRIPT_EDITOR" value="2" enum="ContextMenuSlot"> - Context menu slot for the ScriptEditor file list. - </constant> - <constant name="CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE" value="3" enum="ContextMenuSlot"> - Context menu slot for the FileSystem create submenu. - </constant> </constants> </class> diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp index e5caa6a352..ee16c61c89 100644 --- a/editor/editor_data.cpp +++ b/editor/editor_data.cpp @@ -522,138 +522,6 @@ EditorPlugin *EditorData::get_extension_editor_plugin(const StringName &p_class_ return plugin == nullptr ? nullptr : *plugin; } -void EditorData::add_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) { - ContextMenu cm; - cm.p_slot = p_slot; - cm.plugin = p_plugin; - p_plugin->start_idx = context_menu_plugins.size() * EditorContextMenuPlugin::MAX_ITEMS; - context_menu_plugins.push_back(cm); -} - -void EditorData::remove_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) { - for (int i = context_menu_plugins.size() - 1; i > -1; i--) { - if (context_menu_plugins[i].p_slot == p_slot && context_menu_plugins[i].plugin == p_plugin) { - context_menu_plugins.remove_at(i); - } - } -} - -int EditorData::match_context_menu_shortcut(ContextMenuSlot p_slot, const Ref<InputEvent> &p_event) { - for (ContextMenu &cm : context_menu_plugins) { - if (cm.p_slot != p_slot) { - continue; - } - HashMap<Ref<Shortcut>, Callable> &cms = cm.plugin->context_menu_shortcuts; - int shortcut_idx = 0; - for (KeyValue<Ref<Shortcut>, Callable> &E : cms) { - const Ref<Shortcut> &p_shortcut = E.key; - if (p_shortcut->matches_event(p_event)) { - return EditorData::CONTEXT_MENU_ITEM_ID_BASE + cm.plugin->start_idx + shortcut_idx; - } - shortcut_idx++; - } - } - return 0; -} - -void EditorData::add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector<String> &p_paths) { - bool add_separator = false; - - for (ContextMenu &cm : context_menu_plugins) { - if (cm.p_slot != p_slot) { - continue; - } - cm.plugin->clear_context_menu_items(); - cm.plugin->add_options(p_paths); - HashMap<String, EditorContextMenuPlugin::ContextMenuItem> &items = cm.plugin->context_menu_items; - if (items.size() > 0 && !add_separator) { - add_separator = true; - p_popup->add_separator(); - } - for (KeyValue<String, EditorContextMenuPlugin::ContextMenuItem> &E : items) { - EditorContextMenuPlugin::ContextMenuItem &item = E.value; - - if (item.icon.is_valid()) { - p_popup->add_icon_item(item.icon, item.item_name, item.idx); - const int icon_size = p_popup->get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor)); - p_popup->set_item_icon_max_width(-1, icon_size); - } else { - p_popup->add_item(item.item_name, item.idx); - } - if (item.shortcut.is_valid()) { - p_popup->set_item_shortcut(-1, item.shortcut, true); - } - } - } -} - -template <typename T> -void EditorData::invoke_plugin_callback(ContextMenuSlot p_slot, int p_option, const T &p_arg) { - Variant arg = p_arg; - Variant *argptr = &arg; - - for (int i = 0; i < context_menu_plugins.size(); i++) { - if (context_menu_plugins[i].p_slot != p_slot || context_menu_plugins[i].plugin.is_null()) { - continue; - } - Ref<EditorContextMenuPlugin> plugin = context_menu_plugins[i].plugin; - - // Shortcut callback. - int shortcut_idx = 0; - int shortcut_base_idx = EditorData::CONTEXT_MENU_ITEM_ID_BASE + plugin->start_idx; - for (KeyValue<Ref<Shortcut>, Callable> &E : plugin->context_menu_shortcuts) { - if (shortcut_base_idx + shortcut_idx == p_option) { - const Callable &callable = E.value; - Callable::CallError ce; - Variant result; - callable.callp((const Variant **)&argptr, 1, result, ce); - } - shortcut_idx++; - } - if (p_option < shortcut_base_idx + shortcut_idx) { - return; - } - - HashMap<String, EditorContextMenuPlugin::ContextMenuItem> &items = plugin->context_menu_items; - for (KeyValue<String, EditorContextMenuPlugin::ContextMenuItem> &E : items) { - EditorContextMenuPlugin::ContextMenuItem &item = E.value; - - if (p_option != item.idx || !item.callable.is_valid()) { - continue; - } - - Callable::CallError ce; - Variant result; - item.callable.callp((const Variant **)&argptr, 1, result, ce); - - if (ce.error != Callable::CallError::CALL_OK) { - String err = Variant::get_callable_error_text(item.callable, nullptr, 0, ce); - ERR_PRINT("Error calling function from context menu: " + err); - } - } - } - // Invoke submenu items. - if (p_slot == CONTEXT_SLOT_FILESYSTEM) { - invoke_plugin_callback(CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE, p_option, p_arg); - } -} - -void EditorData::filesystem_options_pressed(ContextMenuSlot p_slot, int p_option, const Vector<String> &p_selected) { - invoke_plugin_callback(p_slot, p_option, p_selected); -} - -void EditorData::scene_tree_options_pressed(ContextMenuSlot p_slot, int p_option, const List<Node *> &p_selected) { - TypedArray<Node> nodes; - for (Node *selected : p_selected) { - nodes.append(selected); - } - invoke_plugin_callback(p_slot, p_option, nodes); -} - -void EditorData::script_editor_options_pressed(ContextMenuSlot p_slot, int p_option, const Ref<Resource> &p_script) { - invoke_plugin_callback(p_slot, p_option, p_script); -} - void EditorData::add_custom_type(const String &p_type, const String &p_inherits, const Ref<Script> &p_script, const Ref<Texture2D> &p_icon) { ERR_FAIL_COND_MSG(p_script.is_null(), "It's not a reference to a valid Script object."); CustomType ct; diff --git a/editor/editor_data.h b/editor/editor_data.h index 754d44c479..5b49304c73 100644 --- a/editor/editor_data.h +++ b/editor/editor_data.h @@ -37,7 +37,6 @@ class ConfigFile; class EditorPlugin; class EditorUndoRedoManager; -class EditorContextMenuPlugin; class PopupMenu; /** @@ -125,22 +124,6 @@ public: uint64_t last_checked_version = 0; }; - enum ContextMenuSlot { - CONTEXT_SLOT_SCENE_TREE, - CONTEXT_SLOT_FILESYSTEM, - CONTEXT_SLOT_SCRIPT_EDITOR, - CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE, - }; - - inline static constexpr int CONTEXT_MENU_ITEM_ID_BASE = 1000; - - struct ContextMenu { - int p_slot; - Ref<EditorContextMenuPlugin> plugin; - }; - - Vector<ContextMenu> context_menu_plugins; - private: Vector<EditorPlugin *> editor_plugins; HashMap<StringName, EditorPlugin *> extension_editor_plugins; @@ -195,18 +178,6 @@ public: bool has_extension_editor_plugin(const StringName &p_class_name); EditorPlugin *get_extension_editor_plugin(const StringName &p_class_name); - // Context menu plugin. - void add_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin); - void remove_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin); - int match_context_menu_shortcut(ContextMenuSlot p_slot, const Ref<InputEvent> &p_event); - - void add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector<String> &p_paths); - void filesystem_options_pressed(ContextMenuSlot p_slot, int p_option, const Vector<String> &p_selected); - void scene_tree_options_pressed(ContextMenuSlot p_slot, int p_option, const List<Node *> &p_selected); - void script_editor_options_pressed(ContextMenuSlot p_slot, int p_option, const Ref<Resource> &p_script); - template <typename T> - void invoke_plugin_callback(ContextMenuSlot p_slot, int p_option, const T &p_arg); - void add_undo_redo_inspector_hook_callback(Callable p_callable); // Callbacks should have this signature: void (Object* undo_redo, Object *modified_object, String property, Variant new_value) void remove_undo_redo_inspector_hook_callback(Callable p_callable); const Vector<Callable> get_undo_redo_inspector_hook_callback(); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 56dbfab97c..363d07008a 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -45,6 +45,7 @@ #include "core/string/translation_server.h" #include "core/version.h" #include "editor/editor_string_names.h" +#include "editor/plugins/editor_context_menu_plugin.h" #include "main/main.h" #include "scene/3d/bone_attachment_3d.h" #include "scene/animation/animation_tree.h" @@ -6784,6 +6785,8 @@ EditorNode::EditorNode() { EditorFileSystem *efs = memnew(EditorFileSystem); add_child(efs); + EditorContextMenuPluginManager::create(); + // Used for previews. FileDialog::get_icon_func = _file_dialog_get_icon; FileDialog::register_func = _file_dialog_register; @@ -7784,6 +7787,7 @@ EditorNode::~EditorNode() { EditorInspector::cleanup_plugins(); EditorTranslationParser::get_singleton()->clean_parsers(); ResourceImporterScene::clean_up_importer_plugins(); + EditorContextMenuPluginManager::cleanup(); remove_print_handler(&print_handler); EditorHelp::cleanup_doc(); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index f7e81b329c..68d74236bb 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -2557,9 +2557,12 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected String dir = ProjectSettings::get_singleton()->globalize_path(fpath); ScriptEditor::get_singleton()->open_text_file_create_dialog(dir); } break; - default: - EditorNode::get_editor_data().filesystem_options_pressed(EditorData::CONTEXT_SLOT_FILESYSTEM, p_option, p_selected); - break; + + default: { + if (!EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM, p_option, p_selected)) { + EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM_CREATE, p_option, p_selected); + } + } } } @@ -3183,7 +3186,7 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, const Vect new_menu->add_icon_item(get_editor_theme_icon(SNAME("Object")), TTR("Resource..."), FILE_NEW_RESOURCE); new_menu->add_icon_item(get_editor_theme_icon(SNAME("TextFile")), TTR("TextFile..."), FILE_NEW_TEXTFILE); - EditorNode::get_editor_data().add_options_from_plugins(new_menu, EditorData::CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE, p_paths); + EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(new_menu, EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM_CREATE, p_paths); p_popup->add_separator(); } @@ -3323,8 +3326,7 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, const Vect current_path = fpath; } - - EditorNode::get_editor_data().add_options_from_plugins(p_popup, EditorData::CONTEXT_SLOT_FILESYSTEM, p_paths); + EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(p_popup, EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM, p_paths); } void FileSystemDock::_tree_rmb_select(const Vector2 &p_pos, MouseButton p_button) { @@ -3559,11 +3561,16 @@ void FileSystemDock::_tree_gui_input(Ref<InputEvent> p_event) { } else if (ED_IS_SHORTCUT("editor/open_search", p_event)) { focus_on_filter(); } else { - int match_option = EditorNode::get_editor_data().match_context_menu_shortcut(EditorData::CONTEXT_SLOT_FILESYSTEM, p_event); - if (match_option) { - _tree_rmb_option(match_option); + Callable custom_callback = EditorContextMenuPluginManager::get_singleton()->match_custom_shortcut(EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM, p_event); + if (!custom_callback.is_valid()) { + custom_callback = EditorContextMenuPluginManager::get_singleton()->match_custom_shortcut(EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM_CREATE, p_event); + } + + if (custom_callback.is_valid()) { + EditorContextMenuPluginManager::get_singleton()->invoke_callback(custom_callback, _tree_get_selected(false)); + } else { + return; } - return; } accept_event(); @@ -3631,7 +3638,16 @@ void FileSystemDock::_file_list_gui_input(Ref<InputEvent> p_event) { } else if (ED_IS_SHORTCUT("editor/open_search", p_event)) { focus_on_filter(); } else { - return; + Callable custom_callback = EditorContextMenuPluginManager::get_singleton()->match_custom_shortcut(EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM, p_event); + if (!custom_callback.is_valid()) { + custom_callback = EditorContextMenuPluginManager::get_singleton()->match_custom_shortcut(EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM_CREATE, p_event); + } + + if (custom_callback.is_valid()) { + EditorContextMenuPluginManager::get_singleton()->invoke_callback(custom_callback, files->get_selected_items()); + } else { + return; + } } accept_event(); diff --git a/editor/plugins/editor_context_menu_plugin.cpp b/editor/plugins/editor_context_menu_plugin.cpp index 86d3c0a896..0648327fab 100644 --- a/editor/plugins/editor_context_menu_plugin.cpp +++ b/editor/plugins/editor_context_menu_plugin.cpp @@ -32,9 +32,11 @@ #include "core/input/shortcut.h" #include "editor/editor_node.h" +#include "editor/editor_string_names.h" +#include "scene/gui/popup_menu.h" #include "scene/resources/texture.h" -void EditorContextMenuPlugin::add_options(const Vector<String> &p_paths) { +void EditorContextMenuPlugin::get_options(const Vector<String> &p_paths) { GDVIRTUAL_CALL(_popup_menu, p_paths); } @@ -42,24 +44,142 @@ void EditorContextMenuPlugin::add_menu_shortcut(const Ref<Shortcut> &p_shortcut, context_menu_shortcuts.insert(p_shortcut, p_callable); } -void EditorContextMenuPlugin::add_context_menu_item(const String &p_name, const Callable &p_callable, const Ref<Texture2D> &p_texture, const Ref<Shortcut> &p_shortcut) { +void EditorContextMenuPlugin::add_context_menu_item(const String &p_name, const Callable &p_callable, const Ref<Texture2D> &p_texture) { ERR_FAIL_COND_MSG(context_menu_items.has(p_name), "Context menu item already registered."); ERR_FAIL_COND_MSG(context_menu_items.size() == MAX_ITEMS, "Maximum number of context menu items reached."); + ContextMenuItem item; item.item_name = p_name; item.callable = p_callable; item.icon = p_texture; - item.shortcut = p_shortcut; - item.idx = EditorData::CONTEXT_MENU_ITEM_ID_BASE + start_idx + context_menu_shortcuts.size() + context_menu_items.size(); context_menu_items.insert(p_name, item); } -void EditorContextMenuPlugin::clear_context_menu_items() { - context_menu_items.clear(); +void EditorContextMenuPlugin::add_context_menu_item_from_shortcut(const String &p_name, const Ref<Shortcut> &p_shortcut, const Ref<Texture2D> &p_texture) { + Callable *callback = context_menu_shortcuts.getptr(p_shortcut); + ERR_FAIL_NULL_MSG(callback, "Shortcut not registered. Use add_menu_shortcut() first."); + + ContextMenuItem item; + item.item_name = p_name; + item.callable = *callback; + item.icon = p_texture; + item.shortcut = p_shortcut; + context_menu_items.insert(p_name, item); } void EditorContextMenuPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("add_menu_shortcut", "shortcut", "callback"), &EditorContextMenuPlugin::add_menu_shortcut); - ClassDB::bind_method(D_METHOD("add_context_menu_item", "name", "callback", "icon", "shortcut"), &EditorContextMenuPlugin::add_context_menu_item, DEFVAL(Ref<Texture2D>()), DEFVAL(Ref<Shortcut>())); + ClassDB::bind_method(D_METHOD("add_context_menu_item", "name", "callback", "icon"), &EditorContextMenuPlugin::add_context_menu_item, DEFVAL(Ref<Texture2D>())); + ClassDB::bind_method(D_METHOD("add_context_menu_item_from_shortcut", "name", "shortcut", "icon"), &EditorContextMenuPlugin::add_context_menu_item_from_shortcut, DEFVAL(Ref<Texture2D>())); + GDVIRTUAL_BIND(_popup_menu, "paths"); + + 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); +} + +void EditorContextMenuPluginManager::add_plugin(EditorContextMenuPlugin::ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) { + ERR_FAIL_COND(p_plugin.is_null()); + ERR_FAIL_COND(plugin_list.has(p_plugin)); + + p_plugin->slot = p_slot; + plugin_list.push_back(p_plugin); +} + +void EditorContextMenuPluginManager::remove_plugin(const Ref<EditorContextMenuPlugin> &p_plugin) { + ERR_FAIL_COND(p_plugin.is_null()); + ERR_FAIL_COND(!plugin_list.has(p_plugin)); + + plugin_list.erase(p_plugin); +} + +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)); + int id = EditorContextMenuPlugin::BASE_ID; + + for (Ref<EditorContextMenuPlugin> &plugin : plugin_list) { + if (plugin->slot != p_slot) { + continue; + } + plugin->context_menu_items.clear(); + plugin->get_options(p_paths); + + HashMap<String, EditorContextMenuPlugin::ContextMenuItem> &items = plugin->context_menu_items; + if (items.size() > 0 && !separator_added) { + separator_added = true; + p_popup->add_separator(); + } + + for (KeyValue<String, EditorContextMenuPlugin::ContextMenuItem> &E : items) { + EditorContextMenuPlugin::ContextMenuItem &item = E.value; + item.id = id; + + if (item.icon.is_valid()) { + p_popup->add_icon_item(item.icon, item.item_name, id); + p_popup->set_item_icon_max_width(-1, icon_size); + } else { + p_popup->add_item(item.item_name, id); + } + if (item.shortcut.is_valid()) { + p_popup->set_item_shortcut(-1, item.shortcut, true); + } + id++; + } + } +} + +Callable EditorContextMenuPluginManager::match_custom_shortcut(EditorContextMenuPlugin::ContextMenuSlot p_slot, const Ref<InputEvent> &p_event) { + for (Ref<EditorContextMenuPlugin> &plugin : plugin_list) { + if (plugin->slot != p_slot) { + continue; + } + + for (KeyValue<Ref<Shortcut>, Callable> &E : plugin->context_menu_shortcuts) { + if (E.key->matches_event(p_event)) { + return E.value; + } + } + } + return Callable(); +} + +bool EditorContextMenuPluginManager::activate_custom_option(ContextMenuSlot p_slot, int p_option, const Variant &p_arg) { + for (Ref<EditorContextMenuPlugin> &plugin : plugin_list) { + if (plugin->slot != p_slot) { + continue; + } + + for (KeyValue<String, EditorContextMenuPlugin::ContextMenuItem> &E : plugin->context_menu_items) { + if (E.value.id == p_option) { + invoke_callback(E.value.callable, p_arg); + return true; + } + } + } + return false; +} + +void EditorContextMenuPluginManager::invoke_callback(const Callable &p_callback, const Variant &p_arg) { + const Variant *argptr = &p_arg; + Callable::CallError ce; + Variant result; + p_callback.callp(&argptr, 1, result, ce); + + if (ce.error != Callable::CallError::CALL_OK) { + ERR_FAIL_MSG("Failed to execute context menu callback: " + Variant::get_callable_error_text(p_callback, &argptr, 1, ce) + "."); + } +} + +void EditorContextMenuPluginManager::create() { + ERR_FAIL_COND(singleton != nullptr); + singleton = memnew(EditorContextMenuPluginManager); +} + +void EditorContextMenuPluginManager::cleanup() { + ERR_FAIL_NULL(singleton); + memdelete(singleton); + singleton = nullptr; } diff --git a/editor/plugins/editor_context_menu_plugin.h b/editor/plugins/editor_context_menu_plugin.h index db05e6c6c7..0232d254ba 100644 --- a/editor/plugins/editor_context_menu_plugin.h +++ b/editor/plugins/editor_context_menu_plugin.h @@ -34,19 +34,33 @@ #include "core/object/gdvirtual.gen.inc" #include "core/object/ref_counted.h" -class Texture2D; +class InputEvent; +class PopupMenu; class Shortcut; +class Texture2D; class EditorContextMenuPlugin : public RefCounted { GDCLASS(EditorContextMenuPlugin, RefCounted); -public: - int start_idx; + friend class EditorContextMenuPluginManager; inline static constexpr int MAX_ITEMS = 100; +public: + enum ContextMenuSlot { + CONTEXT_SLOT_SCENE_TREE, + CONTEXT_SLOT_FILESYSTEM, + CONTEXT_SLOT_SCRIPT_EDITOR, + CONTEXT_SLOT_FILESYSTEM_CREATE, + }; + inline static constexpr int BASE_ID = 2000; + +private: + int slot = -1; + +public: struct ContextMenuItem { - int idx = 0; + int id = 0; String item_name; Callable callable; Ref<Texture2D> icon; @@ -61,10 +75,37 @@ protected: GDVIRTUAL1(_popup_menu, Vector<String>); public: - virtual void add_options(const Vector<String> &p_paths); + virtual void get_options(const Vector<String> &p_paths); + void add_menu_shortcut(const Ref<Shortcut> &p_shortcut, const Callable &p_callable); - void add_context_menu_item(const String &p_name, const Callable &p_callable, const Ref<Texture2D> &p_texture, const Ref<Shortcut> &p_shortcut); - void clear_context_menu_items(); + void add_context_menu_item(const String &p_name, const Callable &p_callable, const Ref<Texture2D> &p_texture); + void add_context_menu_item_from_shortcut(const String &p_name, const Ref<Shortcut> &p_shortcut, const Ref<Texture2D> &p_texture); +}; + +VARIANT_ENUM_CAST(EditorContextMenuPlugin::ContextMenuSlot); + +class EditorContextMenuPluginManager : public Object { + GDCLASS(EditorContextMenuPluginManager, Object); + + using ContextMenuSlot = EditorContextMenuPlugin::ContextMenuSlot; + static inline EditorContextMenuPluginManager *singleton = nullptr; + + LocalVector<Ref<EditorContextMenuPlugin>> plugin_list; + +public: + static EditorContextMenuPluginManager *get_singleton() { return singleton; } + + void add_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin); + void remove_plugin(const Ref<EditorContextMenuPlugin> &p_plugin); + + 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); + + void invoke_callback(const Callable &p_callback, const Variant &p_arg); + + static void create(); + static void cleanup(); }; #endif // EDITOR_CONTEXT_MENU_PLUGIN_H diff --git a/editor/plugins/editor_plugin.cpp b/editor/plugins/editor_plugin.cpp index 0ee67e08ba..c8426bce73 100644 --- a/editor/plugins/editor_plugin.cpp +++ b/editor/plugins/editor_plugin.cpp @@ -47,7 +47,6 @@ #include "editor/import/editor_import_plugin.h" #include "editor/inspector_dock.h" #include "editor/plugins/canvas_item_editor_plugin.h" -#include "editor/plugins/editor_context_menu_plugin.h" #include "editor/plugins/editor_debugger_plugin.h" #include "editor/plugins/editor_resource_conversion_plugin.h" #include "editor/plugins/node_3d_editor_plugin.h" @@ -491,14 +490,12 @@ void EditorPlugin::remove_scene_post_import_plugin(const Ref<EditorScenePostImpo ResourceImporterScene::remove_post_importer_plugin(p_plugin); } -void EditorPlugin::add_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) { - ERR_FAIL_COND(p_plugin.is_null()); - EditorNode::get_editor_data().add_context_menu_plugin(EditorData::ContextMenuSlot(p_slot), p_plugin); +void EditorPlugin::add_context_menu_plugin(EditorContextMenuPlugin::ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) { + EditorContextMenuPluginManager::get_singleton()->add_plugin(p_slot, p_plugin); } -void EditorPlugin::remove_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) { - ERR_FAIL_COND(p_plugin.is_null()); - EditorNode::get_editor_data().remove_context_menu_plugin(EditorData::ContextMenuSlot(p_slot), p_plugin); +void EditorPlugin::remove_context_menu_plugin(const Ref<EditorContextMenuPlugin> &p_plugin) { + EditorContextMenuPluginManager::get_singleton()->remove_plugin(p_plugin); } int find(const PackedStringArray &a, const String &v) { @@ -641,7 +638,7 @@ void EditorPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("set_input_event_forwarding_always_enabled"), &EditorPlugin::set_input_event_forwarding_always_enabled); ClassDB::bind_method(D_METHOD("set_force_draw_over_forwarding_enabled"), &EditorPlugin::set_force_draw_over_forwarding_enabled); ClassDB::bind_method(D_METHOD("add_context_menu_plugin", "slot", "plugin"), &EditorPlugin::add_context_menu_plugin); - ClassDB::bind_method(D_METHOD("remove_context_menu_plugin", "slot", "plugin"), &EditorPlugin::remove_context_menu_plugin); + ClassDB::bind_method(D_METHOD("remove_context_menu_plugin", "plugin"), &EditorPlugin::remove_context_menu_plugin); ClassDB::bind_method(D_METHOD("get_editor_interface"), &EditorPlugin::get_editor_interface); ClassDB::bind_method(D_METHOD("get_script_create_dialog"), &EditorPlugin::get_script_create_dialog); @@ -707,11 +704,6 @@ void EditorPlugin::_bind_methods() { BIND_ENUM_CONSTANT(AFTER_GUI_INPUT_PASS); BIND_ENUM_CONSTANT(AFTER_GUI_INPUT_STOP); BIND_ENUM_CONSTANT(AFTER_GUI_INPUT_CUSTOM); - - BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCENE_TREE); - BIND_ENUM_CONSTANT(CONTEXT_SLOT_FILESYSTEM); - BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCRIPT_EDITOR); - BIND_ENUM_CONSTANT(CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE); } EditorUndoRedoManager *EditorPlugin::get_undo_redo() { diff --git a/editor/plugins/editor_plugin.h b/editor/plugins/editor_plugin.h index 35f65f03cd..b43cbca661 100644 --- a/editor/plugins/editor_plugin.h +++ b/editor/plugins/editor_plugin.h @@ -32,6 +32,7 @@ #define EDITOR_PLUGIN_H #include "core/io/config_file.h" +#include "editor/plugins/editor_context_menu_plugin.h" #include "scene/3d/camera_3d.h" #include "scene/gui/control.h" @@ -53,7 +54,6 @@ class EditorToolAddons; class EditorTranslationParserPlugin; class EditorUndoRedoManager; class ScriptCreateDialog; -class EditorContextMenuPlugin; class EditorPlugin : public Node { GDCLASS(EditorPlugin, Node); @@ -103,13 +103,6 @@ public: AFTER_GUI_INPUT_CUSTOM, }; - enum ContextMenuSlot { - CONTEXT_SLOT_SCENE_TREE, - CONTEXT_SLOT_FILESYSTEM, - CONTEXT_SLOT_SCRIPT_EDITOR, - CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE, - }; - protected: void _notification(int p_what); @@ -257,8 +250,8 @@ public: void add_resource_conversion_plugin(const Ref<EditorResourceConversionPlugin> &p_plugin); void remove_resource_conversion_plugin(const Ref<EditorResourceConversionPlugin> &p_plugin); - void add_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin); - void remove_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin); + void add_context_menu_plugin(EditorContextMenuPlugin::ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin); + void remove_context_menu_plugin(const Ref<EditorContextMenuPlugin> &p_plugin); void enable_plugin(); void disable_plugin(); @@ -270,7 +263,6 @@ public: VARIANT_ENUM_CAST(EditorPlugin::CustomControlContainer); VARIANT_ENUM_CAST(EditorPlugin::DockSlot); VARIANT_ENUM_CAST(EditorPlugin::AfterGUIInput); -VARIANT_ENUM_CAST(EditorPlugin::ContextMenuSlot); typedef EditorPlugin *(*EditorPluginCreateFunc)(); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index f73950a255..4996964976 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -1400,13 +1400,12 @@ void ScriptEditor::_menu_option(int p_option) { } } - // Context menu options. - if (p_option >= EditorData::CONTEXT_MENU_ITEM_ID_BASE) { + if (p_option >= EditorContextMenuPlugin::BASE_ID) { Ref<Resource> resource; if (current) { resource = current->get_edited_resource(); } - EditorNode::get_editor_data().script_editor_options_pressed(EditorData::CONTEXT_SLOT_SCRIPT_EDITOR, p_option, resource); + EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_SCRIPT_EDITOR, p_option, resource); return; } @@ -3316,10 +3315,16 @@ void ScriptEditor::shortcut_input(const Ref<InputEvent> &p_event) { _menu_option(WINDOW_MOVE_DOWN); accept_event(); } - // Context menu shortcuts. - int match_option = EditorNode::get_editor_data().match_context_menu_shortcut(EditorData::CONTEXT_SLOT_SCRIPT_EDITOR, p_event); - if (match_option) { - _menu_option(match_option); + + Callable custom_callback = EditorContextMenuPluginManager::get_singleton()->match_custom_shortcut(EditorContextMenuPlugin::CONTEXT_SLOT_SCRIPT_EDITOR, p_event); + if (custom_callback.is_valid()) { + Ref<Resource> resource; + ScriptEditorBase *current = _get_current_editor(); + if (current) { + resource = current->get_edited_resource(); + } + EditorContextMenuPluginManager::get_singleton()->invoke_callback(custom_callback, resource); + accept_event(); } } @@ -3388,7 +3393,7 @@ void ScriptEditor::_make_script_list_context_menu() { selected_paths.push_back(path); } } - EditorNode::get_editor_data().add_options_from_plugins(context_menu, EditorData::CONTEXT_SLOT_SCRIPT_EDITOR, selected_paths); + EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(context_menu, EditorContextMenuPlugin::CONTEXT_SLOT_SCRIPT_EDITOR, selected_paths); context_menu->set_position(get_screen_position() + get_local_mouse_position()); context_menu->reset_size(); diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 4e9980352a..9f56c586a2 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -215,11 +215,12 @@ void SceneTreeDock::shortcut_input(const Ref<InputEvent> &p_event) { } else if (ED_IS_SHORTCUT("scene_tree/delete", p_event)) { _tool_selected(TOOL_ERASE); } else { - int match_option = EditorNode::get_editor_data().match_context_menu_shortcut(EditorData::CONTEXT_SLOT_SCENE_TREE, p_event); - if (match_option) { - _tool_selected(match_option); + Callable custom_callback = EditorContextMenuPluginManager::get_singleton()->match_custom_shortcut(EditorContextMenuPlugin::CONTEXT_SLOT_SCENE_TREE, p_event); + if (custom_callback.is_valid()) { + EditorContextMenuPluginManager::get_singleton()->invoke_callback(custom_callback, _get_selection_array()); + } else { + return; } - return; } // Tool selection was successful, accept the event to stop propagation. @@ -1492,10 +1493,8 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { } break; default: { - // Editor context plugin. - if (p_tool >= EditorData::CONTEXT_MENU_ITEM_ID_BASE) { - List<Node *> selection = editor_selection->get_selected_node_list(); - EditorNode::get_editor_data().scene_tree_options_pressed(EditorData::CONTEXT_SLOT_SCENE_TREE, p_tool, selection); + if (p_tool >= EditorContextMenuPlugin::BASE_ID) { + EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_SCENE_TREE, p_tool, _get_selection_array()); break; } @@ -3367,6 +3366,18 @@ void SceneTreeDock::_normalize_drop(Node *&to_node, int &to_pos, int p_type) { } } +Array SceneTreeDock::_get_selection_array() { + List<Node *> selection = editor_selection->get_selected_node_list(); + TypedArray<Node> array; + array.resize(selection.size()); + + int i = 0; + for (const Node *E : selection) { + array[i++] = E; + } + return array; +} + void SceneTreeDock::_files_dropped(const Vector<String> &p_files, NodePath p_to, int p_type) { Node *node = get_node(p_to); ERR_FAIL_NULL(node); @@ -3767,7 +3778,7 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) { String node_path = root->get_path().rel_path_to(E->get()->get_path()); p_paths.push_back(node_path); } - EditorNode::get_editor_data().add_options_from_plugins(menu, EditorData::CONTEXT_SLOT_SCENE_TREE, p_paths); + EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(menu, EditorContextMenuPlugin::CONTEXT_SLOT_SCENE_TREE, p_paths); menu->reset_size(); menu->set_position(p_menu_pos); diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index 1807ec5896..d0cdaf8dd1 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -262,6 +262,7 @@ class SceneTreeDock : public VBoxContainer { bool _has_tracks_to_delete(Node *p_node, List<Node *> &p_to_delete) const; void _normalize_drop(Node *&to_node, int &to_pos, int p_type); + Array _get_selection_array(); void _nodes_dragged(const Array &p_nodes, NodePath p_to, int p_type); void _files_dropped(const Vector<String> &p_files, NodePath p_to, int p_type); |