diff --git a/editor/limbo_ai_editor_plugin.cpp b/editor/limbo_ai_editor_plugin.cpp index e635956..654a4f3 100644 --- a/editor/limbo_ai_editor_plugin.cpp +++ b/editor/limbo_ai_editor_plugin.cpp @@ -345,48 +345,63 @@ void LimboAIEditor::_process_shortcut_input(const Ref &p_event) { return; } + bool handled = false; + // * Global shortcuts. if (LW_IS_SHORTCUT("limbo_ai/open_debugger", p_event)) { _misc_option_selected(MISC_OPEN_DEBUGGER); + handled = true; + } + + // * When editor is on screen. + + if (!handled && is_visible_in_tree()) { + if (LW_IS_SHORTCUT("limbo_ai/jump_to_owner", p_event)) { + _tab_menu_option_selected(TAB_JUMP_TO_OWNER); + handled = true; + } else if (LW_IS_SHORTCUT("limbo_ai/close_tab", p_event)) { + _tab_menu_option_selected(TAB_CLOSE); + handled = true; + } + } + + // * When editor is focused. + + if (!handled && (has_focus() || (get_viewport()->gui_get_focus_owner() && is_ancestor_of(get_viewport()->gui_get_focus_owner())))) { + handled = true; + if (LW_IS_SHORTCUT("limbo_ai/rename_task", p_event)) { + _action_selected(ACTION_RENAME); + } else if (LW_IS_SHORTCUT("limbo_ai/cut_task", p_event)) { + _action_selected(ACTION_CUT); + } else if (LW_IS_SHORTCUT("limbo_ai/copy_task", p_event)) { + _action_selected(ACTION_COPY); + } else if (LW_IS_SHORTCUT("limbo_ai/paste_task", p_event)) { + _action_selected(ACTION_PASTE); + } else if (LW_IS_SHORTCUT("limbo_ai/paste_task_after", p_event)) { + _action_selected(ACTION_PASTE_AFTER); + } else if (LW_IS_SHORTCUT("limbo_ai/move_task_up", p_event)) { + _action_selected(ACTION_MOVE_UP); + } else if (LW_IS_SHORTCUT("limbo_ai/move_task_down", p_event)) { + _action_selected(ACTION_MOVE_DOWN); + } else if (LW_IS_SHORTCUT("limbo_ai/duplicate_task", p_event)) { + _action_selected(ACTION_DUPLICATE); + } else if (LW_IS_SHORTCUT("limbo_ai/remove_task", p_event)) { + _action_selected(ACTION_REMOVE); + } else if (LW_IS_SHORTCUT("limbo_ai/new_behavior_tree", p_event)) { + _new_bt(); + } else if (LW_IS_SHORTCUT("limbo_ai/save_behavior_tree", p_event)) { + _on_save_pressed(); + } else if (LW_IS_SHORTCUT("limbo_ai/load_behavior_tree", p_event)) { + _popup_file_dialog(load_dialog); + } else { + handled = false; + } + } + + if (handled) { get_viewport()->set_input_as_handled(); } - - // * Local shortcuts. - - if (!(has_focus() || get_viewport()->gui_get_focus_owner() == nullptr || is_ancestor_of(get_viewport()->gui_get_focus_owner()))) { - return; - } - - if (LW_IS_SHORTCUT("limbo_ai/rename_task", p_event)) { - _action_selected(ACTION_RENAME); - } else if (LW_IS_SHORTCUT("limbo_ai/cut_task", p_event)) { - _action_selected(ACTION_CUT); - } else if (LW_IS_SHORTCUT("limbo_ai/copy_task", p_event)) { - _action_selected(ACTION_COPY); - } else if (LW_IS_SHORTCUT("limbo_ai/paste_task", p_event)) { - _action_selected(ACTION_PASTE); - } else if (LW_IS_SHORTCUT("limbo_ai/paste_task_after", p_event)) { - _action_selected(ACTION_PASTE_AFTER); - } else if (LW_IS_SHORTCUT("limbo_ai/move_task_up", p_event)) { - _action_selected(ACTION_MOVE_UP); - } else if (LW_IS_SHORTCUT("limbo_ai/move_task_down", p_event)) { - _action_selected(ACTION_MOVE_DOWN); - } else if (LW_IS_SHORTCUT("limbo_ai/duplicate_task", p_event)) { - _action_selected(ACTION_DUPLICATE); - } else if (LW_IS_SHORTCUT("limbo_ai/remove_task", p_event)) { - _action_selected(ACTION_REMOVE); - } else if (LW_IS_SHORTCUT("limbo_ai/new_behavior_tree", p_event)) { - _new_bt(); - } else if (LW_IS_SHORTCUT("limbo_ai/save_behavior_tree", p_event)) { - _on_save_pressed(); - } else if (LW_IS_SHORTCUT("limbo_ai/load_behavior_tree", p_event)) { - _popup_file_dialog(load_dialog); - } else { - return; - } - - get_viewport()->set_input_as_handled(); } void LimboAIEditor::_on_tree_rmb(const Vector2 &p_menu_pos) { @@ -1013,9 +1028,10 @@ void LimboAIEditor::_tab_input(const Ref &p_input) { void LimboAIEditor::_show_tab_context_menu() { tab_menu->clear(); + tab_menu->add_shortcut(LW_GET_SHORTCUT("limbo_ai/jump_to_owner"), TabMenu::TAB_JUMP_TO_OWNER); tab_menu->add_item(TTR("Show in FileSystem"), TabMenu::TAB_SHOW_IN_FILESYSTEM); tab_menu->add_separator(); - tab_menu->add_item(TTR("Close Tab"), TabMenu::TAB_CLOSE); + tab_menu->add_shortcut(LW_GET_SHORTCUT("limbo_ai/close_tab"), TabMenu::TAB_CLOSE); tab_menu->add_item(TTR("Close Other Tabs"), TabMenu::TAB_CLOSE_OTHER); tab_menu->add_item(TTR("Close Tabs to the Right"), TabMenu::TAB_CLOSE_RIGHT); tab_menu->add_item(TTR("Close All Tabs"), TabMenu::TAB_CLOSE_ALL); @@ -1025,7 +1041,12 @@ void LimboAIEditor::_show_tab_context_menu() { } void LimboAIEditor::_tab_menu_option_selected(int p_id) { + if (history.size() == 0) { + // No tabs open, returning. + return; + } ERR_FAIL_INDEX(idx_history, history.size()); + switch (p_id) { case TAB_SHOW_IN_FILESYSTEM: { Ref bt = history[idx_history]; @@ -1034,6 +1055,14 @@ void LimboAIEditor::_tab_menu_option_selected(int p_id) { FS_DOCK_SELECT_FILE(path.get_slice("::", 0)); } } break; + case TAB_JUMP_TO_OWNER: { + Ref bt = history[idx_history]; + ERR_FAIL_NULL(bt); + String bt_path = bt->get_path(); + if (!bt_path.is_empty()) { + owner_picker->pick_and_open_owner_of_resource(bt_path); + } + } break; case TAB_CLOSE: { _tab_closed(idx_history); } break; @@ -1347,6 +1376,8 @@ LimboAIEditor::LimboAIEditor() { LW_SHORTCUT("limbo_ai/save_behavior_tree", TTR("Save Behavior Tree"), (Key)(LW_KEY_MASK(CMD_OR_CTRL) | LW_KEY_MASK(ALT) | LW_KEY(S))); LW_SHORTCUT("limbo_ai/load_behavior_tree", TTR("Load Behavior Tree"), (Key)(LW_KEY_MASK(CMD_OR_CTRL) | LW_KEY_MASK(ALT) | LW_KEY(L))); LW_SHORTCUT("limbo_ai/open_debugger", TTR("Open Debugger"), (Key)(LW_KEY_MASK(CMD_OR_CTRL) | LW_KEY_MASK(ALT) | LW_KEY(D))); + LW_SHORTCUT("limbo_ai/jump_to_owner", TTR("Jump to Owner"), (Key)(LW_KEY_MASK(CMD_OR_CTRL) | LW_KEY(J))); + LW_SHORTCUT("limbo_ai/close_tab", TTR("Close Tab"), (Key)(LW_KEY_MASK(CMD_OR_CTRL) | LW_KEY(W))); set_process_shortcut_input(true); @@ -1450,6 +1481,9 @@ LimboAIEditor::LimboAIEditor() { tab_menu = memnew(PopupMenu); add_child(tab_menu); + owner_picker = memnew(OwnerPicker); + add_child(owner_picker); + hsc = memnew(HSplitContainer); hsc->set_h_size_flags(SIZE_EXPAND_FILL); hsc->set_v_size_flags(SIZE_EXPAND_FILL); diff --git a/editor/limbo_ai_editor_plugin.h b/editor/limbo_ai_editor_plugin.h index fce4a28..d45a62a 100644 --- a/editor/limbo_ai_editor_plugin.h +++ b/editor/limbo_ai_editor_plugin.h @@ -17,6 +17,7 @@ #include "../bt/behavior_tree.h" #include "../bt/tasks/bt_task.h" #include "editor_property_variable_name.h" +#include "owner_picker.h" #include "task_palette.h" #include "task_tree.h" @@ -101,6 +102,7 @@ private: enum TabMenu { TAB_SHOW_IN_FILESYSTEM, + TAB_JUMP_TO_OWNER, TAB_CLOSE, TAB_CLOSE_OTHER, TAB_CLOSE_RIGHT, @@ -139,6 +141,7 @@ private: HBoxContainer *tab_bar_container; TabBar *tab_bar; PopupMenu *tab_menu; + OwnerPicker *owner_picker; HSplitContainer *hsc; TaskTree *task_tree; VBoxContainer *banners; diff --git a/editor/owner_picker.cpp b/editor/owner_picker.cpp new file mode 100644 index 0000000..8dc1b5e --- /dev/null +++ b/editor/owner_picker.cpp @@ -0,0 +1,147 @@ +/** + * owner_picker.cpp + * ============================================================================= + * Copyright 2021-2024 Serhii Snitsaruk + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + * ============================================================================= + */ + +#ifdef TOOLS_ENABLED + +#include "owner_picker.h" + +#include "../util/limbo_compat.h" + +#ifdef LIMBOAI_MODULE +#include "editor/editor_file_system.h" +#include "editor/editor_interface.h" +#elif LIMBOAI_GDEXTENSION +#include +#include +#include +#include +#endif + +Vector OwnerPicker::_find_owners(const String &p_path) const { + Vector owners; + + if (RESOURCE_PATH_IS_BUILT_IN(p_path)) { + // For built-in resources we use the path to the containing resource. + String owner_path = p_path.substr(0, p_path.rfind("::")); + owners.append(owner_path); + return owners; + } + + List dirs; + dirs.push_back(EDITOR_FILE_SYSTEM()->get_filesystem()); + while (dirs.size() > 0) { + EditorFileSystemDirectory *efd = dirs.front()->get(); + dirs.pop_front(); + + for (int i = 0; i < efd->get_file_count(); i++) { + String file_path = efd->get_file_path(i); + + Vector deps; +#ifdef LIMBOAI_MODULE + deps = efd->get_file_deps(i); +#elif LIMBOAI_GDEXTENSION + PackedStringArray res_deps = ResourceLoader::get_singleton()->get_dependencies(file_path); + for (String dep : res_deps) { + if (dep.begins_with("uid://")) { + dep = dep.get_slice("::", 2); + } + deps.append(dep); + } +#endif // LIMBOAI_MODULE + + for (int j = 0; j < deps.size(); j++) { + if (deps[j] == p_path) { + owners.append(file_path); + break; + } + } + } + + for (int k = 0; k < efd->get_subdir_count(); k++) { + dirs.push_back(efd->get_subdir(k)); + } + } + + return owners; +} + +void OwnerPicker::pick_and_open_owner_of_resource(const String &p_path) { + if (p_path.is_empty()) { + return; + } + + owners_item_list->clear(); + + Vector owners = _find_owners(p_path); + for (int i = 0; i < owners.size(); i++) { + owners_item_list->add_item(owners[i]); + } + + if (owners_item_list->get_item_count() > 0) { + owners_item_list->select(0); + owners_item_list->ensure_current_is_visible(); + } + + if (owners_item_list->get_item_count() == 1) { + // Open owner immediately if there is only one owner. + _selection_confirmed(); + } else if (owners_item_list->get_item_count() == 0) { + owners_item_list->hide(); + set_title(TTR("Alert!")); + set_text(TTR("Couldn't find owner. Looks like it's not used by any other resource.")); + reset_size(); + popup_centered(); + } else { + owners_item_list->show(); + set_title(TTR("Pick owner")); + set_text(""); + reset_size(); + popup_centered_ratio(0.3); + owners_item_list->grab_focus(); + } +} + +void OwnerPicker::_item_activated(int p_item) { + hide(); + emit_signal("confirmed"); +} + +void OwnerPicker::_selection_confirmed() { + for (int idx : owners_item_list->get_selected_items()) { + String owner_path = owners_item_list->get_item_text(idx); + if (RESOURCE_IS_SCENE_FILE(owner_path)) { + EditorInterface::get_singleton()->open_scene_from_path(owner_path); + } else { + EditorInterface::get_singleton()->edit_resource(RESOURCE_LOAD(owner_path, "")); + } + } +} + +void OwnerPicker::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_READY: { + owners_item_list->connect("item_activated", callable_mp(this, &OwnerPicker::_item_activated)); + connect("confirmed", callable_mp(this, &OwnerPicker::_selection_confirmed)); + } break; + } +} + +void OwnerPicker::_bind_methods() { +} + +OwnerPicker::OwnerPicker() { + owners_item_list = memnew(ItemList); + // Note: In my tests, editor couldn't process open request for multiple packed scenes at once. + owners_item_list->set_select_mode(ItemList::SELECT_SINGLE); + add_child(owners_item_list); +} + +#endif // TOOLS_ENABLED diff --git a/editor/owner_picker.h b/editor/owner_picker.h new file mode 100644 index 0000000..9fcb9d8 --- /dev/null +++ b/editor/owner_picker.h @@ -0,0 +1,47 @@ +/** + * owner_picker.h + * ============================================================================= + * Copyright 2021-2024 Serhii Snitsaruk + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + * ============================================================================= + */ + +#ifndef OWNER_PICKER_H +#define OWNER_PICKER_H + +#ifdef LIMBOAI_MODULE +#include "scene/gui/dialogs.h" +#include "scene/gui/item_list.h" +#elif LIMBOAI_GDEXTENSION +#include +#include +#include +using namespace godot; +#endif + +class OwnerPicker : public AcceptDialog { + GDCLASS(OwnerPicker, AcceptDialog); + +private: + ItemList *owners_item_list; + + void _item_activated(int p_item); + void _selection_confirmed(); + +protected: + static void _bind_methods(); + + void _notification(int p_what); + + Vector _find_owners(const String &p_path) const; + +public: + void pick_and_open_owner_of_resource(const String &p_path); + + OwnerPicker(); +}; + +#endif // OWNER_PICKER_H diff --git a/register_types.cpp b/register_types.cpp index d209c9f..967f7e9 100644 --- a/register_types.cpp +++ b/register_types.cpp @@ -263,6 +263,7 @@ void initialize_limboai_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(EditorInspectorPluginBBPlan); GDREGISTER_CLASS(EditorPropertyVariableName); GDREGISTER_CLASS(EditorInspectorPluginVariableName); + GDREGISTER_CLASS(OwnerPicker); GDREGISTER_CLASS(LimboAIEditor); GDREGISTER_CLASS(LimboAIEditorPlugin); #endif // LIMBOAI_GDEXTENSION diff --git a/util/limbo_compat.h b/util/limbo_compat.h index 228cb3e..55760f5 100644 --- a/util/limbo_compat.h +++ b/util/limbo_compat.h @@ -42,8 +42,8 @@ #define RESOURCE_LOAD_NO_CACHE(m_path, m_hint) ResourceLoader::load(m_path, m_hint, ResourceFormatLoader::CACHE_MODE_IGNORE) #define RESOURCE_SAVE(m_res, m_path, m_flags) ResourceSaver::save(m_res, m_path, m_flags) #define RESOURCE_IS_CACHED(m_path) (ResourceCache::has(m_path)) -#define RESOURCE_GET_TYPE(m_path) (ResourceLoader::get_resource_type(m_path)) #define RESOURCE_EXISTS(m_path, m_type_hint) (ResourceLoader::exists(m_path, m_type_hint)) +#define RESOURCE_IS_SCENE_FILE(m_path) (ResourceLoader::get_resource_type(m_path) == "PackedScene") #define GET_PROJECT_SETTINGS_DIR() EditorPaths::get_singleton()->get_project_settings_dir() #define EDIT_RESOURCE(m_res) EditorNode::get_singleton()->edit_resource(m_res) #define INSPECTOR_GET_EDITED_OBJECT() (InspectorDock::get_inspector_singleton()->get_edited_object()) @@ -127,7 +127,7 @@ using namespace godot; #define RESOURCE_LOAD_NO_CACHE(m_path, m_hint) ResourceLoader::get_singleton()->load(m_path, m_hint, ResourceLoader::CACHE_MODE_IGNORE) #define RESOURCE_SAVE(m_res, m_path, m_flags) ResourceSaver::get_singleton()->save(m_res, m_path, m_flags) #define RESOURCE_IS_CACHED(m_path) (ResourceLoader::get_singleton()->has_cached(res_path)) -#define RESOURCE_GET_TYPE(m_path) (ResourceLoader::get_resource_type(m_path)) +#define RESOURCE_IS_SCENE_FILE(m_path) (ResourceLoader::get_singleton()->get_recognized_extensions_for_type("PackedScene").has(m_path.get_extension())) #define RESOURCE_EXISTS(m_path, m_type_hint) (ResourceLoader::get_singleton()->exists(m_path, m_type_hint)) #define GET_PROJECT_SETTINGS_DIR() EditorInterface::get_singleton()->get_editor_paths()->get_project_settings_dir() #define EDIT_RESOURCE(m_res) EditorInterface::get_singleton()->edit_resource(m_res) @@ -237,6 +237,7 @@ Variant VARIANT_DEFAULT(Variant::Type p_type); #define IS_RESOURCE_FILE(m_path) (m_path.begins_with("res://") && m_path.find("::") == -1) #define RESOURCE_TYPE_HINT(m_type) vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, m_type) #define RESOURCE_IS_BUILT_IN(m_res) (m_res->get_path().is_empty() || m_res->get_path().contains("::")) +#define RESOURCE_PATH_IS_BUILT_IN(m_path) (m_path.is_empty() || m_path.contains("::")) #ifdef TOOLS_ENABLED