editor_debugger_tree.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  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_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("multi_selected", callable_mp(this, &EditorDebuggerTree::_scene_tree_selection_changed));
  62. connect("nothing_selected", callable_mp(this, &EditorDebuggerTree::_scene_tree_nothing_selected));
  63. connect("item_collapsed", callable_mp(this, &EditorDebuggerTree::_scene_tree_folded));
  64. connect("item_mouse_selected", callable_mp(this, &EditorDebuggerTree::_scene_tree_rmb_selected));
  65. } break;
  66. case NOTIFICATION_ENTER_TREE: {
  67. update_icon_max_width();
  68. } break;
  69. }
  70. }
  71. void EditorDebuggerTree::_bind_methods() {
  72. ADD_SIGNAL(MethodInfo("objects_selected", PropertyInfo(Variant::ARRAY, "object_ids"), PropertyInfo(Variant::INT, "debugger")));
  73. ADD_SIGNAL(MethodInfo("selection_cleared", PropertyInfo(Variant::INT, "debugger")));
  74. ADD_SIGNAL(MethodInfo("save_node", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "filename"), PropertyInfo(Variant::INT, "debugger")));
  75. ADD_SIGNAL(MethodInfo("open"));
  76. }
  77. void EditorDebuggerTree::_scene_tree_selection_changed(TreeItem *p_item, int p_column, bool p_selected) {
  78. if (updating_scene_tree || !p_item) {
  79. return;
  80. }
  81. uint64_t id = uint64_t(p_item->get_metadata(0));
  82. if (p_selected) {
  83. if (inspected_object_ids.size() == (int)EDITOR_GET("debugger/max_node_selection")) {
  84. selection_surpassed_limit = true;
  85. p_item->deselect(0);
  86. return;
  87. }
  88. if (!inspected_object_ids.has(id)) {
  89. inspected_object_ids.append(id);
  90. }
  91. } else if (inspected_object_ids.has(id)) {
  92. inspected_object_ids.erase(id);
  93. }
  94. if (!notify_selection_queued) {
  95. callable_mp(this, &EditorDebuggerTree::_notify_selection_changed).call_deferred();
  96. notify_selection_queued = true;
  97. }
  98. }
  99. void EditorDebuggerTree::_scene_tree_nothing_selected() {
  100. deselect_all();
  101. inspected_object_ids.clear();
  102. emit_signal(SNAME("selection_cleared"), debugger_id);
  103. }
  104. void EditorDebuggerTree::_notify_selection_changed() {
  105. notify_selection_queued = false;
  106. if (inspected_object_ids.is_empty()) {
  107. emit_signal(SNAME("selection_cleared"), debugger_id);
  108. } else {
  109. emit_signal(SNAME("objects_selected"), inspected_object_ids.duplicate(), debugger_id);
  110. }
  111. if (selection_surpassed_limit) {
  112. selection_surpassed_limit = false;
  113. 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);
  114. }
  115. }
  116. void EditorDebuggerTree::_scene_tree_folded(Object *p_obj) {
  117. if (updating_scene_tree) {
  118. return;
  119. }
  120. TreeItem *item = Object::cast_to<TreeItem>(p_obj);
  121. if (!item) {
  122. return;
  123. }
  124. ObjectID id = ObjectID(uint64_t(item->get_metadata(0)));
  125. if (unfold_cache.has(id)) {
  126. unfold_cache.erase(id);
  127. } else {
  128. unfold_cache.insert(id);
  129. }
  130. }
  131. void EditorDebuggerTree::_scene_tree_rmb_selected(const Vector2 &p_position, MouseButton p_button) {
  132. if (p_button != MouseButton::RIGHT) {
  133. return;
  134. }
  135. TreeItem *item = get_item_at_position(p_position);
  136. if (!item) {
  137. return;
  138. }
  139. item->select(0);
  140. item_menu->clear();
  141. item_menu->add_icon_item(get_editor_theme_icon(SNAME("CreateNewSceneFrom")), TTR("Save Branch as Scene..."), ITEM_MENU_SAVE_REMOTE_NODE);
  142. item_menu->add_icon_item(get_editor_theme_icon(SNAME("CopyNodePath")), TTR("Copy Node Path"), ITEM_MENU_COPY_NODE_PATH);
  143. item_menu->add_icon_item(get_editor_theme_icon(SNAME("Collapse")), TTR("Expand/Collapse Branch"), ITEM_MENU_EXPAND_COLLAPSE);
  144. item_menu->set_position(get_screen_position() + get_local_mouse_position());
  145. item_menu->reset_size();
  146. item_menu->popup();
  147. }
  148. /// Populates inspect_scene_tree given data in nodes as a flat list, encoded depth first.
  149. ///
  150. /// Given a nodes array like [R,A,B,C,D,E] the following Tree will be generated, assuming
  151. /// filter is an empty String, R and A child count are 2, B is 1 and C, D and E are 0.
  152. ///
  153. /// R
  154. /// |-A
  155. /// | |-B
  156. /// | | |-C
  157. /// | |
  158. /// | |-D
  159. /// |
  160. /// |-E
  161. ///
  162. void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int p_debugger) {
  163. set_hide_root(false);
  164. updating_scene_tree = true;
  165. const String last_path = get_selected_path();
  166. const String filter = SceneTreeDock::get_singleton()->get_filter();
  167. LocalVector<TreeItem *> select_items;
  168. bool hide_filtered_out_parents = EDITOR_GET("docks/scene_tree/hide_filtered_out_parents");
  169. bool should_scroll = scrolling_to_item || filter != last_filter;
  170. scrolling_to_item = false;
  171. TreeItem *scroll_item = nullptr;
  172. TypedArray<uint64_t> ids_present;
  173. // Nodes are in a flatten list, depth first. Use a stack of parents, avoid recursion.
  174. List<ParentItem> parents;
  175. for (const SceneDebuggerTree::RemoteNode &node : p_tree->nodes) {
  176. TreeItem *parent = nullptr;
  177. Pair<TreeItem *, TreeItem *> move_from_to;
  178. if (parents.size()) { // Find last parent.
  179. ParentItem &p = parents.front()->get();
  180. parent = p.tree_item;
  181. if (!(--p.child_count)) { // If no child left, remove it.
  182. parents.pop_front();
  183. if (hide_filtered_out_parents && !filter.is_subsequence_ofn(parent->get_text(0))) {
  184. if (parent == get_root()) {
  185. set_hide_root(true);
  186. } else {
  187. move_from_to.first = parent;
  188. // Find the closest ancestor that matches the filter.
  189. for (const ParentItem p2 : parents) {
  190. move_from_to.second = p2.tree_item;
  191. if (p2.matches_filter || move_from_to.second == get_root()) {
  192. break;
  193. }
  194. }
  195. if (!move_from_to.second) {
  196. move_from_to.second = get_root();
  197. }
  198. }
  199. }
  200. }
  201. }
  202. // Add this node.
  203. TreeItem *item = create_item(parent);
  204. item->set_text(0, node.name);
  205. if (node.scene_file_path.is_empty()) {
  206. item->set_tooltip_text(0, node.name + "\n" + TTR("Type:") + " " + node.type_name);
  207. } else {
  208. item->set_tooltip_text(0, node.name + "\n" + TTR("Instance:") + " " + node.scene_file_path + "\n" + TTR("Type:") + " " + node.type_name);
  209. }
  210. Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(node.type_name, "");
  211. if (icon.is_valid()) {
  212. item->set_icon(0, icon);
  213. }
  214. item->set_metadata(0, node.id);
  215. String current_path;
  216. if (parent) {
  217. current_path += (String)parent->get_meta("node_path");
  218. // Set current item as collapsed if necessary (root is never collapsed).
  219. if (!unfold_cache.has(node.id)) {
  220. item->set_collapsed(true);
  221. }
  222. }
  223. item->set_meta("node_path", current_path + "/" + item->get_text(0));
  224. // Select previously selected nodes.
  225. if (debugger_id == p_debugger) { // Can use remote id.
  226. if (inspected_object_ids.has(uint64_t(node.id))) {
  227. ids_present.append(node.id);
  228. if (selection_uncollapse_all) {
  229. selection_uncollapse_all = false;
  230. // Temporarily set to `false`, to allow caching the unfolds.
  231. updating_scene_tree = false;
  232. item->uncollapse_tree();
  233. updating_scene_tree = true;
  234. }
  235. select_items.push_back(item);
  236. if (should_scroll) {
  237. scroll_item = item;
  238. }
  239. }
  240. } else if (last_path == (String)item->get_meta("node_path")) { // Must use path.
  241. updating_scene_tree = false; // Force emission of new selections.
  242. select_items.push_back(item);
  243. if (should_scroll) {
  244. scroll_item = item;
  245. }
  246. updating_scene_tree = true;
  247. }
  248. // Add buttons.
  249. const Color remote_button_color = Color(1, 1, 1, 0.8);
  250. if (!node.scene_file_path.is_empty()) {
  251. String node_scene_file_path = node.scene_file_path;
  252. Ref<Texture2D> button_icon = get_editor_theme_icon(SNAME("InstanceOptions"));
  253. 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);
  254. item->set_meta("scene_file_path", node_scene_file_path);
  255. item->add_button(0, button_icon, BUTTON_SUBSCENE, false, tooltip);
  256. item->set_button_color(0, item->get_button_count(0) - 1, remote_button_color);
  257. }
  258. if (node.view_flags & SceneDebuggerTree::RemoteNode::VIEW_HAS_VISIBLE_METHOD) {
  259. bool node_visible = node.view_flags & SceneDebuggerTree::RemoteNode::VIEW_VISIBLE;
  260. bool node_visible_in_tree = node.view_flags & SceneDebuggerTree::RemoteNode::VIEW_VISIBLE_IN_TREE;
  261. Ref<Texture2D> button_icon = get_editor_theme_icon(node_visible ? SNAME("GuiVisibilityVisible") : SNAME("GuiVisibilityHidden"));
  262. String tooltip = TTR("Toggle Visibility");
  263. item->set_meta("visible", node_visible);
  264. item->add_button(0, button_icon, BUTTON_VISIBILITY, false, tooltip);
  265. if (ClassDB::is_parent_class(node.type_name, "CanvasItem") || ClassDB::is_parent_class(node.type_name, "Node3D")) {
  266. item->set_button_color(0, item->get_button_count(0) - 1, node_visible_in_tree ? remote_button_color : Color(1, 1, 1, 0.6));
  267. } else {
  268. item->set_button_color(0, item->get_button_count(0) - 1, remote_button_color);
  269. }
  270. }
  271. // Add in front of the parents stack if children are expected.
  272. if (node.child_count) {
  273. parents.push_front(ParentItem(item, node.child_count, filter.is_subsequence_ofn(item->get_text(0))));
  274. } else {
  275. // Apply filters.
  276. while (parent) {
  277. const bool had_siblings = item->get_prev() || item->get_next();
  278. if (filter.is_subsequence_ofn(item->get_text(0))) {
  279. break; // Filter matches, must survive.
  280. }
  281. if (select_items.has(item) || scroll_item == item) {
  282. select_items.resize(select_items.size() - 1);
  283. scroll_item = nullptr;
  284. }
  285. parent->remove_child(item);
  286. memdelete(item);
  287. if (had_siblings) {
  288. break; // Parent must survive.
  289. }
  290. item = parent;
  291. parent = item->get_parent();
  292. // Check if parent expects more children.
  293. for (ParentItem &pair : parents) {
  294. if (pair.tree_item == item) {
  295. parent = nullptr;
  296. break; // Might have more children.
  297. }
  298. }
  299. }
  300. }
  301. // Move all children to the ancestor that matches the filter, if picked.
  302. if (move_from_to.first) {
  303. TreeItem *from = move_from_to.first;
  304. TypedArray<TreeItem> children = from->get_children();
  305. if (!children.is_empty()) {
  306. for (Variant &c : children) {
  307. TreeItem *ti = Object::cast_to<TreeItem>(c);
  308. from->remove_child(ti);
  309. move_from_to.second->add_child(ti);
  310. }
  311. from->get_parent()->remove_child(from);
  312. memdelete(from);
  313. if (select_items.has(from) || scroll_item == from) {
  314. select_items.erase(from);
  315. scroll_item = nullptr;
  316. }
  317. }
  318. }
  319. }
  320. inspected_object_ids = ids_present;
  321. debugger_id = p_debugger; // Needed by hook, could be avoided if every debugger had its own tree.
  322. for (TreeItem *item : select_items) {
  323. item->select(0);
  324. }
  325. if (scroll_item) {
  326. scroll_to_item(scroll_item, false);
  327. }
  328. last_filter = filter;
  329. updating_scene_tree = false;
  330. }
  331. void EditorDebuggerTree::select_nodes(const TypedArray<int64_t> &p_ids) {
  332. // Manually select, as the tree control may be out-of-date for some reason (e.g. not shown yet).
  333. selection_uncollapse_all = true;
  334. inspected_object_ids = p_ids;
  335. scrolling_to_item = true;
  336. if (!updating_scene_tree) {
  337. // Request a tree refresh.
  338. EditorDebuggerNode::get_singleton()->request_remote_tree();
  339. }
  340. // Set the value immediately, so no update flooding happens and causes a crash.
  341. updating_scene_tree = true;
  342. }
  343. void EditorDebuggerTree::clear_selection() {
  344. inspected_object_ids.clear();
  345. if (!updating_scene_tree) {
  346. // Request a tree refresh.
  347. EditorDebuggerNode::get_singleton()->request_remote_tree();
  348. }
  349. // Set the value immediately, so no update flooding happens and causes a crash.
  350. updating_scene_tree = true;
  351. }
  352. Variant EditorDebuggerTree::get_drag_data(const Point2 &p_point) {
  353. if (get_button_id_at_position(p_point) != -1) {
  354. return Variant();
  355. }
  356. TreeItem *selected = get_selected();
  357. if (!selected) {
  358. return Variant();
  359. }
  360. String path = selected->get_text(0);
  361. const int icon_size = get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
  362. HBoxContainer *hb = memnew(HBoxContainer);
  363. TextureRect *tf = memnew(TextureRect);
  364. tf->set_texture(selected->get_icon(0));
  365. tf->set_custom_minimum_size(Size2(icon_size, icon_size));
  366. tf->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
  367. tf->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
  368. hb->add_child(tf);
  369. Label *label = memnew(Label(path));
  370. hb->add_child(label);
  371. set_drag_preview(hb);
  372. if (!selected->get_parent() || !selected->get_parent()->get_parent()) {
  373. path = ".";
  374. } else {
  375. while (selected->get_parent()->get_parent() != get_root()) {
  376. selected = selected->get_parent();
  377. path = selected->get_text(0) + "/" + path;
  378. }
  379. }
  380. return vformat("\"%s\"", path);
  381. }
  382. void EditorDebuggerTree::update_icon_max_width() {
  383. add_theme_constant_override("icon_max_width", get_theme_constant("class_icon_size", EditorStringName(Editor)));
  384. }
  385. String EditorDebuggerTree::get_selected_path() {
  386. if (!get_selected()) {
  387. return "";
  388. }
  389. return get_selected()->get_meta("node_path");
  390. }
  391. void EditorDebuggerTree::_item_menu_id_pressed(int p_option) {
  392. switch (p_option) {
  393. case ITEM_MENU_SAVE_REMOTE_NODE: {
  394. file_dialog->set_access(EditorFileDialog::ACCESS_RESOURCES);
  395. file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
  396. List<String> extensions;
  397. Ref<PackedScene> sd = memnew(PackedScene);
  398. ResourceSaver::get_recognized_extensions(sd, &extensions);
  399. file_dialog->clear_filters();
  400. for (const String &extension : extensions) {
  401. file_dialog->add_filter("*." + extension, extension.to_upper());
  402. }
  403. String filename = get_selected_path().get_file() + "." + extensions.front()->get().to_lower();
  404. file_dialog->set_current_path(filename);
  405. file_dialog->popup_file_dialog();
  406. } break;
  407. case ITEM_MENU_COPY_NODE_PATH: {
  408. String text = get_selected_path();
  409. if (text.is_empty()) {
  410. return;
  411. } else if (text == "/root") {
  412. text = ".";
  413. } else {
  414. text = text.replace("/root/", "");
  415. int slash = text.find_char('/');
  416. if (slash < 0) {
  417. text = ".";
  418. } else {
  419. text = text.substr(slash + 1);
  420. }
  421. }
  422. DisplayServer::get_singleton()->clipboard_set(text);
  423. } break;
  424. case ITEM_MENU_EXPAND_COLLAPSE: {
  425. TreeItem *s_item = get_selected();
  426. if (!s_item) {
  427. s_item = get_root();
  428. if (!s_item) {
  429. break;
  430. }
  431. }
  432. bool collapsed = s_item->is_any_collapsed();
  433. s_item->set_collapsed_recursive(!collapsed);
  434. ensure_cursor_is_visible();
  435. }
  436. }
  437. }
  438. void EditorDebuggerTree::_file_selected(const String &p_file) {
  439. if (inspected_object_ids.size() != 1) {
  440. 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()));
  441. accept->popup_centered();
  442. return;
  443. }
  444. emit_signal(SNAME("save_node"), inspected_object_ids[0], p_file, debugger_id);
  445. }