Path: blob/master/editor/debugger/editor_debugger_tree.cpp
20860 views
/**************************************************************************/1/* editor_debugger_tree.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "editor_debugger_tree.h"3132#include "core/io/resource_saver.h"33#include "editor/debugger/editor_debugger_node.h"34#include "editor/docks/scene_tree_dock.h"35#include "editor/editor_node.h"36#include "editor/editor_string_names.h"37#include "editor/gui/editor_file_dialog.h"38#include "editor/gui/editor_toaster.h"39#include "editor/settings/editor_settings.h"40#include "scene/debugger/scene_debugger_object.h"41#include "scene/gui/texture_rect.h"42#include "scene/resources/packed_scene.h"43#include "servers/display/display_server.h"4445EditorDebuggerTree::EditorDebuggerTree() {46set_v_size_flags(SIZE_EXPAND_FILL);47set_allow_rmb_select(true);48set_select_mode(SELECT_MULTI);4950// Popup51item_menu = memnew(PopupMenu);52item_menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorDebuggerTree::_item_menu_id_pressed));53add_child(item_menu);5455// File Dialog56file_dialog = memnew(EditorFileDialog);57file_dialog->connect("file_selected", callable_mp(this, &EditorDebuggerTree::_file_selected));58add_child(file_dialog);5960accept = memnew(AcceptDialog);61add_child(accept);62}6364void EditorDebuggerTree::_notification(int p_what) {65switch (p_what) {66case NOTIFICATION_POSTINITIALIZE: {67set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);6869connect("cell_selected", callable_mp(this, &EditorDebuggerTree::_scene_tree_selected));70connect("multi_selected", callable_mp(this, &EditorDebuggerTree::_scene_tree_selection_changed));71connect("nothing_selected", callable_mp(this, &EditorDebuggerTree::_scene_tree_nothing_selected));72connect("item_collapsed", callable_mp(this, &EditorDebuggerTree::_scene_tree_folded));73connect("item_mouse_selected", callable_mp(this, &EditorDebuggerTree::_scene_tree_rmb_selected));74} break;7576case NOTIFICATION_READY: {77update_icon_max_width();78} break;79}80}8182void EditorDebuggerTree::_bind_methods() {83ADD_SIGNAL(MethodInfo("objects_selected", PropertyInfo(Variant::ARRAY, "object_ids"), PropertyInfo(Variant::INT, "debugger")));84ADD_SIGNAL(MethodInfo("selection_cleared", PropertyInfo(Variant::INT, "debugger")));85ADD_SIGNAL(MethodInfo("save_node", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "filename"), PropertyInfo(Variant::INT, "debugger")));86ADD_SIGNAL(MethodInfo("open"));87}8889void EditorDebuggerTree::_scene_tree_selected() {90TreeItem *item = get_selected();91if (!item) {92return;93}9495if (!inspected_object_ids.is_empty()) {96inspected_object_ids.clear();97deselect_all();98item->select(0);99}100101uint64_t id = uint64_t(item->get_metadata(0));102inspected_object_ids.append(id);103104if (!notify_selection_queued) {105callable_mp(this, &EditorDebuggerTree::_notify_selection_changed).call_deferred();106notify_selection_queued = true;107}108}109110void EditorDebuggerTree::_scene_tree_selection_changed(TreeItem *p_item, int p_column, bool p_selected) {111if (updating_scene_tree || !p_item) {112return;113}114115uint64_t id = uint64_t(p_item->get_metadata(0));116if (p_selected) {117if (inspected_object_ids.size() == (int)EDITOR_GET("debugger/max_node_selection")) {118selection_surpassed_limit = true;119p_item->deselect(0);120} else if (!inspected_object_ids.has(id)) {121inspected_object_ids.append(id);122}123} else if (inspected_object_ids.has(id)) {124inspected_object_ids.erase(id);125}126127if (!notify_selection_queued) {128callable_mp(this, &EditorDebuggerTree::_notify_selection_changed).call_deferred();129notify_selection_queued = true;130}131}132133void EditorDebuggerTree::_scene_tree_nothing_selected() {134deselect_all();135inspected_object_ids.clear();136emit_signal(SNAME("selection_cleared"), debugger_id);137}138139void EditorDebuggerTree::_notify_selection_changed() {140notify_selection_queued = false;141142if (inspected_object_ids.is_empty()) {143emit_signal(SNAME("selection_cleared"), debugger_id);144} else {145emit_signal(SNAME("objects_selected"), inspected_object_ids.duplicate(), debugger_id);146}147148if (selection_surpassed_limit) {149selection_surpassed_limit = false;150EditorToaster::get_singleton()->popup_str(vformat(TTR("Some remote nodes were not selected, as the configured maximum selection is %d. This can be changed at \"debugger/max_node_selection\" in the Editor Settings."), EDITOR_GET("debugger/max_node_selection")), EditorToaster::SEVERITY_WARNING);151}152}153154void EditorDebuggerTree::_scene_tree_folded(Object *p_obj) {155if (updating_scene_tree) {156return;157}158TreeItem *item = Object::cast_to<TreeItem>(p_obj);159160if (!item) {161return;162}163164ObjectID id = ObjectID(uint64_t(item->get_metadata(0)));165if (unfold_cache.has(id)) {166unfold_cache.erase(id);167} else {168unfold_cache.insert(id);169}170}171172void EditorDebuggerTree::_scene_tree_rmb_selected(const Vector2 &p_position, MouseButton p_button) {173if (p_button != MouseButton::RIGHT) {174return;175}176177TreeItem *item = get_item_at_position(p_position);178if (!item) {179return;180}181182item->select(0);183184item_menu->clear();185item_menu->add_icon_item(get_editor_theme_icon(SNAME("CreateNewSceneFrom")), TTR("Save Branch as Scene..."), ITEM_MENU_SAVE_REMOTE_NODE);186item_menu->add_icon_item(get_editor_theme_icon(SNAME("CopyNodePath")), TTR("Copy Node Path"), ITEM_MENU_COPY_NODE_PATH);187item_menu->add_icon_item(get_editor_theme_icon(SNAME("Collapse")), TTR("Expand/Collapse Branch"), ITEM_MENU_EXPAND_COLLAPSE);188item_menu->set_position(get_screen_position() + get_local_mouse_position());189item_menu->reset_size();190item_menu->popup();191}192193/// Populates inspect_scene_tree given data in nodes as a flat list, encoded depth first.194///195/// Given a nodes array like [R,A,B,C,D,E] the following Tree will be generated, assuming196/// filter is an empty String, R and A child count are 2, B is 1 and C, D and E are 0.197///198/// R199/// |-A200/// | |-B201/// | | |-C202/// | |203/// | |-D204/// |205/// |-E206///207void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int p_debugger) {208set_hide_root(false);209210updating_scene_tree = true;211const String last_path = get_selected_path();212const String filter = SceneTreeDock::get_singleton()->get_filter();213LocalVector<TreeItem *> select_items;214bool hide_filtered_out_parents = EDITOR_GET("docks/scene_tree/hide_filtered_out_parents");215216bool should_scroll = scrolling_to_item || filter != last_filter;217scrolling_to_item = false;218TreeItem *scroll_item = nullptr;219TypedArray<uint64_t> ids_present;220221// Nodes are in a flatten list, depth first. Use a stack of parents, avoid recursion.222List<ParentItem> parents;223for (const SceneDebuggerTree::RemoteNode &node : p_tree->nodes) {224TreeItem *parent = nullptr;225Pair<TreeItem *, TreeItem *> move_from_to;226if (parents.size()) { // Find last parent.227ParentItem &p = parents.front()->get();228parent = p.tree_item;229if (!(--p.child_count)) { // If no child left, remove it.230parents.pop_front();231232if (hide_filtered_out_parents && !filter.is_subsequence_ofn(parent->get_text(0))) {233if (parent == get_root()) {234set_hide_root(true);235} else {236move_from_to.first = parent;237// Find the closest ancestor that matches the filter.238for (const ParentItem p2 : parents) {239move_from_to.second = p2.tree_item;240if (p2.matches_filter || move_from_to.second == get_root()) {241break;242}243}244245if (!move_from_to.second) {246move_from_to.second = get_root();247}248}249}250}251}252253// Add this node.254TreeItem *item = create_item(parent);255item->set_text(0, node.name);256if (node.scene_file_path.is_empty()) {257item->set_tooltip_text(0, node.name + "\n" + TTR("Type:") + " " + node.type_name);258} else {259item->set_tooltip_text(0, node.name + "\n" + TTR("Instance:") + " " + node.scene_file_path + "\n" + TTR("Type:") + " " + node.type_name);260}261Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(node.type_name);262if (icon.is_valid()) {263item->set_icon(0, icon);264}265item->set_metadata(0, node.id);266267String current_path;268if (parent) {269current_path += (String)parent->get_meta("node_path");270271// Set current item as collapsed if necessary (root is never collapsed).272if (!unfold_cache.has(node.id)) {273item->set_collapsed(true);274}275}276item->set_meta("node_path", current_path + "/" + item->get_text(0));277278// Select previously selected nodes.279if (debugger_id == p_debugger) { // Can use remote id.280if (inspected_object_ids.has(uint64_t(node.id))) {281ids_present.append(node.id);282283if (selection_uncollapse_all) {284selection_uncollapse_all = false;285286// Temporarily set to `false`, to allow caching the unfolds.287updating_scene_tree = false;288item->uncollapse_tree();289updating_scene_tree = true;290}291292select_items.push_back(item);293if (should_scroll) {294scroll_item = item;295}296}297} else if (last_path == (String)item->get_meta("node_path")) { // Must use path.298updating_scene_tree = false; // Force emission of new selections.299select_items.push_back(item);300if (should_scroll) {301scroll_item = item;302}303updating_scene_tree = true;304}305306// Add buttons.307const Color remote_button_color = Color(1, 1, 1, 0.8);308if (!node.scene_file_path.is_empty()) {309String node_scene_file_path = node.scene_file_path;310Ref<Texture2D> button_icon = get_editor_theme_icon(SNAME("InstanceOptions"));311String tooltip = vformat(TTR("This node has been instantiated from a PackedScene file:\n%s\nClick to open the original file in the Editor."), node_scene_file_path);312313item->set_meta("scene_file_path", node_scene_file_path);314item->add_button(0, button_icon, BUTTON_SUBSCENE, false, tooltip);315item->set_button_color(0, item->get_button_count(0) - 1, remote_button_color);316}317318if (node.view_flags & SceneDebuggerTree::RemoteNode::VIEW_HAS_VISIBLE_METHOD) {319bool node_visible = node.view_flags & SceneDebuggerTree::RemoteNode::VIEW_VISIBLE;320bool node_visible_in_tree = node.view_flags & SceneDebuggerTree::RemoteNode::VIEW_VISIBLE_IN_TREE;321Ref<Texture2D> button_icon = get_editor_theme_icon(node_visible ? SNAME("GuiVisibilityVisible") : SNAME("GuiVisibilityHidden"));322String tooltip = TTR("Toggle Visibility");323324item->set_meta("visible", node_visible);325item->add_button(0, button_icon, BUTTON_VISIBILITY, false, tooltip);326if (ClassDB::is_parent_class(node.type_name, "CanvasItem") || ClassDB::is_parent_class(node.type_name, "Node3D")) {327item->set_button_color(0, item->get_button_count(0) - 1, node_visible_in_tree ? remote_button_color : Color(1, 1, 1, 0.6));328} else {329item->set_button_color(0, item->get_button_count(0) - 1, remote_button_color);330}331}332333// Add in front of the parents stack if children are expected.334if (node.child_count) {335parents.push_front(ParentItem(item, node.child_count, filter.is_subsequence_ofn(item->get_text(0))));336} else {337// Apply filters.338while (parent) {339const bool had_siblings = item->get_prev() || item->get_next();340if (filter.is_subsequence_ofn(item->get_text(0))) {341break; // Filter matches, must survive.342}343344if (select_items.has(item) || scroll_item == item) {345select_items.resize(select_items.size() - 1);346scroll_item = nullptr;347}348parent->remove_child(item);349memdelete(item);350351if (had_siblings) {352break; // Parent must survive.353}354355item = parent;356parent = item->get_parent();357// Check if parent expects more children.358for (ParentItem &pair : parents) {359if (pair.tree_item == item) {360parent = nullptr;361break; // Might have more children.362}363}364}365}366367// Move all children to the ancestor that matches the filter, if picked.368if (move_from_to.first) {369TreeItem *from = move_from_to.first;370TypedArray<TreeItem> children = from->get_children();371if (!children.is_empty()) {372for (Variant &c : children) {373TreeItem *ti = Object::cast_to<TreeItem>(c);374from->remove_child(ti);375move_from_to.second->add_child(ti);376}377378from->get_parent()->remove_child(from);379memdelete(from);380if (select_items.has(from) || scroll_item == from) {381select_items.erase(from);382scroll_item = nullptr;383}384}385}386}387388inspected_object_ids = ids_present;389390debugger_id = p_debugger; // Needed by hook, could be avoided if every debugger had its own tree.391392for (TreeItem *item : select_items) {393item->select(0);394}395if (scroll_item) {396scroll_to_item(scroll_item, false);397}398399if (new_session) {400// Some nodes may stay selected between sessions.401// Make sure the inspector shows them properly.402if (!notify_selection_queued) {403callable_mp(this, &EditorDebuggerTree::_notify_selection_changed).call_deferred();404notify_selection_queued = true;405}406new_session = false;407}408409last_filter = filter;410updating_scene_tree = false;411}412413void EditorDebuggerTree::select_nodes(const TypedArray<int64_t> &p_ids) {414// Manually select, as the tree control may be out-of-date for some reason (e.g. not shown yet).415selection_uncollapse_all = true;416inspected_object_ids = p_ids;417scrolling_to_item = true;418419if (!updating_scene_tree) {420// Request a tree refresh.421EditorDebuggerNode::get_singleton()->request_remote_tree();422}423// Set the value immediately, so no update flooding happens and causes a crash.424updating_scene_tree = true;425}426427void EditorDebuggerTree::clear_selection() {428inspected_object_ids.clear();429430if (!updating_scene_tree) {431// Request a tree refresh.432EditorDebuggerNode::get_singleton()->request_remote_tree();433}434// Set the value immediately, so no update flooding happens and causes a crash.435updating_scene_tree = true;436}437438Variant EditorDebuggerTree::get_drag_data(const Point2 &p_point) {439if (get_button_id_at_position(p_point) != -1) {440return Variant();441}442443TreeItem *selected = get_selected();444if (!selected) {445return Variant();446}447448String path = selected->get_text(0);449const int icon_size = get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));450451HBoxContainer *hb = memnew(HBoxContainer);452TextureRect *tf = memnew(TextureRect);453tf->set_texture(selected->get_icon(0));454tf->set_custom_minimum_size(Size2(icon_size, icon_size));455tf->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);456tf->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);457hb->add_child(tf);458Label *label = memnew(Label(path));459hb->add_child(label);460set_drag_preview(hb);461462if (!selected->get_parent() || !selected->get_parent()->get_parent()) {463path = ".";464} else {465while (selected->get_parent()->get_parent() != get_root()) {466selected = selected->get_parent();467path = selected->get_text(0) + "/" + path;468}469}470471return vformat("\"%s\"", path);472}473474void EditorDebuggerTree::update_icon_max_width() {475add_theme_constant_override("icon_max_width", get_theme_constant("class_icon_size", EditorStringName(Editor)));476}477478String EditorDebuggerTree::get_selected_path() {479if (!get_selected()) {480return "";481}482return get_selected()->get_meta("node_path");483}484485void EditorDebuggerTree::_item_menu_id_pressed(int p_option) {486switch (p_option) {487case ITEM_MENU_SAVE_REMOTE_NODE: {488file_dialog->set_access(EditorFileDialog::ACCESS_RESOURCES);489file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);490491List<String> extensions;492Ref<PackedScene> sd = memnew(PackedScene);493ResourceSaver::get_recognized_extensions(sd, &extensions);494file_dialog->clear_filters();495for (const String &extension : extensions) {496file_dialog->add_filter("*." + extension, extension.to_upper());497}498499String filename = get_selected_path().get_file() + "." + extensions.front()->get().to_lower();500file_dialog->set_current_path(filename);501file_dialog->popup_file_dialog();502} break;503case ITEM_MENU_COPY_NODE_PATH: {504String text = get_selected_path();505if (text.is_empty()) {506return;507} else if (text == "/root") {508text = ".";509} else {510text = text.replace("/root/", "");511int slash = text.find_char('/');512if (slash < 0) {513text = ".";514} else {515text = text.substr(slash + 1);516}517}518DisplayServer::get_singleton()->clipboard_set(text);519} break;520case ITEM_MENU_EXPAND_COLLAPSE: {521TreeItem *s_item = get_selected();522523if (!s_item) {524s_item = get_root();525if (!s_item) {526break;527}528}529530bool collapsed = s_item->is_any_collapsed();531s_item->set_collapsed_recursive(!collapsed);532533ensure_cursor_is_visible();534}535}536}537538void EditorDebuggerTree::_file_selected(const String &p_file) {539if (inspected_object_ids.size() != 1) {540accept->set_text(vformat(TTR("Saving the branch as a scene requires selecting only one node, but you have selected %d nodes."), inspected_object_ids.size()));541accept->popup_centered();542return;543}544545emit_signal(SNAME("save_node"), inspected_object_ids[0], p_file, debugger_id);546}547548549