2020-02-07 02:52:05 +01:00
/**************************************************************************/
/* editor_debugger_tree.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
# include "editor_debugger_tree.h"
# include "editor/editor_node.h"
2024-05-06 17:20:59 +02:00
# include "editor/editor_string_names.h"
2023-04-07 18:59:49 +02:00
# include "editor/gui/editor_file_dialog.h"
2022-02-12 02:46:22 +01:00
# include "editor/scene_tree_dock.h"
2020-02-07 02:52:05 +01:00
# include "scene/debugger/scene_debugger.h"
2022-11-19 12:45:49 +01:00
# include "scene/gui/texture_rect.h"
2020-02-07 02:52:05 +01:00
# include "scene/resources/packed_scene.h"
2020-03-03 14:36:29 +01:00
# include "servers/display_server.h"
2020-02-07 02:52:05 +01:00
EditorDebuggerTree : : EditorDebuggerTree ( ) {
set_v_size_flags ( SIZE_EXPAND_FILL ) ;
set_allow_rmb_select ( true ) ;
// Popup
item_menu = memnew ( PopupMenu ) ;
2024-05-14 14:13:31 +02:00
item_menu - > connect ( SceneStringName ( id_pressed ) , callable_mp ( this , & EditorDebuggerTree : : _item_menu_id_pressed ) ) ;
2020-02-07 02:52:05 +01:00
add_child ( item_menu ) ;
// File Dialog
file_dialog = memnew ( EditorFileDialog ) ;
2020-02-21 18:28:45 +01:00
file_dialog - > connect ( " file_selected " , callable_mp ( this , & EditorDebuggerTree : : _file_selected ) ) ;
2020-02-07 02:52:05 +01:00
add_child ( file_dialog ) ;
}
void EditorDebuggerTree : : _notification ( int p_what ) {
2022-02-16 00:52:32 +01:00
switch ( p_what ) {
case NOTIFICATION_POSTINITIALIZE : {
2024-03-29 17:44:49 +01:00
set_auto_translate_mode ( AUTO_TRANSLATE_MODE_DISABLED ) ;
2022-02-16 00:52:32 +01:00
connect ( " cell_selected " , callable_mp ( this , & EditorDebuggerTree : : _scene_tree_selected ) ) ;
connect ( " item_collapsed " , callable_mp ( this , & EditorDebuggerTree : : _scene_tree_folded ) ) ;
2021-09-18 09:33:18 +02:00
connect ( " item_mouse_selected " , callable_mp ( this , & EditorDebuggerTree : : _scene_tree_rmb_selected ) ) ;
2022-02-16 00:52:32 +01:00
} break ;
2024-05-06 17:20:59 +02:00
case NOTIFICATION_ENTER_TREE : {
update_icon_max_width ( ) ;
} break ;
2020-02-07 02:52:05 +01:00
}
}
void EditorDebuggerTree : : _bind_methods ( ) {
ADD_SIGNAL ( MethodInfo ( " object_selected " , PropertyInfo ( Variant : : INT , " object_id " ) , PropertyInfo ( Variant : : INT , " debugger " ) ) ) ;
ADD_SIGNAL ( MethodInfo ( " save_node " , PropertyInfo ( Variant : : INT , " object_id " ) , PropertyInfo ( Variant : : STRING , " filename " ) , PropertyInfo ( Variant : : INT , " debugger " ) ) ) ;
2022-08-31 00:52:05 +02:00
ADD_SIGNAL ( MethodInfo ( " open " ) ) ;
2020-02-07 02:52:05 +01:00
}
void EditorDebuggerTree : : _scene_tree_selected ( ) {
if ( updating_scene_tree ) {
return ;
}
TreeItem * item = get_selected ( ) ;
if ( ! item ) {
return ;
}
inspected_object_id = uint64_t ( item - > get_metadata ( 0 ) ) ;
2021-07-17 23:22:52 +02:00
emit_signal ( SNAME ( " object_selected " ) , inspected_object_id , debugger_id ) ;
2020-02-07 02:52:05 +01:00
}
void EditorDebuggerTree : : _scene_tree_folded ( Object * p_obj ) {
if ( updating_scene_tree ) {
return ;
}
TreeItem * item = Object : : cast_to < TreeItem > ( p_obj ) ;
2020-05-14 16:41:43 +02:00
if ( ! item ) {
2020-02-07 02:52:05 +01:00
return ;
2020-05-14 16:41:43 +02:00
}
2020-02-07 02:52:05 +01:00
ObjectID id = ObjectID ( uint64_t ( item - > get_metadata ( 0 ) ) ) ;
if ( unfold_cache . has ( id ) ) {
unfold_cache . erase ( id ) ;
} else {
unfold_cache . insert ( id ) ;
}
}
2021-09-18 09:33:18 +02:00
void EditorDebuggerTree : : _scene_tree_rmb_selected ( const Vector2 & p_position , MouseButton p_button ) {
if ( p_button ! = MouseButton : : RIGHT ) {
return ;
}
2020-02-07 02:52:05 +01:00
TreeItem * item = get_item_at_position ( p_position ) ;
2020-05-14 16:41:43 +02:00
if ( ! item ) {
2020-02-07 02:52:05 +01:00
return ;
2020-05-14 16:41:43 +02:00
}
2020-02-07 02:52:05 +01:00
item - > select ( 0 ) ;
item_menu - > clear ( ) ;
2023-08-13 02:33:39 +02:00
item_menu - > add_icon_item ( get_editor_theme_icon ( SNAME ( " CreateNewSceneFrom " ) ) , TTR ( " Save Branch as Scene " ) , ITEM_MENU_SAVE_REMOTE_NODE ) ;
item_menu - > add_icon_item ( get_editor_theme_icon ( SNAME ( " CopyNodePath " ) ) , TTR ( " Copy Node Path " ) , ITEM_MENU_COPY_NODE_PATH ) ;
2024-07-12 15:33:34 +02:00
i tem_menu - > add_icon_item ( get_editor_theme_icon ( SNAME ( " Collapse " ) ) , TTR ( " Expand/Collapse Branch " ) , ITEM_MENU_EXPAND_COLLAPSE ) ;
2021-08-31 17:43:35 +02:00
item_menu - > set_position ( get_screen_position ( ) + get_local_mouse_position ( ) ) ;
2022-12-25 13:56:27 +01:00
item_menu - > reset_size ( ) ;
2020-02-07 02:52:05 +01:00
item_menu - > popup ( ) ;
}
/// Populates inspect_scene_tree given data in nodes as a flat list, encoded depth first.
///
/// Given a nodes array like [R,A,B,C,D,E] the following Tree will be generated, assuming
/// filter is an empty String, R and A child count are 2, B is 1 and C, D and E are 0.
///
/// R
/// |-A
/// | |-B
/// | | |-C
/// | |
/// | |-D
/// |
/// |-E
///
void EditorDebuggerTree : : update_scene_tree ( const SceneDebuggerTree * p_tree , int p_debugger ) {
updating_scene_tree = true ;
const String last_path = get_selected_path ( ) ;
2021-11-17 21:08:55 +01:00
const String filter = SceneTreeDock : : get_singleton ( ) - > get_filter ( ) ;
2021-02-08 01:19:46 +01:00
bool filter_changed = filter ! = last_filter ;
TreeItem * scroll_item = nullptr ;
2020-02-07 02:52:05 +01:00
// Nodes are in a flatten list, depth first. Use a stack of parents, avoid recursion.
2020-03-17 07:33:00 +01:00
List < Pair < TreeItem * , int > > parents ;
2024-04-15 15:18:34 +02:00
for ( const SceneDebuggerTree : : RemoteNode & node : p_tree - > nodes ) {
2020-04-02 01:20:12 +02:00
TreeItem * parent = nullptr ;
2020-02-07 02:52:05 +01:00
if ( parents . size ( ) ) { // Find last parent.
2024-04-15 15:18:34 +02:00
Pair < TreeItem * , int > & p = parents . front ( ) - > get ( ) ;
2020-02-07 02:52:05 +01:00
parent = p . first ;
if ( ! ( - - p . second ) ) { // If no child left, remove it.
parents . pop_front ( ) ;
}
}
// Add this node.
TreeItem * item = create_item ( parent ) ;
item - > set_text ( 0 , node . name ) ;
2023-01-03 17:06:36 +01:00
if ( node . scene_file_path . is_empty ( ) ) {
item - > set_tooltip_text ( 0 , node . name + " \n " + TTR ( " Type: " ) + " " + node . type_name ) ;
} else {
item - > set_tooltip_text ( 0 , node . name + " \n " + TTR ( " Instance: " ) + " " + node . scene_file_path + " \n " + TTR ( " Type: " ) + " " + node . type_name ) ;
}
2020-02-07 02:52:05 +01:00
Ref < Texture2D > icon = EditorNode : : get_singleton ( ) - > get_class_icon ( node . type_name , " " ) ;
if ( icon . is_valid ( ) ) {
item - > set_icon ( 0 , icon ) ;
}
item - > set_metadata ( 0 , node . id ) ;
2022-08-31 00:52:05 +02:00
// Set current item as collapsed if necessary (root is never collapsed).
2020-02-07 02:52:05 +01:00
if ( parent ) {
if ( ! unfold_cache . has ( node . id ) ) {
item - > set_collapsed ( true ) ;
}
}
// Select previously selected node.
if ( debugger_id = = p_debugger ) { // Can use remote id.
if ( node . id = = inspected_object_id ) {
item - > select ( 0 ) ;
2021-02-08 01:19:46 +01:00
if ( filter_changed ) {
scroll_item = item ;
}
2020-02-07 02:52:05 +01:00
}
} else { // Must use path
if ( last_path = = _get_path ( item ) ) {
2022-08-31 00:52:05 +02:00
updating_scene_tree = false ; // Force emission of new selection.
2020-02-07 02:52:05 +01:00
item - > select ( 0 ) ;
2021-02-08 01:19:46 +01:00
if ( filter_changed ) {
scroll_item = item ;
}
2020-02-07 02:52:05 +01:00
updating_scene_tree = true ;
}
}
2022-08-31 00:52:05 +02:00
// Add buttons.
const Color remote_button_color = Color ( 1 , 1 , 1 , 0.8 ) ;
if ( ! node . scene_file_path . is_empty ( ) ) {
String node_scene_file_path = node . scene_file_path ;
2023-08-13 02:33:39 +02:00
Ref < Texture2D > button_icon = get_editor_theme_icon ( SNAME ( " InstanceOptions " ) ) ;
2022-08-31 00:52:05 +02:00
String tooltip = vformat ( TTR ( " This node has been instantiated from a PackedScene file: \n %s \n Click to open the original file in the Editor. " ) , node_scene_file_path ) ;
item - > set_meta ( " scene_file_path " , node_scene_file_path ) ;
item - > add_button ( 0 , button_icon , BUTTON_SUBSCENE , false , tooltip ) ;
item - > set_button_color ( 0 , item - > get_button_count ( 0 ) - 1 , remote_button_color ) ;
}
if ( node . view_flags & SceneDebuggerTree : : RemoteNode : : VIEW_HAS_VISIBLE_METHOD ) {
bool node_visible = node . view_flags & SceneDebuggerTree : : RemoteNode : : VIEW_VISIBLE ;
bool node_visible_in_tree = node . view_flags & SceneDebuggerTree : : RemoteNode : : VIEW_VISIBLE_IN_TREE ;
2023-08-13 02:33:39 +02:00
Ref < Texture2D > button_icon = get_editor_theme_icon ( node_visible ? SNAME ( " GuiVisibilityVisible " ) : SNAME ( " GuiVisibilityHidden " ) ) ;
2022-08-31 00:52:05 +02:00
String tooltip = TTR ( " Toggle Visibility " ) ;
item - > set_meta ( " visible " , node_visible ) ;
item - > add_button ( 0 , button_icon , BUTTON_VISIBILITY , false , tooltip ) ;
if ( ClassDB : : is_parent_class ( node . type_name , " CanvasItem " ) | | ClassDB : : is_parent_class ( node . type_name , " Node3D " ) ) {
item - > set_button_color ( 0 , item - > get_button_count ( 0 ) - 1 , node_visible_in_tree ? remote_button_color : Color ( 1 , 1 , 1 , 0.6 ) ) ;
} else {
item - > set_button_color ( 0 , item - > get_button_count ( 0 ) - 1 , remote_button_color ) ;
}
}
2020-02-07 02:52:05 +01:00
// Add in front of the parents stack if children are expected.
if ( node . child_count ) {
parents . push_front ( Pair < TreeItem * , int > ( item , node . child_count ) ) ;
} else {
// Apply filters.
while ( parent ) {
const bool had_siblings = item - > get_prev ( ) | | item - > get_next ( ) ;
2022-01-27 00:03:56 +01:00
if ( filter . is_subsequence_ofn ( item - > get_text ( 0 ) ) ) {
2020-02-07 02:52:05 +01:00
break ; // Filter matches, must survive.
2020-05-14 16:41:43 +02:00
}
2020-02-07 02:52:05 +01:00
parent - > remove_child ( item ) ;
memdelete ( item ) ;
2021-02-08 01:19:46 +01:00
if ( scroll_item = = item ) {
scroll_item = nullptr ;
}
2020-05-14 16:41:43 +02:00
if ( had_siblings ) {
2020-02-07 02:52:05 +01:00
break ; // Parent must survive.
2020-05-14 16:41:43 +02:00
}
2020-02-07 02:52:05 +01:00
item = parent ;
parent = item - > get_parent ( ) ;
// Check if parent expects more children.
2024-04-15 15:18:34 +02:00
for ( const Pair < TreeItem * , int > & pair : parents ) {
if ( pair . first = = item ) {
2020-04-02 01:20:12 +02:00
parent = nullptr ;
2020-02-07 02:52:05 +01:00
break ; // Might have more children.
}
}
}
}
}
debugger_id = p_debugger ; // Needed by hook, could be avoided if every debugger had its own tree
2021-02-08 01:19:46 +01:00
if ( scroll_item ) {
2023-12-18 15:46:56 +01:00
callable_mp ( ( Tree * ) this , & Tree : : scroll_to_item ) . call_deferred ( scroll_item , false ) ;
2021-02-08 01:19:46 +01:00
}
last_filter = filter ;
2020-02-07 02:52:05 +01:00
updating_scene_tree = false ;
}
2021-12-19 00:01:35 +01:00
Variant EditorDebuggerTree : : get_drag_data ( const Point2 & p_point ) {
if ( get_button_id_at_position ( p_point ) ! = - 1 ) {
return Variant ( ) ;
}
TreeItem * selected = get_selected ( ) ;
if ( ! selected ) {
return Variant ( ) ;
}
String path = selected - > get_text ( 0 ) ;
HBoxContainer * hb = memnew ( HBoxContainer ) ;
TextureRect * tf = memnew ( TextureRect ) ;
tf - > set_texture ( selected - > get_icon ( 0 ) ) ;
tf - > set_stretch_mode ( TextureRect : : STRETCH_KEEP_CENTERED ) ;
hb - > add_child ( tf ) ;
Label * label = memnew ( Label ( path ) ) ;
hb - > add_child ( label ) ;
set_drag_preview ( hb ) ;
if ( ! selected - > get_parent ( ) | | ! selected - > get_parent ( ) - > get_parent ( ) ) {
path = " . " ;
} else {
while ( selected - > get_parent ( ) - > get_parent ( ) ! = get_root ( ) ) {
selected = selected - > get_parent ( ) ;
path = selected - > get_text ( 0 ) + " / " + path ;
}
}
return vformat ( " \" %s \" " , path ) ;
}
2024-05-06 17:20:59 +02:00
void EditorDebuggerTree : : update_icon_max_width ( ) {
add_theme_constant_override ( " icon_max_width " , get_theme_constant ( " class_icon_size " , EditorStringName ( Editor ) ) ) ;
}
2020-02-07 02:52:05 +01:00
String EditorDebuggerTree : : get_selected_path ( ) {
2020-05-14 16:41:43 +02:00
if ( ! get_selected ( ) ) {
2020-02-07 02:52:05 +01:00
return " " ;
2020-05-14 16:41:43 +02:00
}
2020-02-07 02:52:05 +01:00
return _get_path ( get_selected ( ) ) ;
}
String EditorDebuggerTree : : _get_path ( TreeItem * p_item ) {
2023-09-09 17:24:40 +02:00
ERR_FAIL_NULL_V ( p_item , " " ) ;
2020-02-07 02:52:05 +01:00
2020-04-02 01:20:12 +02:00
if ( p_item - > get_parent ( ) = = nullptr ) {
2020-02-07 02:52:05 +01:00
return " /root " ;
}
String text = p_item - > get_text ( 0 ) ;
TreeItem * cur = p_item - > get_parent ( ) ;
while ( cur ) {
text = cur - > get_text ( 0 ) + " / " + text ;
cur = cur - > get_parent ( ) ;
}
return " / " + text ;
}
void EditorDebuggerTree : : _item_menu_id_pressed ( int p_option ) {
switch ( p_option ) {
case ITEM_MENU_SAVE_REMOTE_NODE : {
file_dialog - > set_access ( EditorFileDialog : : ACCESS_RESOURCES ) ;
2020-03-06 18:00:16 +01:00
file_dialog - > set_file_mode ( EditorFileDialog : : FILE_MODE_SAVE_FILE ) ;
2020-02-07 02:52:05 +01:00
List < String > extensions ;
Ref < PackedScene > sd = memnew ( PackedScene ) ;
ResourceSaver : : get_recognized_extensions ( sd , & extensions ) ;
file_dialog - > clear_filters ( ) ;
2024-04-15 15:18:34 +02:00
for ( const String & extension : extensions ) {
file_dialog - > add_filter ( " *. " + extension , extension . to_upper ( ) ) ;
2020-02-07 02:52:05 +01:00
}
2022-12-25 13:56:27 +01:00
String filename = get_selected_path ( ) . get_file ( ) + " . " + extensions . front ( ) - > get ( ) . to_lower ( ) ;
file_dialog - > set_current_path ( filename ) ;
2020-07-11 18:45:19 +02:00
file_dialog - > popup_file_dialog ( ) ;
2020-02-07 02:52:05 +01:00
} break ;
case ITEM_MENU_COPY_NODE_PATH : {
String text = get_selected_path ( ) ;
2020-12-15 13:04:21 +01:00
if ( text . is_empty ( ) ) {
2020-02-07 02:52:05 +01:00
return ;
} else if ( text = = " /root " ) {
text = " . " ;
} else {
text = text . replace ( " /root/ " , " " ) ;
int slash = text . find ( " / " ) ;
if ( slash < 0 ) {
text = " . " ;
} else {
text = text . substr ( slash + 1 ) ;
}
}
2020-03-03 14:36:29 +01:00
DisplayServer : : get_singleton ( ) - > clipboard_set ( text ) ;
2020-02-07 02:52:05 +01:00
} break ;
2024-07-12 15:33:34 +02:00
case ITEM_MENU_EXPAND_COLLAPSE : {
TreeItem * s_item = get_selected ( ) ;
if ( ! s_item ) {
s_item = get_root ( ) ;
if ( ! s_item ) {
break ;
}
}
bool collapsed = s_item - > is_any_collapsed ( ) ;
s_item - > set_collapsed_recursive ( ! collapsed ) ;
ensure_cursor_is_visible ( ) ;
}
2020-02-07 02:52:05 +01:00
}
}
void EditorDebuggerTree : : _file_selected ( const String & p_file ) {
2020-05-14 16:41:43 +02:00
if ( inspected_object_id . is_null ( ) ) {
2020-02-07 02:52:05 +01:00
return ;
2020-05-14 16:41:43 +02:00
}
2021-07-17 23:22:52 +02:00
emit_signal ( SNAME ( " save_node " ) , inspected_object_id , p_file , debugger_id ) ;
2020-02-07 02:52:05 +01:00
}