editor_debugger_tree.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. /**************************************************************************/
  2. /* editor_debugger_tree.cpp */
  3. /**************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /**************************************************************************/
  8. /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
  9. /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /**************************************************************************/
  30. #include "editor_debugger_tree.h"
  31. #include "editor/debugger/editor_debugger_node.h"
  32. #include "editor/docks/scene_tree_dock.h"
  33. #include "editor/editor_node.h"
  34. #include "editor/editor_string_names.h"
  35. #include "editor/gui/editor_file_dialog.h"
  36. #include "editor/gui/editor_toaster.h"
  37. #include "editor/settings/editor_settings.h"
  38. #include "scene/debugger/scene_debugger.h"
  39. #include "scene/gui/texture_rect.h"
  40. #include "scene/resources/packed_scene.h"
  41. #include "servers/display/display_server.h"
  42. EditorDebuggerTree::EditorDebuggerTree() {
  43. set_v_size_flags(SIZE_EXPAND_FILL);
  44. set_allow_rmb_select(true);
  45. set_select_mode(SELECT_MULTI);
  46. // Popup
  47. item_menu = memnew(PopupMenu);
  48. item_menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorDebuggerTree::_item_menu_id_pressed));
  49. add_child(item_menu);
  50. // File Dialog
  51. file_dialog = memnew(EditorFileDialog);
  52. file_dialog->connect("file_selected", callable_mp(this, &EditorDebuggerTree::_file_selected));
  53. add_child(file_dialog);
  54. accept = memnew(AcceptDialog);
  55. add_child(accept);
  56. }
  57. void EditorDebuggerTree::_notification(int p_what) {
  58. switch (p_what) {
  59. case NOTIFICATION_POSTINITIALIZE: {
  60. set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
  61. connect("cell_selected", callable_mp(this, &EditorDebuggerTree::_scene_tree_selected));
  62. connect("multi_selected", callable_mp(this, &EditorDebuggerTree::_scene_tree_selection_changed));
  63. connect("nothing_selected", callable_mp(this, &EditorDebuggerTree::_scene_tree_nothing_selected));
  64. connect("item_collapsed", callable_mp(this, &EditorDebuggerTree::_scene_tree_folded));
  65. connect("item_mouse_selected", callable_mp(this, &EditorDebuggerTree::_scene_tree_rmb_selected));
  66. } break;
  67. case NOTIFICATION_READY: {
  68. update_icon_max_width();
  69. } break;
  70. }
  71. }
  72. void EditorDebuggerTree::_bind_methods() {
  73. ADD_SIGNAL(MethodInfo("objects_selected", PropertyInfo(Variant::ARRAY, "object_ids"), PropertyInfo(Variant::INT, "debugger")));
  74. ADD_SIGNAL(MethodInfo("selection_cleared", PropertyInfo(Variant::INT, "debugger")));
  75. ADD_SIGNAL(MethodInfo("save_node", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "filename"), PropertyInfo(Variant::INT, "debugger")));
  76. ADD_SIGNAL(MethodInfo("open"));
  77. }
  78. void EditorDebuggerTree::_scene_tree_selected() {
  79. TreeItem *item = get_selected();
  80. if (!item) {
  81. return;
  82. }
  83. if (!inspected_object_ids.is_empty()) {
  84. inspected_object_ids.clear();
  85. deselect_all();
  86. item->select(0);
  87. }
  88. uint64_t id = uint64_t(item->get_metadata(0));
  89. inspected_object_ids.append(id);
  90. if (!notify_selection_queued) {
  91. callable_mp(this, &EditorDebuggerTree::_notify_selection_changed).call_deferred();
  92. notify_selection_queued = true;
  93. }
  94. }
  95. void EditorDebuggerTree::_scene_tree_selection_changed(TreeItem *p_item, int p_column, bool p_selected) {
  96. if (updating_scene_tree || !p_item) {
  97. return;
  98. }
  99. uint64_t id = uint64_t(p_item->get_metadata(0));
  100. if (p_selected) {
  101. if (inspected_object_ids.size() == (int)EDITOR_GET("debugger/max_node_selection")) {
  102. selection_surpassed_limit = true;
  103. p_item->deselect(0);
  104. } else if (!inspected_object_ids.has(id)) {
  105. inspected_object_ids.append(id);
  106. }
  107. } else if (inspected_object_ids.has(id)) {
  108. inspected_object_ids.erase(id);
  109. }
  110. if (!notify_selection_queued) {
  111. callable_mp(this, &EditorDebuggerTree::_notify_selection_changed).call_deferred();
  112. notify_selection_queued = true;
  113. }
  114. }
  115. void EditorDebuggerTree::_scene_tree_nothing_selected() {
  116. deselect_all();
  117. inspected_object_ids.clear();
  118. emit_signal(SNAME("selection_cleared"), debugger_id);
  119. }
  120. void EditorDebuggerTree::_notify_selection_changed() {
  121. notify_selection_queued = false;
  122. if (inspected_object_ids.is_empty()) {
  123. emit_signal(SNAME("selection_cleared"), debugger_id);
  124. } else {
  125. emit_signal(SNAME("objects_selected"), inspected_object_ids.duplicate(), debugger_id);
  126. }
  127. if (selection_surpassed_limit) {
  128. selection_surpassed_limit = false;
  129. EditorToaster::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);
  130. }
  131. }
  132. void EditorDebuggerTree::_scene_tree_folded(Object *p_obj) {
  133. if (updating_scene_tree) {
  134. return;
  135. }
  136. TreeItem *item = Object::cast_to<TreeItem>(p_obj);
  137. if (!item) {
  138. return;
  139. }
  140. ObjectID id = ObjectID(uint64_t(item->get_metadata(0)));
  141. if (unfold_cache.has(id)) {
  142. unfold_cache.erase(id);
  143. } else {
  144. unfold_cache.insert(id);
  145. }
  146. }
  147. void EditorDebuggerTree::_scene_tree_rmb_selected(const Vector2 &p_position, MouseButton p_button) {
  148. if (p_button != MouseButton::RIGHT) {
  149. return;
  150. }
  151. TreeItem *item = get_item_at_position(p_position);
  152. if (!item) {
  153. return;
  154. }
  155. item->select(0);
  156. item_menu->clear();
  157. item_menu->add_icon_item(get_editor_theme_icon(SNAME("CreateNewSceneFrom")), TTR("Save Branch as Scene..."), ITEM_MENU_SAVE_REMOTE_NODE);
  158. item_menu->add_icon_item(get_editor_theme_icon(SNAME("CopyNodePath")), TTR("Copy Node Path"), ITEM_MENU_COPY_NODE_PATH);
  159. item_menu->add_icon_item(get_editor_theme_icon(SNAME("Collapse")), TTR("Expand/Collapse Branch"), ITEM_MENU_EXPAND_COLLAPSE);
  160. item_menu->set_position(get_screen_position() + get_local_mouse_position());
  161. item_menu->reset_size();
  162. item_menu->popup();
  163. }
  164. /// Populates inspect_scene_tree given data in nodes as a flat list, encoded depth first.
  165. ///
  166. /// Given a nodes array like [R,A,B,C,D,E] the following Tree will be generated, assuming
  167. /// filter is an empty String, R and A child count are 2, B is 1 and C, D and E are 0.
  168. ///
  169. /// R
  170. /// |-A
  171. /// | |-B
  172. /// | | |-C
  173. /// | |
  174. /// | |-D
  175. /// |
  176. /// |-E
  177. ///
  178. void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int p_debugger) {
  179. set_hide_root(false);
  180. updating_scene_tree = true;
  181. const String last_path = get_selected_path();
  182. const String filter = SceneTreeDock::get_singleton()->get_filter();
  183. LocalVector<TreeItem *> select_items;
  184. bool hide_filtered_out_parents = EDITOR_GET("docks/scene_tree/hide_filtered_out_parents");
  185. bool should_scroll = scrolling_to_item || filter != last_filter;
  186. scrolling_to_item = false;
  187. TreeItem *scroll_item = nullptr;
  188. TypedArray<uint64_t> ids_present;
  189. // Nodes are in a flatten list, depth first. Use a stack of parents, avoid recursion.
  190. List<ParentItem> parents;
  191. for (const SceneDebuggerTree::RemoteNode &node : p_tree->nodes) {
  192. TreeItem *parent = nullptr;
  193. Pair<TreeItem *, TreeItem *> move_from_to;
  194. if (parents.size()) { // Find last parent.
  195. ParentItem &p = parents.front()->get();
  196. parent = p.tree_item;
  197. if (!(--p.child_count)) { // If no child left, remove it.
  198. parents.pop_front();
  199. if (hide_filtered_out_parents && !filter.is_subsequence_ofn(parent->get_text(0))) {
  200. if (parent == get_root()) {
  201. set_hide_root(true);
  202. } else {
  203. move_from_to.first = parent;
  204. // Find the closest ancestor that matches the filter.
  205. for (const ParentItem p2 : parents) {
  206. move_from_to.second = p2.tree_item;
  207. if (p2.matches_filter || move_from_to.second == get_root()) {
  208. break;
  209. }
  210. }
  211. if (!move_from_to.second) {
  212. move_from_to.second = get_root();
  213. }
  214. }
  215. }
  216. }
  217. }
  218. // Add this node.
  219. TreeItem *item = create_item(parent);
  220. item->set_text(0, node.name);
  221. if (node.scene_file_path.is_empty()) {
  222. item->set_tooltip_text(0, node.name + "\n" + TTR("Type:") + " " + node.type_name);
  223. } else {
  224. item->set_tooltip_text(0, node.name + "\n" + TTR("Instance:") + " " + node.scene_file_path + "\n" + TTR("Type:") + " " + node.type_name);
  225. }
  226. Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(node.type_name);
  227. if (icon.is_valid()) {
  228. item->set_icon(0, icon);
  229. }
  230. item->set_metadata(0, node.id);
  231. String current_path;
  232. if (parent) {
  233. current_path += (String)parent->get_meta("node_path");
  234. // Set current item as collapsed if necessary (root is never collapsed).
  235. if (!unfold_cache.has(node.id)) {
  236. item->set_collapsed(true);
  237. }
  238. }
  239. item->set_meta("node_path", current_path + "/" + item->get_text(0));
  240. // Select previously selected nodes.
  241. if (debugger_id == p_debugger) { // Can use remote id.
  242. if (inspected_object_ids.has(uint64_t(node.id))) {
  243. ids_present.append(node.id);
  244. if (selection_uncollapse_all) {
  245. selection_uncollapse_all = false;
  246. // Temporarily set to `false`, to allow caching the unfolds.
  247. updating_scene_tree = false;
  248. item->uncollapse_tree();
  249. updating_scene_tree = true;
  250. }
  251. select_items.push_back(item);
  252. if (should_scroll) {
  253. scroll_item = item;
  254. }
  255. }
  256. } else if (last_path == (String)item->get_meta("node_path")) { // Must use path.
  257. updating_scene_tree = false; // Force emission of new selections.
  258. select_items.push_back(item);
  259. if (should_scroll) {
  260. scroll_item = item;
  261. }
  262. updating_scene_tree = true;
  263. }
  264. // Add buttons.
  265. const Color remote_button_color = Color(1, 1, 1, 0.8);
  266. if (!node.scene_file_path.is_empty()) {
  267. String node_scene_file_path = node.scene_file_path;
  268. Ref<Texture2D> button_icon = get_editor_theme_icon(SNAME("InstanceOptions"));
  269. String 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);
  270. item->set_meta("scene_file_path", node_scene_file_path);
  271. item->add_button(0, button_icon, BUTTON_SUBSCENE, false, tooltip);
  272. item->set_button_color(0, item->get_button_count(0) - 1, remote_button_color);
  273. }
  274. if (node.view_flags & SceneDebuggerTree::RemoteNode::VIEW_HAS_VISIBLE_METHOD) {
  275. bool node_visible = node.view_flags & SceneDebuggerTree::RemoteNode::VIEW_VISIBLE;
  276. bool node_visible_in_tree = node.view_flags & SceneDebuggerTree::RemoteNode::VIEW_VISIBLE_IN_TREE;
  277. Ref<Texture2D> button_icon = get_editor_theme_icon(node_visible ? SNAME("GuiVisibilityVisible") : SNAME("GuiVisibilityHidden"));
  278. String tooltip = TTR("Toggle Visibility");
  279. item->set_meta("visible", node_visible);
  280. item->add_button(0, button_icon, BUTTON_VISIBILITY, false, tooltip);
  281. if (ClassDB::is_parent_class(node.type_name, "CanvasItem") || ClassDB::is_parent_class(node.type_name, "Node3D")) {
  282. item->set_button_color(0, item->get_button_count(0) - 1, node_visible_in_tree ? remote_button_color : Color(1, 1, 1, 0.6));
  283. } else {
  284. item->set_button_color(0, item->get_button_count(0) - 1, remote_button_color);
  285. }
  286. }
  287. // Add in front of the parents stack if children are expected.
  288. if (node.child_count) {
  289. parents.push_front(ParentItem(item, node.child_count, filter.is_subsequence_ofn(item->get_text(0))));
  290. } else {
  291. // Apply filters.
  292. while (parent) {
  293. const bool had_siblings = item->get_prev() || item->get_next();
  294. if (filter.is_subsequence_ofn(item->get_text(0))) {
  295. break; // Filter matches, must survive.
  296. }
  297. if (select_items.has(item) || scroll_item == item) {
  298. select_items.resize(select_items.size() - 1);
  299. scroll_item = nullptr;
  300. }
  301. parent->remove_child(item);
  302. memdelete(item);
  303. if (had_siblings) {
  304. break; // Parent must survive.
  305. }
  306. item = parent;
  307. parent = item->get_parent();
  308. // Check if parent expects more children.
  309. for (ParentItem &pair : parents) {
  310. if (pair.tree_item == item) {
  311. parent = nullptr;
  312. break; // Might have more children.
  313. }
  314. }
  315. }
  316. }
  317. // Move all children to the ancestor that matches the filter, if picked.
  318. if (move_from_to.first) {
  319. TreeItem *from = move_from_to.first;
  320. TypedArray<TreeItem> children = from->get_children();
  321. if (!children.is_empty()) {
  322. for (Variant &c : children) {
  323. TreeItem *ti = Object::cast_to<TreeItem>(c);
  324. from->remove_child(ti);
  325. move_from_to.second->add_child(ti);
  326. }
  327. from->get_parent()->remove_child(from);
  328. memdelete(from);
  329. if (select_items.has(from) || scroll_item == from) {
  330. select_items.erase(from);
  331. scroll_item = nullptr;
  332. }
  333. }
  334. }
  335. }
  336. inspected_object_ids = ids_present;
  337. debugger_id = p_debugger; // Needed by hook, could be avoided if every debugger had its own tree.
  338. for (TreeItem *item : select_items) {
  339. item->select(0);
  340. }
  341. if (scroll_item) {
  342. scroll_to_item(scroll_item, false);
  343. }
  344. if (new_session) {
  345. // Some nodes may stay selected between sessions.
  346. // Make sure the inspector shows them properly.
  347. if (!notify_selection_queued) {
  348. callable_mp(this, &EditorDebuggerTree::_notify_selection_changed).call_deferred();
  349. notify_selection_queued = true;
  350. }
  351. new_session = false;
  352. }
  353. last_filter = filter;
  354. updating_scene_tree = false;
  355. }
  356. void EditorDebuggerTree::select_nodes(const TypedArray<int64_t> &p_ids) {
  357. // Manually select, as the tree control may be out-of-date for some reason (e.g. not shown yet).
  358. selection_uncollapse_all = true;
  359. inspected_object_ids = p_ids;
  360. scrolling_to_item = true;
  361. if (!updating_scene_tree) {
  362. // Request a tree refresh.
  363. EditorDebuggerNode::get_singleton()->request_remote_tree();
  364. }
  365. // Set the value immediately, so no update flooding happens and causes a crash.
  366. updating_scene_tree = true;
  367. }
  368. void EditorDebuggerTree::clear_selection() {
  369. inspected_object_ids.clear();
  370. if (!updating_scene_tree) {
  371. // Request a tree refresh.
  372. EditorDebuggerNode::get_singleton()->request_remote_tree();
  373. }
  374. // Set the value immediately, so no update flooding happens and causes a crash.
  375. updating_scene_tree = true;
  376. }
  377. Variant EditorDebuggerTree::get_drag_data(const Point2 &p_point) {
  378. if (get_button_id_at_position(p_point) != -1) {
  379. return Variant();
  380. }
  381. TreeItem *selected = get_selected();
  382. if (!selected) {
  383. return Variant();
  384. }
  385. String path = selected->get_text(0);
  386. const int icon_size = get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
  387. HBoxContainer *hb = memnew(HBoxContainer);
  388. TextureRect *tf = memnew(TextureRect);
  389. tf->set_texture(selected->get_icon(0));
  390. tf->set_custom_minimum_size(Size2(icon_size, icon_size));
  391. tf->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
  392. tf->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
  393. hb->add_child(tf);
  394. Label *label = memnew(Label(path));
  395. hb->add_child(label);
  396. set_drag_preview(hb);
  397. if (!selected->get_parent() || !selected->get_parent()->get_parent()) {
  398. path = ".";
  399. } else {
  400. while (selected->get_parent()->get_parent() != get_root()) {
  401. selected = selected->get_parent();
  402. path = selected->get_text(0) + "/" + path;
  403. }
  404. }
  405. return vformat("\"%s\"", path);
  406. }
  407. void EditorDebuggerTree::update_icon_max_width() {
  408. add_theme_constant_override("icon_max_width", get_theme_constant("class_icon_size", EditorStringName(Editor)));
  409. }
  410. String EditorDebuggerTree::get_selected_path() {
  411. if (!get_selected()) {
  412. return "";
  413. }
  414. return get_selected()->get_meta("node_path");
  415. }
  416. void EditorDebuggerTree::_item_menu_id_pressed(int p_option) {
  417. switch (p_option) {
  418. case ITEM_MENU_SAVE_REMOTE_NODE: {
  419. file_dialog->set_access(EditorFileDialog::ACCESS_RESOURCES);
  420. file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
  421. List<String> extensions;
  422. Ref<PackedScene> sd = memnew(PackedScene);
  423. ResourceSaver::get_recognized_extensions(sd, &extensions);
  424. file_dialog->clear_filters();
  425. for (const String &extension : extensions) {
  426. file_dialog->add_filter("*." + extension, extension.to_upper());
  427. }
  428. String filename = get_selected_path().get_file() + "." + extensions.front()->get().to_lower();
  429. file_dialog->set_current_path(filename);
  430. file_dialog->popup_file_dialog();
  431. } break;
  432. case ITEM_MENU_COPY_NODE_PATH: {
  433. String text = get_selected_path();
  434. if (text.is_empty()) {
  435. return;
  436. } else if (text == "/root") {
  437. text = ".";
  438. } else {
  439. text = text.replace("/root/", "");
  440. int slash = text.find_char('/');
  441. if (slash < 0) {
  442. text = ".";
  443. } else {
  444. text = text.substr(slash + 1);
  445. }
  446. }
  447. DisplayServer::get_singleton()->clipboard_set(text);
  448. } break;
  449. case ITEM_MENU_EXPAND_COLLAPSE: {
  450. TreeItem *s_item = get_selected();
  451. if (!s_item) {
  452. s_item = get_root();
  453. if (!s_item) {
  454. break;
  455. }
  456. }
  457. bool collapsed = s_item->is_any_collapsed();
  458. s_item->set_collapsed_recursive(!collapsed);
  459. ensure_cursor_is_visible();
  460. }
  461. }
  462. }
  463. void EditorDebuggerTree::_file_selected(const String &p_file) {
  464. if (inspected_object_ids.size() != 1) {
  465. accept->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()));
  466. accept->popup_centered();
  467. return;
  468. }
  469. emit_signal(SNAME("save_node"), inspected_object_ids[0], p_file, debugger_id);
  470. }