2023-01-05 13:25:55 +01:00
/**************************************************************************/
/* editor_resource_picker.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. */
/**************************************************************************/
2021-05-17 14:25:38 +02:00
# include "editor_resource_picker.h"
2022-07-21 01:00:58 +02:00
# include "editor/audio_stream_preview.h"
2022-02-12 02:46:22 +01:00
# include "editor/editor_node.h"
2022-07-23 23:41:51 +02:00
# include "editor/editor_quick_open.h"
2021-05-17 14:25:38 +02:00
# include "editor/editor_resource_preview.h"
2022-02-12 02:46:22 +01:00
# include "editor/editor_scale.h"
# include "editor/editor_settings.h"
# include "editor/filesystem_dock.h"
2023-04-07 18:59:49 +02:00
# include "editor/gui/editor_file_dialog.h"
2022-07-31 20:14:15 +02:00
# include "editor/plugins/editor_resource_conversion_plugin.h"
2022-02-14 14:00:03 +01:00
# include "editor/plugins/script_editor_plugin.h"
2022-02-12 02:46:22 +01:00
# include "editor/scene_tree_dock.h"
2023-06-04 23:59:03 +02:00
# include "scene/gui/button.h"
# include "scene/gui/texture_rect.h"
2023-07-11 22:29:09 +02:00
# include "scene/resources/gradient_texture.h"
# include "scene/resources/image_texture.h"
2021-05-17 14:25:38 +02:00
void EditorResourcePicker : : _update_resource ( ) {
2022-07-21 01:00:58 +02:00
String resource_path ;
if ( edited_resource . is_valid ( ) & & edited_resource - > get_path ( ) . is_resource_file ( ) ) {
resource_path = edited_resource - > get_path ( ) + " \n " ;
}
2022-04-26 07:55:51 +02:00
String class_name = _get_resource_type ( edited_resource ) ;
2021-05-17 14:25:38 +02:00
2022-07-21 01:00:58 +02:00
if ( preview_rect ) {
preview_rect - > set_texture ( Ref < Texture2D > ( ) ) ;
assign_button - > set_custom_minimum_size ( assign_button_min_size ) ;
2021-05-17 14:25:38 +02:00
2022-07-21 01:00:58 +02:00
if ( edited_resource = = Ref < Resource > ( ) ) {
assign_button - > set_icon ( Ref < Texture2D > ( ) ) ;
2022-07-25 00:15:20 +02:00
assign_button - > set_text ( TTR ( " <empty> " ) ) ;
2022-08-25 12:42:17 +02:00
assign_button - > set_tooltip_text ( " " ) ;
2021-05-17 14:25:38 +02:00
} else {
2022-04-26 07:55:51 +02:00
assign_button - > set_icon ( EditorNode : : get_singleton ( ) - > get_object_icon ( edited_resource . operator - > ( ) , SNAME ( " Object " ) ) ) ;
2021-05-17 14:25:38 +02:00
2022-07-21 01:00:58 +02:00
if ( ! edited_resource - > get_name ( ) . is_empty ( ) ) {
assign_button - > set_text ( edited_resource - > get_name ( ) ) ;
} else if ( edited_resource - > get_path ( ) . is_resource_file ( ) ) {
assign_button - > set_text ( edited_resource - > get_path ( ) . get_file ( ) ) ;
} else {
2022-04-26 07:55:51 +02:00
assign_button - > set_text ( class_name ) ;
2022-07-21 01:00:58 +02:00
}
2022-04-26 07:55:51 +02:00
if ( edited_resource - > get_path ( ) . is_resource_file ( ) ) {
resource_path = edited_resource - > get_path ( ) + " \n " ;
}
assign_button - > set_tooltip_text ( resource_path + TTR ( " Type: " ) + " " + class_name ) ;
2022-07-21 01:00:58 +02:00
// Preview will override the above, so called at the end.
EditorResourcePreview : : get_singleton ( ) - > queue_edited_resource_preview ( edited_resource , this , " _update_resource_preview " , edited_resource - > get_instance_id ( ) ) ;
2021-05-17 14:25:38 +02:00
}
2022-07-21 01:00:58 +02:00
} else if ( edited_resource . is_valid ( ) ) {
2022-08-25 12:42:17 +02:00
assign_button - > set_tooltip_text ( resource_path + TTR ( " Type: " ) + " " + edited_resource - > get_class ( ) ) ;
2021-05-17 14:25:38 +02:00
}
2022-04-29 08:06:48 +02:00
assign_button - > set_disabled ( ! editable & & ! edited_resource . is_valid ( ) ) ;
2021-05-17 14:25:38 +02:00
}
void EditorResourcePicker : : _update_resource_preview ( const String & p_path , const Ref < Texture2D > & p_preview , const Ref < Texture2D > & p_small_preview , ObjectID p_obj ) {
if ( ! edited_resource . is_valid ( ) | | edited_resource - > get_instance_id ( ) ! = p_obj ) {
return ;
}
2022-07-21 01:00:58 +02:00
if ( preview_rect ) {
2022-09-29 11:53:28 +02:00
Ref < Script > scr = edited_resource ;
if ( scr . is_valid ( ) ) {
assign_button - > set_text ( scr - > get_path ( ) . get_file ( ) ) ;
2022-07-21 01:00:58 +02:00
return ;
}
2021-05-17 14:25:38 +02:00
2022-07-21 01:00:58 +02:00
if ( p_preview . is_valid ( ) ) {
2023-01-19 17:14:09 +01:00
preview_rect - > set_offset ( SIDE_LEFT , assign_button - > get_icon ( ) - > get_width ( ) + assign_button - > get_theme_stylebox ( SNAME ( " normal " ) ) - > get_content_margin ( SIDE_LEFT ) + get_theme_constant ( SNAME ( " h_separation " ) , SNAME ( " Button " ) ) ) ;
2021-05-17 14:25:38 +02:00
2022-07-21 01:00:58 +02:00
// Resource-specific stretching.
if ( Ref < GradientTexture1D > ( edited_resource ) . is_valid ( ) | | Ref < Gradient > ( edited_resource ) . is_valid ( ) ) {
preview_rect - > set_stretch_mode ( TextureRect : : STRETCH_SCALE ) ;
assign_button - > set_custom_minimum_size ( assign_button_min_size ) ;
} else {
preview_rect - > set_stretch_mode ( TextureRect : : STRETCH_KEEP_ASPECT_CENTERED ) ;
2022-10-18 16:43:37 +02:00
int thumbnail_size = EDITOR_GET ( " filesystem/file_dialog/thumbnail_size " ) ;
2022-07-21 01:00:58 +02:00
thumbnail_size * = EDSCALE ;
2022-08-18 04:20:00 +02:00
assign_button - > set_custom_minimum_size ( Size2 ( MAX ( 1 , assign_button_min_size . x ) , MAX ( thumbnail_size , assign_button_min_size . y ) ) ) ;
2022-07-21 01:00:58 +02:00
}
2021-05-17 14:25:38 +02:00
2022-07-21 01:00:58 +02:00
preview_rect - > set_texture ( p_preview ) ;
assign_button - > set_text ( " " ) ;
}
2021-05-17 14:25:38 +02:00
}
}
void EditorResourcePicker : : _resource_selected ( ) {
if ( edited_resource . is_null ( ) ) {
edit_button - > set_pressed ( true ) ;
_update_menu ( ) ;
return ;
}
2021-11-02 12:35:15 +01:00
emit_signal ( SNAME ( " resource_selected " ) , edited_resource , false ) ;
2021-05-17 14:25:38 +02:00
}
void EditorResourcePicker : : _file_selected ( const String & p_path ) {
2022-05-03 01:43:50 +02:00
Ref < Resource > loaded_resource = ResourceLoader : : load ( p_path ) ;
2021-05-17 14:25:38 +02:00
ERR_FAIL_COND_MSG ( loaded_resource . is_null ( ) , " Cannot load resource from path ' " + p_path + " '. " ) ;
2021-12-09 10:42:46 +01:00
if ( ! base_type . is_empty ( ) ) {
2021-05-17 14:25:38 +02:00
bool any_type_matches = false ;
2022-04-26 07:55:51 +02:00
String res_type = loaded_resource - > get_class ( ) ;
Ref < Script > res_script = loaded_resource - > get_script ( ) ;
bool is_global_class = false ;
if ( res_script . is_valid ( ) ) {
String script_type = EditorNode : : get_editor_data ( ) . script_class_get_name ( res_script - > get_path ( ) ) ;
if ( ! script_type . is_empty ( ) ) {
is_global_class = true ;
res_type = script_type ;
}
}
2021-05-17 14:25:38 +02:00
for ( int i = 0 ; i < base_type . get_slice_count ( " , " ) ; i + + ) {
String base = base_type . get_slice ( " , " , i ) ;
2022-04-26 07:55:51 +02:00
any_type_matches = is_global_class ? EditorNode : : get_editor_data ( ) . script_class_is_parent ( res_type , base ) : loaded_resource - > is_class ( base ) ;
2022-10-03 03:09:54 +02:00
if ( any_type_matches ) {
2021-05-17 14:25:38 +02:00
break ;
}
}
if ( ! any_type_matches ) {
2022-04-26 07:55:51 +02:00
EditorNode : : get_singleton ( ) - > show_warning ( vformat ( TTR ( " The selected resource (%s) does not match any type expected for this property (%s). " ) , res_type , base_type ) ) ;
2021-05-17 14:25:38 +02:00
return ;
}
}
edited_resource = loaded_resource ;
2021-07-17 23:22:52 +02:00
emit_signal ( SNAME ( " resource_changed " ) , edited_resource ) ;
2021-05-17 14:25:38 +02:00
_update_resource ( ) ;
}
2020-03-22 14:31:46 +01:00
void EditorResourcePicker : : _file_quick_selected ( ) {
_file_selected ( quick_open - > get_selected ( ) ) ;
}
2021-05-17 14:25:38 +02:00
void EditorResourcePicker : : _update_menu ( ) {
_update_menu_items ( ) ;
Rect2 gt = edit_button - > get_screen_rect ( ) ;
2022-03-06 00:57:42 +01:00
edit_menu - > reset_size ( ) ;
2021-05-17 14:25:38 +02:00
int ms = edit_menu - > get_contents_minimum_size ( ) . width ;
2021-09-22 10:24:45 +02:00
Vector2 popup_pos = gt . get_end ( ) - Vector2 ( ms , 0 ) ;
2021-05-17 14:25:38 +02:00
edit_menu - > set_position ( popup_pos ) ;
edit_menu - > popup ( ) ;
}
void EditorResourcePicker : : _update_menu_items ( ) {
2021-07-16 23:36:05 +02:00
_ensure_resource_menu ( ) ;
2021-05-17 14:25:38 +02:00
edit_menu - > clear ( ) ;
// Add options for creating specific subtypes of the base resource type.
2022-04-29 08:06:48 +02:00
if ( is_editable ( ) ) {
set_create_options ( edit_menu ) ;
2021-05-17 14:25:38 +02:00
2022-04-29 08:06:48 +02:00
// Add an option to load a resource from a file using the QuickOpen dialog.
edit_menu - > add_icon_item ( get_theme_icon ( SNAME ( " Load " ) , SNAME ( " EditorIcons " ) ) , TTR ( " Quick Load " ) , OBJ_MENU_QUICKLOAD ) ;
2020-03-22 14:31:46 +01:00
2022-04-29 08:06:48 +02:00
// Add an option to load a resource from a file using the regular file dialog.
edit_menu - > add_icon_item ( get_theme_icon ( SNAME ( " Load " ) , SNAME ( " EditorIcons " ) ) , TTR ( " Load " ) , OBJ_MENU_LOAD ) ;
}
2021-05-17 14:25:38 +02:00
// Add options for changing existing value of the resource.
if ( edited_resource . is_valid ( ) ) {
2022-04-29 08:06:48 +02:00
// Determine if the edited resource is part of another scene (foreign) which was imported
2022-09-07 11:14:06 +02:00
bool is_edited_resource_foreign_import = EditorNode : : get_singleton ( ) - > is_resource_read_only ( edited_resource , true ) ;
2022-04-29 08:06:48 +02:00
// If the resource is determined to be foreign and imported, change the menu entry's description to 'inspect' rather than 'edit'
// since will only be able to view its properties in read-only mode.
if ( is_edited_resource_foreign_import ) {
// The 'Search' icon is a magnifying glass, which seems appropriate, but maybe a bespoke icon is preferred here.
edit_menu - > add_icon_item ( get_theme_icon ( SNAME ( " Search " ) , SNAME ( " EditorIcons " ) ) , TTR ( " Inspect " ) , OBJ_MENU_INSPECT ) ;
} else {
edit_menu - > add_icon_item ( get_theme_icon ( SNAME ( " Edit " ) , SNAME ( " EditorIcons " ) ) , TTR ( " Edit " ) , OBJ_MENU_INSPECT ) ;
2022-04-28 02:24:13 +02:00
}
2022-04-29 08:06:48 +02:00
if ( is_editable ( ) ) {
edit_menu - > add_icon_item ( get_theme_icon ( SNAME ( " Clear " ) , SNAME ( " EditorIcons " ) ) , TTR ( " Clear " ) , OBJ_MENU_CLEAR ) ;
edit_menu - > add_icon_item ( get_theme_icon ( SNAME ( " Duplicate " ) , SNAME ( " EditorIcons " ) ) , TTR ( " Make Unique " ) , OBJ_MENU_MAKE_UNIQUE ) ;
// Check whether the resource has subresources.
List < PropertyInfo > property_list ;
edited_resource - > get_property_list ( & property_list ) ;
bool has_subresources = false ;
for ( PropertyInfo & p : property_list ) {
2023-06-04 23:59:03 +02:00
if ( ( p . type = = Variant : : OBJECT ) & & ( p . hint = = PROPERTY_HINT_RESOURCE_TYPE ) & & ( p . name ! = " script " ) & & ( ( Object * ) edited_resource - > get ( p . name ) ! = nullptr ) ) {
2022-04-29 08:06:48 +02:00
has_subresources = true ;
break ;
}
}
if ( has_subresources ) {
edit_menu - > add_icon_item ( get_theme_icon ( SNAME ( " Duplicate " ) , SNAME ( " EditorIcons " ) ) , TTR ( " Make Unique (Recursive) " ) , OBJ_MENU_MAKE_UNIQUE_RECURSIVE ) ;
}
edit_menu - > add_icon_item ( get_theme_icon ( SNAME ( " Save " ) , SNAME ( " EditorIcons " ) ) , TTR ( " Save " ) , OBJ_MENU_SAVE ) ;
}
2021-05-17 14:25:38 +02:00
if ( edited_resource - > get_path ( ) . is_resource_file ( ) ) {
edit_menu - > add_separator ( ) ;
edit_menu - > add_item ( TTR ( " Show in FileSystem " ) , OBJ_MENU_SHOW_IN_FILE_SYSTEM ) ;
}
}
// Add options to copy/paste resource.
2022-05-03 01:43:50 +02:00
Ref < Resource > cb = EditorSettings : : get_singleton ( ) - > get_resource_clipboard ( ) ;
2021-05-17 14:25:38 +02:00
bool paste_valid = false ;
2022-04-26 07:55:51 +02:00
if ( is_editable ( ) & & cb . is_valid ( ) ) {
if ( base_type . is_empty ( ) ) {
paste_valid = true ;
} else {
String res_type = _get_resource_type ( cb ) ;
for ( int i = 0 ; i < base_type . get_slice_count ( " , " ) ; i + + ) {
String base = base_type . get_slice ( " , " , i ) ;
paste_valid = ClassDB : : is_parent_class ( res_type , base ) | | EditorNode : : get_editor_data ( ) . script_class_is_parent ( res_type , base ) ;
2022-10-20 01:05:21 +02:00
if ( paste_valid ) {
2022-04-26 07:55:51 +02:00
break ;
2021-05-17 14:25:38 +02:00
}
}
}
}
if ( edited_resource . is_valid ( ) | | paste_valid ) {
edit_menu - > add_separator ( ) ;
if ( edited_resource . is_valid ( ) ) {
edit_menu - > add_item ( TTR ( " Copy " ) , OBJ_MENU_COPY ) ;
}
if ( paste_valid ) {
edit_menu - > add_item ( TTR ( " Paste " ) , OBJ_MENU_PASTE ) ;
}
}
// Add options to convert existing resource to another type of resource.
2022-04-29 08:06:48 +02:00
if ( is_editable ( ) & & edited_resource . is_valid ( ) ) {
2021-05-17 14:25:38 +02:00
Vector < Ref < EditorResourceConversionPlugin > > conversions = EditorNode : : get_singleton ( ) - > find_resource_conversion_plugin ( edited_resource ) ;
if ( conversions . size ( ) ) {
edit_menu - > add_separator ( ) ;
}
for ( int i = 0 ; i < conversions . size ( ) ; i + + ) {
String what = conversions [ i ] - > converts_to ( ) ;
Ref < Texture2D > icon ;
2021-07-17 23:22:52 +02:00
if ( has_theme_icon ( what , SNAME ( " EditorIcons " ) ) ) {
icon = get_theme_icon ( what , SNAME ( " EditorIcons " ) ) ;
2021-05-17 14:25:38 +02:00
} else {
2021-07-17 23:22:52 +02:00
icon = get_theme_icon ( what , SNAME ( " Resource " ) ) ;
2021-05-17 14:25:38 +02:00
}
2021-07-13 15:25:56 +02:00
edit_menu - > add_icon_item ( icon , vformat ( TTR ( " Convert to %s " ) , what ) , CONVERT_BASE_ID + i ) ;
2021-05-17 14:25:38 +02:00
}
}
}
void EditorResourcePicker : : _edit_menu_cbk ( int p_which ) {
switch ( p_which ) {
case OBJ_MENU_LOAD : {
List < String > extensions ;
for ( int i = 0 ; i < base_type . get_slice_count ( " , " ) ; i + + ) {
String base = base_type . get_slice ( " , " , i ) ;
ResourceLoader : : get_recognized_extensions_for_type ( base , & extensions ) ;
2022-04-26 07:55:51 +02:00
if ( ScriptServer : : is_global_class ( base ) ) {
ResourceLoader : : get_recognized_extensions_for_type ( ScriptServer : : get_global_class_native_base ( base ) , & extensions ) ;
}
2021-05-17 14:25:38 +02:00
}
2022-05-19 17:00:06 +02:00
HashSet < String > valid_extensions ;
2021-07-24 15:46:25 +02:00
for ( const String & E : extensions ) {
2021-07-16 05:45:57 +02:00
valid_extensions . insert ( E ) ;
2021-05-17 14:25:38 +02:00
}
2021-05-25 17:38:19 +02:00
if ( ! file_dialog ) {
file_dialog = memnew ( EditorFileDialog ) ;
file_dialog - > set_file_mode ( EditorFileDialog : : FILE_MODE_OPEN_FILE ) ;
add_child ( file_dialog ) ;
file_dialog - > connect ( " file_selected " , callable_mp ( this , & EditorResourcePicker : : _file_selected ) ) ;
}
2021-05-17 14:25:38 +02:00
file_dialog - > clear_filters ( ) ;
2022-05-19 01:43:40 +02:00
for ( const String & E : valid_extensions ) {
2022-07-04 23:26:26 +02:00
file_dialog - > add_filter ( " *. " + E , E . to_upper ( ) ) ;
2021-05-17 14:25:38 +02:00
}
file_dialog - > popup_file_dialog ( ) ;
} break ;
2020-03-22 14:31:46 +01:00
case OBJ_MENU_QUICKLOAD : {
if ( ! quick_open ) {
quick_open = memnew ( EditorQuickOpen ) ;
add_child ( quick_open ) ;
quick_open - > connect ( " quick_open " , callable_mp ( this , & EditorResourcePicker : : _file_quick_selected ) ) ;
}
quick_open - > popup_dialog ( base_type ) ;
quick_open - > set_title ( TTR ( " Resource " ) ) ;
} break ;
2022-04-29 08:06:48 +02:00
case OBJ_MENU_INSPECT : {
2021-05-17 14:25:38 +02:00
if ( edited_resource . is_valid ( ) ) {
2021-11-02 12:35:15 +01:00
emit_signal ( SNAME ( " resource_selected " ) , edited_resource , true ) ;
2021-05-17 14:25:38 +02:00
}
} break ;
case OBJ_MENU_CLEAR : {
2022-05-03 01:43:50 +02:00
edited_resource = Ref < Resource > ( ) ;
2021-07-17 23:22:52 +02:00
emit_signal ( SNAME ( " resource_changed " ) , edited_resource ) ;
2021-05-17 14:25:38 +02:00
_update_resource ( ) ;
} break ;
case OBJ_MENU_MAKE_UNIQUE : {
if ( edited_resource . is_null ( ) ) {
return ;
}
2022-04-28 02:24:13 +02:00
Ref < Resource > unique_resource = edited_resource - > duplicate ( ) ;
2023-06-04 23:59:03 +02:00
ERR_FAIL_COND ( unique_resource . is_null ( ) ) ; // duplicate() may fail.
2021-05-17 14:25:38 +02:00
2022-04-28 02:24:13 +02:00
edited_resource = unique_resource ;
emit_signal ( SNAME ( " resource_changed " ) , edited_resource ) ;
_update_resource ( ) ;
} break ;
case OBJ_MENU_MAKE_UNIQUE_RECURSIVE : {
if ( edited_resource . is_null ( ) ) {
return ;
2021-05-17 14:25:38 +02:00
}
2023-06-04 23:59:03 +02:00
if ( ! duplicate_resources_dialog ) {
duplicate_resources_dialog = memnew ( ConfirmationDialog ) ;
add_child ( duplicate_resources_dialog ) ;
duplicate_resources_dialog - > set_title ( TTR ( " Make Unique (Recursive) " ) ) ;
duplicate_resources_dialog - > connect ( " confirmed " , callable_mp ( this , & EditorResourcePicker : : _duplicate_selected_resources ) ) ;
2021-05-17 14:25:38 +02:00
2023-06-04 23:59:03 +02:00
VBoxContainer * vb = memnew ( VBoxContainer ) ;
duplicate_resources_dialog - > add_child ( vb ) ;
Label * label = memnew ( Label ( TTR ( " Select resources to make unique: " ) ) ) ;
vb - > add_child ( label ) ;
duplicate_resources_tree = memnew ( Tree ) ;
vb - > add_child ( duplicate_resources_tree ) ;
duplicate_resources_tree - > set_columns ( 2 ) ;
duplicate_resources_tree - > set_v_size_flags ( SIZE_EXPAND_FILL ) ;
}
duplicate_resources_tree - > clear ( ) ;
TreeItem * root = duplicate_resources_tree - > create_item ( ) ;
_gather_resources_to_duplicate ( edited_resource , root ) ;
duplicate_resources_dialog - > reset_size ( ) ;
duplicate_resources_dialog - > popup_centered ( Vector2 ( 500 , 400 ) * EDSCALE ) ;
2021-05-17 14:25:38 +02:00
} break ;
case OBJ_MENU_SAVE : {
if ( edited_resource . is_null ( ) ) {
return ;
}
EditorNode : : get_singleton ( ) - > save_resource ( edited_resource ) ;
} break ;
case OBJ_MENU_COPY : {
EditorSettings : : get_singleton ( ) - > set_resource_clipboard ( edited_resource ) ;
} break ;
case OBJ_MENU_PASTE : {
edited_resource = EditorSettings : : get_singleton ( ) - > get_resource_clipboard ( ) ;
2022-03-24 16:47:14 +01:00
if ( edited_resource - > is_built_in ( ) & & EditorNode : : get_singleton ( ) - > get_edited_scene ( ) & &
edited_resource - > get_path ( ) . get_slice ( " :: " , 0 ) ! = EditorNode : : get_singleton ( ) - > get_edited_scene ( ) - > get_scene_file_path ( ) ) {
// Automatically make resource unique if it belongs to another scene.
_edit_menu_cbk ( OBJ_MENU_MAKE_UNIQUE ) ;
return ;
}
2021-07-17 23:22:52 +02:00
emit_signal ( SNAME ( " resource_changed " ) , edited_resource ) ;
2021-05-17 14:25:38 +02:00
_update_resource ( ) ;
} break ;
case OBJ_MENU_SHOW_IN_FILE_SYSTEM : {
2021-11-17 21:08:55 +01:00
FileSystemDock * file_system_dock = FileSystemDock : : get_singleton ( ) ;
2021-05-17 14:25:38 +02:00
file_system_dock - > navigate_to_path ( edited_resource - > get_path ( ) ) ;
// Ensure that the FileSystem dock is visible.
2023-03-08 07:22:58 +01:00
if ( file_system_dock - > get_window ( ) = = get_tree ( ) - > get_root ( ) ) {
TabContainer * tab_container = ( TabContainer * ) file_system_dock - > get_parent_control ( ) ;
tab_container - > set_current_tab ( tab_container - > get_tab_idx_from_control ( file_system_dock ) ) ;
} else {
file_system_dock - > get_window ( ) - > grab_focus ( ) ;
}
2021-05-17 14:25:38 +02:00
} break ;
default : {
2021-05-19 14:19:07 +02:00
// Allow subclasses to handle their own options first, only then fallback on the default branch logic.
if ( handle_menu_selected ( p_which ) ) {
break ;
}
2021-05-17 14:25:38 +02:00
if ( p_which > = CONVERT_BASE_ID ) {
int to_type = p_which - CONVERT_BASE_ID ;
Vector < Ref < EditorResourceConversionPlugin > > conversions = EditorNode : : get_singleton ( ) - > find_resource_conversion_plugin ( edited_resource ) ;
ERR_FAIL_INDEX ( to_type , conversions . size ( ) ) ;
edited_resource = conversions [ to_type ] - > convert ( edited_resource ) ;
2021-07-17 23:22:52 +02:00
emit_signal ( SNAME ( " resource_changed " ) , edited_resource ) ;
2021-05-17 14:25:38 +02:00
_update_resource ( ) ;
break ;
}
ERR_FAIL_COND ( inheritors_array . is_empty ( ) ) ;
String intype = inheritors_array [ p_which - TYPE_BASE_ID ] ;
Variant obj ;
if ( ScriptServer : : is_global_class ( intype ) ) {
2022-04-26 07:55:51 +02:00
obj = EditorNode : : get_editor_data ( ) . script_class_instance ( intype ) ;
2021-05-17 14:25:38 +02:00
} else {
2021-06-18 00:03:09 +02:00
obj = ClassDB : : instantiate ( intype ) ;
2021-05-17 14:25:38 +02:00
}
if ( ! obj ) {
2022-11-16 00:13:39 +01:00
obj = EditorNode : : get_editor_data ( ) . instantiate_custom_type ( intype , " Resource " ) ;
2021-05-17 14:25:38 +02:00
}
Resource * resp = Object : : cast_to < Resource > ( obj ) ;
ERR_BREAK ( ! resp ) ;
2020-10-22 21:02:57 +02:00
EditorNode : : get_editor_data ( ) . instantiate_object_properties ( obj ) ;
2022-05-03 01:43:50 +02:00
edited_resource = Ref < Resource > ( resp ) ;
2021-07-17 23:22:52 +02:00
emit_signal ( SNAME ( " resource_changed " ) , edited_resource ) ;
2021-05-17 14:25:38 +02:00
_update_resource ( ) ;
} break ;
}
}
2021-05-19 14:19:07 +02:00
void EditorResourcePicker : : set_create_options ( Object * p_menu_node ) {
2021-07-16 23:36:05 +02:00
_ensure_resource_menu ( ) ;
2021-05-19 14:19:07 +02:00
// If a subclass implements this method, use it to replace all create items.
2021-12-29 18:27:44 +01:00
if ( GDVIRTUAL_CALL ( _set_create_options , p_menu_node ) ) {
2021-05-19 14:19:07 +02:00
return ;
}
// By default provide generic "New ..." options.
2021-12-09 10:42:46 +01:00
if ( ! base_type . is_empty ( ) ) {
2021-05-19 14:19:07 +02:00
int idx = 0 ;
2023-01-19 16:47:01 +01:00
HashSet < StringName > allowed_types ;
2021-05-19 14:19:07 +02:00
_get_allowed_types ( false , & allowed_types ) ;
Vector < EditorData : : CustomType > custom_resources ;
if ( EditorNode : : get_editor_data ( ) . get_custom_types ( ) . has ( " Resource " ) ) {
custom_resources = EditorNode : : get_editor_data ( ) . get_custom_types ( ) [ " Resource " ] ;
}
2023-01-19 16:47:01 +01:00
for ( const StringName & E : allowed_types ) {
2022-05-19 01:43:40 +02:00
const String & t = E ;
2021-05-19 14:19:07 +02:00
bool is_custom_resource = false ;
Ref < Texture2D > icon ;
if ( ! custom_resources . is_empty ( ) ) {
for ( int j = 0 ; j < custom_resources . size ( ) ; j + + ) {
if ( custom_resources [ j ] . name = = t ) {
is_custom_resource = true ;
if ( custom_resources [ j ] . icon . is_valid ( ) ) {
icon = custom_resources [ j ] . icon ;
}
break ;
}
}
}
2021-06-18 00:03:09 +02:00
if ( ! is_custom_resource & & ! ( ScriptServer : : is_global_class ( t ) | | ClassDB : : can_instantiate ( t ) ) ) {
2021-05-19 14:19:07 +02:00
continue ;
}
inheritors_array . push_back ( t ) ;
if ( ! icon . is_valid ( ) ) {
2021-07-17 23:22:52 +02:00
icon = get_theme_icon ( has_theme_icon ( t , SNAME ( " EditorIcons " ) ) ? t : String ( " Object " ) , SNAME ( " EditorIcons " ) ) ;
2021-05-19 14:19:07 +02:00
}
int id = TYPE_BASE_ID + idx ;
edit_menu - > add_icon_item ( icon , vformat ( TTR ( " New %s " ) , t ) , id ) ;
idx + + ;
}
if ( edit_menu - > get_item_count ( ) ) {
edit_menu - > add_separator ( ) ;
}
}
}
bool EditorResourcePicker : : handle_menu_selected ( int p_which ) {
2022-10-18 18:47:44 +02:00
bool success = false ;
GDVIRTUAL_CALL ( _handle_menu_selected , p_which , success ) ;
return success ;
2021-05-19 14:19:07 +02:00
}
2021-05-17 14:25:38 +02:00
void EditorResourcePicker : : _button_draw ( ) {
if ( dropping ) {
2021-07-17 23:22:52 +02:00
Color color = get_theme_color ( SNAME ( " accent_color " ) , SNAME ( " Editor " ) ) ;
2021-05-17 14:25:38 +02:00
assign_button - > draw_rect ( Rect2 ( Point2 ( ) , assign_button - > get_size ( ) ) , color , false ) ;
}
}
void EditorResourcePicker : : _button_input ( const Ref < InputEvent > & p_event ) {
Ref < InputEventMouseButton > mb = p_event ;
2022-04-26 07:55:51 +02:00
if ( mb . is_valid ( ) & & mb - > is_pressed ( ) & & mb - > get_button_index ( ) = = MouseButton : : RIGHT ) {
// Only attempt to update and show the menu if we have
// a valid resource or the Picker is editable, as
// there will otherwise be nothing to display.
if ( edited_resource . is_valid ( ) | | is_editable ( ) ) {
_update_menu_items ( ) ;
Vector2 pos = get_screen_position ( ) + mb - > get_position ( ) ;
edit_menu - > reset_size ( ) ;
edit_menu - > set_position ( pos ) ;
edit_menu - > popup ( ) ;
2021-05-17 14:25:38 +02:00
}
}
}
2022-04-26 07:55:51 +02:00
String EditorResourcePicker : : _get_resource_type ( const Ref < Resource > & p_resource ) const {
if ( p_resource . is_null ( ) ) {
return String ( ) ;
}
String res_type = p_resource - > get_class ( ) ;
Ref < Script > res_script = p_resource - > get_script ( ) ;
if ( res_script . is_null ( ) ) {
return res_type ;
}
// TODO: Replace with EditorFileSystem when PR #60606 is merged to use cached resource type.
String script_type = EditorNode : : get_editor_data ( ) . script_class_get_name ( res_script - > get_path ( ) ) ;
if ( ! script_type . is_empty ( ) ) {
res_type = script_type ;
}
return res_type ;
}
2023-01-19 16:47:01 +01:00
static void _add_allowed_type ( const StringName & p_type , HashSet < StringName > * p_vector ) {
if ( p_vector - > has ( p_type ) ) {
// Already added
return ;
}
2021-05-17 14:25:38 +02:00
2023-01-19 16:47:01 +01:00
if ( ClassDB : : class_exists ( p_type ) ) {
// Engine class,
2021-05-17 14:25:38 +02:00
2023-01-19 16:47:01 +01:00
if ( ! ClassDB : : is_virtual ( p_type ) ) {
p_vector - > insert ( p_type ) ;
2022-10-21 22:30:06 +02:00
}
2021-05-17 14:25:38 +02:00
2023-01-19 16:47:01 +01:00
List < StringName > inheriters ;
ClassDB : : get_inheriters_from_class ( p_type , & inheriters ) ;
for ( const StringName & S : inheriters ) {
_add_allowed_type ( S , p_vector ) ;
}
} else {
// Script class.
p_vector - > insert ( p_type ) ;
}
2021-05-17 14:25:38 +02:00
2023-01-19 16:47:01 +01:00
List < StringName > inheriters ;
ScriptServer : : get_inheriters_list ( p_type , & inheriters ) ;
for ( const StringName & S : inheriters ) {
_add_allowed_type ( S , p_vector ) ;
}
}
2021-05-17 14:25:38 +02:00
2023-01-19 16:47:01 +01:00
void EditorResourcePicker : : _get_allowed_types ( bool p_with_convert , HashSet < StringName > * p_vector ) const {
Vector < String > allowed_types = base_type . split ( " , " ) ;
int size = allowed_types . size ( ) ;
2021-08-03 15:52:12 +02:00
2023-01-19 16:47:01 +01:00
for ( int i = 0 ; i < size ; i + + ) {
String base = allowed_types [ i ] . strip_edges ( ) ;
_add_allowed_type ( base , p_vector ) ;
2021-05-17 14:25:38 +02:00
if ( p_with_convert ) {
2022-01-01 13:51:42 +01:00
if ( base = = " BaseMaterial3D " ) {
2021-05-17 14:25:38 +02:00
p_vector - > insert ( " Texture2D " ) ;
} else if ( base = = " ShaderMaterial " ) {
p_vector - > insert ( " Shader " ) ;
2022-01-01 13:51:42 +01:00
} else if ( base = = " Texture2D " ) {
p_vector - > insert ( " Image " ) ;
2021-05-17 14:25:38 +02:00
}
}
}
}
bool EditorResourcePicker : : _is_drop_valid ( const Dictionary & p_drag_data ) const {
if ( base_type . is_empty ( ) ) {
return true ;
}
Dictionary drag_data = p_drag_data ;
Ref < Resource > res ;
if ( drag_data . has ( " type " ) & & String ( drag_data [ " type " ] ) = = " script_list_element " ) {
ScriptEditorBase * se = Object : : cast_to < ScriptEditorBase > ( drag_data [ " script_list_element " ] ) ;
2021-07-31 20:42:29 +02:00
if ( se ) {
res = se - > get_edited_resource ( ) ;
}
2021-05-17 14:25:38 +02:00
} else if ( drag_data . has ( " type " ) & & String ( drag_data [ " type " ] ) = = " resource " ) {
res = drag_data [ " resource " ] ;
2022-04-26 07:55:51 +02:00
} else if ( drag_data . has ( " type " ) & & String ( drag_data [ " type " ] ) = = " files " ) {
Vector < String > files = drag_data [ " files " ] ;
// TODO: Extract the typename of the dropped filepath's resource in a more performant way, without fully loading it.
if ( files . size ( ) = = 1 ) {
String file = files [ 0 ] ;
res = ResourceLoader : : load ( file ) ;
}
2021-05-17 14:25:38 +02:00
}
2023-01-19 16:47:01 +01:00
HashSet < StringName > allowed_types ;
2021-05-17 14:25:38 +02:00
_get_allowed_types ( true , & allowed_types ) ;
2022-04-26 07:55:51 +02:00
if ( res . is_valid ( ) ) {
String res_type = _get_resource_type ( res ) ;
2021-05-17 14:25:38 +02:00
2022-04-26 07:55:51 +02:00
if ( _is_type_valid ( res_type , allowed_types ) ) {
2021-05-17 14:25:38 +02:00
return true ;
}
2022-04-26 07:55:51 +02:00
StringName custom_class = EditorNode : : get_singleton ( ) - > get_object_custom_type_name ( res . ptr ( ) ) ;
if ( _is_type_valid ( custom_class , allowed_types ) ) {
return true ;
2021-05-17 14:25:38 +02:00
}
}
return false ;
}
2023-01-19 16:47:01 +01:00
bool EditorResourcePicker : : _is_type_valid ( const String p_type_name , HashSet < StringName > p_allowed_types ) const {
for ( const StringName & E : p_allowed_types ) {
String at = E ;
2021-05-17 14:25:38 +02:00
if ( p_type_name = = at | | ClassDB : : is_parent_class ( p_type_name , at ) | | EditorNode : : get_editor_data ( ) . script_class_is_parent ( p_type_name , at ) ) {
return true ;
}
}
return false ;
}
Variant EditorResourcePicker : : get_drag_data_fw ( const Point2 & p_point , Control * p_from ) {
if ( edited_resource . is_valid ( ) ) {
return EditorNode : : get_singleton ( ) - > drag_resource ( edited_resource , p_from ) ;
}
return Variant ( ) ;
}
bool EditorResourcePicker : : can_drop_data_fw ( const Point2 & p_point , const Variant & p_data , Control * p_from ) const {
return editable & & _is_drop_valid ( p_data ) ;
}
void EditorResourcePicker : : drop_data_fw ( const Point2 & p_point , const Variant & p_data , Control * p_from ) {
ERR_FAIL_COND ( ! _is_drop_valid ( p_data ) ) ;
Dictionary drag_data = p_data ;
Ref < Resource > dropped_resource ;
if ( drag_data . has ( " type " ) & & String ( drag_data [ " type " ] ) = = " script_list_element " ) {
ScriptEditorBase * se = Object : : cast_to < ScriptEditorBase > ( drag_data [ " script_list_element " ] ) ;
2021-07-31 20:42:29 +02:00
if ( se ) {
dropped_resource = se - > get_edited_resource ( ) ;
}
2021-05-17 14:25:38 +02:00
} else if ( drag_data . has ( " type " ) & & String ( drag_data [ " type " ] ) = = " resource " ) {
dropped_resource = drag_data [ " resource " ] ;
}
if ( ! dropped_resource . is_valid ( ) & & drag_data . has ( " type " ) & & String ( drag_data [ " type " ] ) = = " files " ) {
Vector < String > files = drag_data [ " files " ] ;
if ( files . size ( ) = = 1 ) {
String file = files [ 0 ] ;
dropped_resource = ResourceLoader : : load ( file ) ;
}
}
if ( dropped_resource . is_valid ( ) ) {
2023-01-19 16:47:01 +01:00
HashSet < StringName > allowed_types ;
2021-05-17 14:25:38 +02:00
_get_allowed_types ( false , & allowed_types ) ;
2022-04-26 07:55:51 +02:00
String res_type = _get_resource_type ( dropped_resource ) ;
2021-05-17 14:25:38 +02:00
// If the accepted dropped resource is from the extended list, it requires conversion.
2022-04-26 07:55:51 +02:00
if ( ! _is_type_valid ( res_type , allowed_types ) ) {
2023-01-19 16:47:01 +01:00
for ( const StringName & E : allowed_types ) {
String at = E ;
2021-05-17 14:25:38 +02:00
2022-02-06 14:12:19 +01:00
if ( at = = " BaseMaterial3D " & & Ref < Texture2D > ( dropped_resource ) . is_valid ( ) ) {
2022-01-01 13:51:42 +01:00
// Use existing resource if possible and only replace its data.
Ref < StandardMaterial3D > mat = edited_resource ;
if ( ! mat . is_valid ( ) ) {
mat . instantiate ( ) ;
}
2021-05-17 14:25:38 +02:00
mat - > set_texture ( StandardMaterial3D : : TextureParam : : TEXTURE_ALBEDO , dropped_resource ) ;
dropped_resource = mat ;
break ;
}
2022-02-06 14:12:19 +01:00
if ( at = = " ShaderMaterial " & & Ref < Shader > ( dropped_resource ) . is_valid ( ) ) {
2022-01-01 13:51:42 +01:00
Ref < ShaderMaterial > mat = edited_resource ;
if ( ! mat . is_valid ( ) ) {
mat . instantiate ( ) ;
}
2021-05-17 14:25:38 +02:00
mat - > set_shader ( dropped_resource ) ;
dropped_resource = mat ;
break ;
}
2023-06-04 15:19:16 +02:00
if ( at = = " ImageTexture " & & Ref < Image > ( dropped_resource ) . is_valid ( ) ) {
2022-01-01 13:51:42 +01:00
Ref < ImageTexture > texture = edited_resource ;
if ( ! texture . is_valid ( ) ) {
texture . instantiate ( ) ;
}
2022-05-04 01:49:20 +02:00
texture - > set_image ( dropped_resource ) ;
2022-01-01 13:51:42 +01:00
dropped_resource = texture ;
break ;
}
2021-05-17 14:25:38 +02:00
}
}
edited_resource = dropped_resource ;
2021-07-17 23:22:52 +02:00
emit_signal ( SNAME ( " resource_changed " ) , edited_resource ) ;
2021-05-17 14:25:38 +02:00
_update_resource ( ) ;
}
}
void EditorResourcePicker : : _bind_methods ( ) {
ClassDB : : bind_method ( D_METHOD ( " _update_resource_preview " ) , & EditorResourcePicker : : _update_resource_preview ) ;
ClassDB : : bind_method ( D_METHOD ( " set_base_type " , " base_type " ) , & EditorResourcePicker : : set_base_type ) ;
ClassDB : : bind_method ( D_METHOD ( " get_base_type " ) , & EditorResourcePicker : : get_base_type ) ;
ClassDB : : bind_method ( D_METHOD ( " get_allowed_types " ) , & EditorResourcePicker : : get_allowed_types ) ;
ClassDB : : bind_method ( D_METHOD ( " set_edited_resource " , " resource " ) , & EditorResourcePicker : : set_edited_resource ) ;
ClassDB : : bind_method ( D_METHOD ( " get_edited_resource " ) , & EditorResourcePicker : : get_edited_resource ) ;
2021-05-19 14:19:07 +02:00
ClassDB : : bind_method ( D_METHOD ( " set_toggle_mode " , " enable " ) , & EditorResourcePicker : : set_toggle_mode ) ;
ClassDB : : bind_method ( D_METHOD ( " is_toggle_mode " ) , & EditorResourcePicker : : is_toggle_mode ) ;
ClassDB : : bind_method ( D_METHOD ( " set_toggle_pressed " , " pressed " ) , & EditorResourcePicker : : set_toggle_pressed ) ;
2021-05-17 14:25:38 +02:00
ClassDB : : bind_method ( D_METHOD ( " set_editable " , " enable " ) , & EditorResourcePicker : : set_editable ) ;
ClassDB : : bind_method ( D_METHOD ( " is_editable " ) , & EditorResourcePicker : : is_editable ) ;
2021-12-29 18:27:44 +01:00
GDVIRTUAL_BIND ( _set_create_options , " menu_node " ) ;
GDVIRTUAL_BIND ( _handle_menu_selected , " id " ) ;
2021-05-19 14:19:07 +02:00
2021-05-17 14:25:38 +02:00
ADD_PROPERTY ( PropertyInfo ( Variant : : STRING , " base_type " ) , " set_base_type " , " get_base_type " ) ;
2021-06-18 01:10:18 +02:00
ADD_PROPERTY ( PropertyInfo ( Variant : : OBJECT , " edited_resource " , PROPERTY_HINT_RESOURCE_TYPE , " Resource " , PROPERTY_USAGE_NONE ) , " set_edited_resource " , " get_edited_resource " ) ;
2021-05-17 14:25:38 +02:00
ADD_PROPERTY ( PropertyInfo ( Variant : : BOOL , " editable " ) , " set_editable " , " is_editable " ) ;
2021-05-19 14:19:07 +02:00
ADD_PROPERTY ( PropertyInfo ( Variant : : BOOL , " toggle_mode " ) , " set_toggle_mode " , " is_toggle_mode " ) ;
2021-05-17 14:25:38 +02:00
2022-04-29 08:06:48 +02:00
ADD_SIGNAL ( MethodInfo ( " resource_selected " , PropertyInfo ( Variant : : OBJECT , " resource " , PROPERTY_HINT_RESOURCE_TYPE , " Resource " ) , PropertyInfo ( Variant : : BOOL , " inspect " ) ) ) ;
2021-05-17 14:25:38 +02:00
ADD_SIGNAL ( MethodInfo ( " resource_changed " , PropertyInfo ( Variant : : OBJECT , " resource " , PROPERTY_HINT_RESOURCE_TYPE , " Resource " ) ) ) ;
}
void EditorResourcePicker : : _notification ( int p_what ) {
switch ( p_what ) {
case NOTIFICATION_ENTER_TREE : {
_update_resource ( ) ;
2022-08-29 11:04:31 +02:00
[[fallthrough]] ;
}
2021-05-17 14:25:38 +02:00
case NOTIFICATION_THEME_CHANGED : {
2023-03-31 21:17:59 +02:00
assign_button - > add_theme_constant_override ( " icon_max_width " , get_theme_constant ( SNAME ( " class_icon_size " ) , SNAME ( " Editor " ) ) ) ;
2021-07-17 23:22:52 +02:00
edit_button - > set_icon ( get_theme_icon ( SNAME ( " select_arrow " ) , SNAME ( " Tree " ) ) ) ;
2021-05-17 14:25:38 +02:00
} break ;
case NOTIFICATION_DRAW : {
2022-09-06 19:09:32 +02:00
draw_style_box ( get_theme_stylebox ( SNAME ( " panel " ) , SNAME ( " Tree " ) ) , Rect2 ( Point2 ( ) , get_size ( ) ) ) ;
2021-05-17 14:25:38 +02:00
} break ;
case NOTIFICATION_DRAG_BEGIN : {
if ( editable & & _is_drop_valid ( get_viewport ( ) - > gui_get_drag_data ( ) ) ) {
dropping = true ;
2022-08-13 23:21:24 +02:00
assign_button - > queue_redraw ( ) ;
2021-05-17 14:25:38 +02:00
}
} break ;
case NOTIFICATION_DRAG_END : {
if ( dropping ) {
dropping = false ;
2022-08-13 23:21:24 +02:00
assign_button - > queue_redraw ( ) ;
2021-05-17 14:25:38 +02:00
}
} break ;
}
}
2023-06-04 23:59:03 +02:00
void EditorResourcePicker : : set_assign_button_min_size ( const Size2i & p_size ) {
assign_button_min_size = p_size ;
assign_button - > set_custom_minimum_size ( assign_button_min_size ) ;
}
2021-05-17 14:25:38 +02:00
void EditorResourcePicker : : set_base_type ( const String & p_base_type ) {
base_type = p_base_type ;
// There is a possibility that the new base type is conflicting with the existing value.
// Keep the value, but warn the user that there is a potential mistake.
if ( ! base_type . is_empty ( ) & & edited_resource . is_valid ( ) ) {
2023-01-19 16:47:01 +01:00
HashSet < StringName > allowed_types ;
2021-05-17 14:25:38 +02:00
_get_allowed_types ( true , & allowed_types ) ;
StringName custom_class ;
bool is_custom = false ;
if ( edited_resource - > get_script ( ) ) {
custom_class = EditorNode : : get_singleton ( ) - > get_object_custom_type_name ( edited_resource - > get_script ( ) ) ;
is_custom = _is_type_valid ( custom_class , allowed_types ) ;
}
if ( ! is_custom & & ! _is_type_valid ( edited_resource - > get_class ( ) , allowed_types ) ) {
String class_str = ( custom_class = = StringName ( ) ? edited_resource - > get_class ( ) : vformat ( " %s (%s) " , custom_class , edited_resource - > get_class ( ) ) ) ;
WARN_PRINT ( vformat ( " Value mismatch between the new base type of this EditorResourcePicker, '%s', and the type of the value it already has, '%s'. " , base_type , class_str ) ) ;
}
}
}
String EditorResourcePicker : : get_base_type ( ) const {
return base_type ;
}
Vector < String > EditorResourcePicker : : get_allowed_types ( ) const {
2023-01-19 16:47:01 +01:00
HashSet < StringName > allowed_types ;
2021-05-17 14:25:38 +02:00
_get_allowed_types ( false , & allowed_types ) ;
Vector < String > types ;
types . resize ( allowed_types . size ( ) ) ;
int i = 0 ;
String * w = types . ptrw ( ) ;
2023-01-19 16:47:01 +01:00
for ( const StringName & E : allowed_types ) {
2022-05-19 17:00:06 +02:00
w [ i ] = E ;
i + + ;
2021-05-17 14:25:38 +02:00
}
return types ;
}
2022-05-03 01:43:50 +02:00
void EditorResourcePicker : : set_edited_resource ( Ref < Resource > p_resource ) {
2021-05-17 14:25:38 +02:00
if ( ! p_resource . is_valid ( ) ) {
2022-05-03 01:43:50 +02:00
edited_resource = Ref < Resource > ( ) ;
2021-05-19 14:19:07 +02:00
_update_resource ( ) ;
2021-05-17 14:25:38 +02:00
return ;
}
if ( ! base_type . is_empty ( ) ) {
2023-01-19 16:47:01 +01:00
HashSet < StringName > allowed_types ;
2021-05-17 14:25:38 +02:00
_get_allowed_types ( true , & allowed_types ) ;
StringName custom_class ;
bool is_custom = false ;
if ( p_resource - > get_script ( ) ) {
custom_class = EditorNode : : get_singleton ( ) - > get_object_custom_type_name ( p_resource - > get_script ( ) ) ;
is_custom = _is_type_valid ( custom_class , allowed_types ) ;
}
if ( ! is_custom & & ! _is_type_valid ( p_resource - > get_class ( ) , allowed_types ) ) {
String class_str = ( custom_class = = StringName ( ) ? p_resource - > get_class ( ) : vformat ( " %s (%s) " , custom_class , p_resource - > get_class ( ) ) ) ;
ERR_FAIL_MSG ( vformat ( " Failed to set a resource of the type '%s' because this EditorResourcePicker only accepts '%s' and its derivatives. " , class_str , base_type ) ) ;
}
}
edited_resource = p_resource ;
_update_resource ( ) ;
}
2022-05-03 01:43:50 +02:00
Ref < Resource > EditorResourcePicker : : get_edited_resource ( ) {
2021-05-17 14:25:38 +02:00
return edited_resource ;
}
2021-05-19 14:19:07 +02:00
void EditorResourcePicker : : set_toggle_mode ( bool p_enable ) {
assign_button - > set_toggle_mode ( p_enable ) ;
}
bool EditorResourcePicker : : is_toggle_mode ( ) const {
return assign_button - > is_toggle_mode ( ) ;
}
void EditorResourcePicker : : set_toggle_pressed ( bool p_pressed ) {
if ( ! is_toggle_mode ( ) ) {
return ;
}
assign_button - > set_pressed ( p_pressed ) ;
}
2021-05-17 14:25:38 +02:00
void EditorResourcePicker : : set_editable ( bool p_editable ) {
editable = p_editable ;
2022-04-29 08:06:48 +02:00
assign_button - > set_disabled ( ! editable & & ! edited_resource . is_valid ( ) ) ;
2021-05-17 14:25:38 +02:00
edit_button - > set_visible ( editable ) ;
}
bool EditorResourcePicker : : is_editable ( ) const {
return editable ;
}
2021-07-16 23:36:05 +02:00
void EditorResourcePicker : : _ensure_resource_menu ( ) {
if ( edit_menu ) {
return ;
}
edit_menu = memnew ( PopupMenu ) ;
add_child ( edit_menu ) ;
edit_menu - > connect ( " id_pressed " , callable_mp ( this , & EditorResourcePicker : : _edit_menu_cbk ) ) ;
2022-07-28 22:56:41 +02:00
edit_menu - > connect ( " popup_hide " , callable_mp ( ( BaseButton * ) edit_button , & BaseButton : : set_pressed ) . bind ( false ) ) ;
2021-07-16 23:36:05 +02:00
}
2023-06-04 23:59:03 +02:00
void EditorResourcePicker : : _gather_resources_to_duplicate ( const Ref < Resource > p_resource , TreeItem * p_item , const String & p_property_name ) const {
p_item - > set_cell_mode ( 0 , TreeItem : : CELL_MODE_CHECK ) ;
String res_name = p_resource - > get_name ( ) ;
if ( res_name . is_empty ( ) & & ! p_resource - > is_built_in ( ) ) {
res_name = p_resource - > get_path ( ) . get_file ( ) ;
}
if ( res_name . is_empty ( ) ) {
p_item - > set_text ( 0 , p_resource - > get_class ( ) ) ;
} else {
p_item - > set_text ( 0 , vformat ( " %s (%s) " , p_resource - > get_class ( ) , res_name ) ) ;
}
p_item - > set_icon ( 0 , EditorNode : : get_singleton ( ) - > get_object_icon ( p_resource . ptr ( ) ) ) ;
p_item - > set_editable ( 0 , true ) ;
Array meta ;
meta . append ( p_resource ) ;
p_item - > set_metadata ( 0 , meta ) ;
if ( ! p_property_name . is_empty ( ) ) {
p_item - > set_text ( 1 , p_property_name ) ;
}
static Vector < String > unique_exceptions = { " Image " , " Shader " , " Mesh " , " FontFile " } ;
if ( ! unique_exceptions . has ( p_resource - > get_class ( ) ) ) {
// Automatically select resource, unless it's something that shouldn't be duplicated.
p_item - > set_checked ( 0 , true ) ;
}
List < PropertyInfo > plist ;
p_resource - > get_property_list ( & plist ) ;
for ( const PropertyInfo & E : plist ) {
if ( ! ( E . usage & PROPERTY_USAGE_STORAGE ) | | E . type ! = Variant : : OBJECT | | E . hint ! = PROPERTY_HINT_RESOURCE_TYPE ) {
continue ;
}
Ref < Resource > res = p_resource - > get ( E . name ) ;
if ( res . is_null ( ) ) {
continue ;
}
TreeItem * child = p_item - > create_child ( ) ;
_gather_resources_to_duplicate ( res , child , E . name ) ;
meta = child - > get_metadata ( 0 ) ;
// Remember property name.
meta . append ( E . name ) ;
if ( ( E . usage & PROPERTY_USAGE_NEVER_DUPLICATE ) ) {
// The resource can't be duplicated, but make it appear on the list anyway.
child - > set_checked ( 0 , false ) ;
child - > set_editable ( 0 , false ) ;
}
}
}
void EditorResourcePicker : : _duplicate_selected_resources ( ) {
for ( TreeItem * item = duplicate_resources_tree - > get_root ( ) ; item ; item = item - > get_next_in_tree ( ) ) {
if ( ! item - > is_checked ( 0 ) ) {
continue ;
}
Array meta = item - > get_metadata ( 0 ) ;
Ref < Resource > res = meta [ 0 ] ;
Ref < Resource > unique_resource = res - > duplicate ( ) ;
ERR_FAIL_COND ( unique_resource . is_null ( ) ) ; // duplicate() may fail.
meta [ 0 ] = unique_resource ;
if ( meta . size ( ) = = 1 ) { // Root.
edited_resource = unique_resource ;
emit_signal ( SNAME ( " resource_changed " ) , edited_resource ) ;
_update_resource ( ) ;
} else {
Array parent_meta = item - > get_parent ( ) - > get_metadata ( 0 ) ;
Ref < Resource > parent = parent_meta [ 0 ] ;
parent - > set ( meta [ 1 ] , unique_resource ) ;
}
}
}
2021-11-17 21:08:55 +01:00
2022-07-21 01:00:58 +02:00
EditorResourcePicker : : EditorResourcePicker ( bool p_hide_assign_button_controls ) {
2021-05-17 14:25:38 +02:00
assign_button = memnew ( Button ) ;
assign_button - > set_flat ( true ) ;
assign_button - > set_h_size_flags ( SIZE_EXPAND_FILL ) ;
2023-03-31 21:17:59 +02:00
assign_button - > set_expand_icon ( true ) ;
2021-05-17 14:25:38 +02:00
assign_button - > set_clip_text ( true ) ;
2023-03-28 17:02:02 +02:00
assign_button - > set_auto_translate ( false ) ;
2023-01-14 03:37:19 +01:00
SET_DRAG_FORWARDING_GCD ( assign_button , EditorResourcePicker ) ;
2021-05-17 14:25:38 +02:00
add_child ( assign_button ) ;
2021-05-25 17:38:19 +02:00
assign_button - > connect ( " pressed " , callable_mp ( this , & EditorResourcePicker : : _resource_selected ) ) ;
assign_button - > connect ( " draw " , callable_mp ( this , & EditorResourcePicker : : _button_draw ) ) ;
assign_button - > connect ( " gui_input " , callable_mp ( this , & EditorResourcePicker : : _button_input ) ) ;
2021-05-17 14:25:38 +02:00
2022-07-21 01:00:58 +02:00
if ( ! p_hide_assign_button_controls ) {
preview_rect = memnew ( TextureRect ) ;
2022-02-25 01:19:24 +01:00
preview_rect - > set_expand_mode ( TextureRect : : EXPAND_IGNORE_SIZE ) ;
2022-07-21 01:00:58 +02:00
preview_rect - > set_anchors_and_offsets_preset ( PRESET_FULL_RECT ) ;
preview_rect - > set_offset ( SIDE_TOP , 1 ) ;
preview_rect - > set_offset ( SIDE_BOTTOM , - 1 ) ;
preview_rect - > set_offset ( SIDE_RIGHT , - 1 ) ;
2022-10-15 03:36:10 +02:00
preview_rect - > set_texture_filter ( TEXTURE_FILTER_NEAREST_WITH_MIPMAPS ) ;
2022-07-21 01:00:58 +02:00
assign_button - > add_child ( preview_rect ) ;
}
2021-05-17 14:25:38 +02:00
edit_button = memnew ( Button ) ;
edit_button - > set_flat ( true ) ;
edit_button - > set_toggle_mode ( true ) ;
edit_button - > connect ( " pressed " , callable_mp ( this , & EditorResourcePicker : : _update_menu ) ) ;
add_child ( edit_button ) ;
edit_button - > connect ( " gui_input " , callable_mp ( this , & EditorResourcePicker : : _button_input ) ) ;
2022-04-01 01:55:51 +02:00
add_theme_constant_override ( " separation " , 0 ) ;
2021-05-17 14:25:38 +02:00
}
2021-05-19 14:19:07 +02:00
2021-06-04 11:24:08 +02:00
// EditorScriptPicker
2021-05-19 14:19:07 +02:00
void EditorScriptPicker : : set_create_options ( Object * p_menu_node ) {
PopupMenu * menu_node = Object : : cast_to < PopupMenu > ( p_menu_node ) ;
if ( ! menu_node ) {
return ;
}
2021-07-17 23:22:52 +02:00
menu_node - > add_icon_item ( get_theme_icon ( SNAME ( " ScriptCreate " ) , SNAME ( " EditorIcons " ) ) , TTR ( " New Script " ) , OBJ_MENU_NEW_SCRIPT ) ;
2022-03-06 03:19:03 +01:00
if ( script_owner ) {
2022-09-29 11:53:28 +02:00
Ref < Script > scr = script_owner - > get_script ( ) ;
if ( scr . is_valid ( ) ) {
2022-03-06 03:19:03 +01:00
menu_node - > add_icon_item ( get_theme_icon ( SNAME ( " ScriptExtend " ) , SNAME ( " EditorIcons " ) ) , TTR ( " Extend Script " ) , OBJ_MENU_EXTEND_SCRIPT ) ;
}
}
2021-05-19 14:19:07 +02:00
menu_node - > add_separator ( ) ;
}
bool EditorScriptPicker : : handle_menu_selected ( int p_which ) {
switch ( p_which ) {
case OBJ_MENU_NEW_SCRIPT : {
if ( script_owner ) {
2021-11-17 21:08:55 +01:00
SceneTreeDock : : get_singleton ( ) - > open_script_dialog ( script_owner , false ) ;
2021-05-19 14:19:07 +02:00
}
return true ;
}
case OBJ_MENU_EXTEND_SCRIPT : {
if ( script_owner ) {
2021-11-17 21:08:55 +01:00
SceneTreeDock : : get_singleton ( ) - > open_script_dialog ( script_owner , true ) ;
2021-05-19 14:19:07 +02:00
}
return true ;
}
}
return false ;
}
void EditorScriptPicker : : set_script_owner ( Node * p_owner ) {
script_owner = p_owner ;
}
Node * EditorScriptPicker : : get_script_owner ( ) const {
return script_owner ;
}
void EditorScriptPicker : : _bind_methods ( ) {
ClassDB : : bind_method ( D_METHOD ( " set_script_owner " , " owner_node " ) , & EditorScriptPicker : : set_script_owner ) ;
ClassDB : : bind_method ( D_METHOD ( " get_script_owner " ) , & EditorScriptPicker : : get_script_owner ) ;
2021-06-18 01:10:18 +02:00
ADD_PROPERTY ( PropertyInfo ( Variant : : OBJECT , " script_owner " , PROPERTY_HINT_RESOURCE_TYPE , " Node " , PROPERTY_USAGE_NONE ) , " set_script_owner " , " get_script_owner " ) ;
2021-05-19 14:19:07 +02:00
}
EditorScriptPicker : : EditorScriptPicker ( ) {
}
2021-06-04 11:24:08 +02:00
// EditorShaderPicker
void EditorShaderPicker : : set_create_options ( Object * p_menu_node ) {
PopupMenu * menu_node = Object : : cast_to < PopupMenu > ( p_menu_node ) ;
if ( ! menu_node ) {
return ;
}
2021-08-20 14:48:34 +02:00
menu_node - > add_icon_item ( get_theme_icon ( SNAME ( " Shader " ) , SNAME ( " EditorIcons " ) ) , TTR ( " New Shader " ) , OBJ_MENU_NEW_SHADER ) ;
2021-06-04 11:24:08 +02:00
menu_node - > add_separator ( ) ;
}
bool EditorShaderPicker : : handle_menu_selected ( int p_which ) {
2022-09-29 11:53:28 +02:00
Ref < ShaderMaterial > ed_material = Ref < ShaderMaterial > ( get_edited_material ( ) ) ;
2021-06-04 11:24:08 +02:00
switch ( p_which ) {
case OBJ_MENU_NEW_SHADER : {
2022-09-29 11:53:28 +02:00
if ( ed_material . is_valid ( ) ) {
SceneTreeDock : : get_singleton ( ) - > open_shader_dialog ( ed_material , preferred_mode ) ;
2021-06-04 11:24:08 +02:00
return true ;
}
} break ;
default :
break ;
}
return false ;
}
void EditorShaderPicker : : set_edited_material ( ShaderMaterial * p_material ) {
edited_material = p_material ;
}
ShaderMaterial * EditorShaderPicker : : get_edited_material ( ) const {
return edited_material ;
}
2021-09-17 16:35:24 +02:00
void EditorShaderPicker : : set_preferred_mode ( int p_mode ) {
preferred_mode = p_mode ;
}
2021-06-04 11:24:08 +02:00
EditorShaderPicker : : EditorShaderPicker ( ) {
}
2022-07-21 01:00:58 +02:00
//////////////
void EditorAudioStreamPicker : : _notification ( int p_what ) {
switch ( p_what ) {
2022-08-29 11:04:31 +02:00
case NOTIFICATION_READY :
2022-07-21 01:00:58 +02:00
case NOTIFICATION_THEME_CHANGED : {
_update_resource ( ) ;
} break ;
case NOTIFICATION_INTERNAL_PROCESS : {
Ref < AudioStream > audio_stream = get_edited_resource ( ) ;
if ( audio_stream . is_valid ( ) ) {
if ( audio_stream - > get_length ( ) > 0 ) {
Ref < AudioStreamPreview > preview = AudioStreamPreviewGenerator : : get_singleton ( ) - > generate_preview ( audio_stream ) ;
if ( preview . is_valid ( ) ) {
if ( preview - > get_version ( ) ! = last_preview_version ) {
2022-08-13 23:21:24 +02:00
stream_preview_rect - > queue_redraw ( ) ;
2022-07-21 01:00:58 +02:00
last_preview_version = preview - > get_version ( ) ;
}
}
}
uint64_t tagged_frame = audio_stream - > get_tagged_frame ( ) ;
uint64_t diff_frames = AudioServer : : get_singleton ( ) - > get_mixed_frames ( ) - tagged_frame ;
uint64_t diff_msec = diff_frames * 1000 / AudioServer : : get_singleton ( ) - > get_mix_rate ( ) ;
if ( diff_msec < 300 ) {
uint32_t count = audio_stream - > get_tagged_frame_count ( ) ;
bool differ = false ;
if ( count ! = tagged_frame_offset_count ) {
differ = true ;
}
float offsets [ MAX_TAGGED_FRAMES ] ;
for ( uint32_t i = 0 ; i < MIN ( count , uint32_t ( MAX_TAGGED_FRAMES ) ) ; i + + ) {
offsets [ i ] = audio_stream - > get_tagged_frame_offset ( i ) ;
if ( offsets [ i ] ! = tagged_frame_offsets [ i ] ) {
differ = true ;
}
}
if ( differ ) {
tagged_frame_offset_count = count ;
for ( uint32_t i = 0 ; i < count ; i + + ) {
tagged_frame_offsets [ i ] = offsets [ i ] ;
}
}
2022-08-13 23:21:24 +02:00
stream_preview_rect - > queue_redraw ( ) ;
2022-07-21 01:00:58 +02:00
} else {
if ( tagged_frame_offset_count ! = 0 ) {
2022-08-13 23:21:24 +02:00
stream_preview_rect - > queue_redraw ( ) ;
2022-07-21 01:00:58 +02:00
}
tagged_frame_offset_count = 0 ;
}
}
} break ;
}
}
void EditorAudioStreamPicker : : _update_resource ( ) {
EditorResourcePicker : : _update_resource ( ) ;
Ref < Font > font = get_theme_font ( SNAME ( " font " ) , SNAME ( " Label " ) ) ;
int font_size = get_theme_font_size ( SNAME ( " font_size " ) , SNAME ( " Label " ) ) ;
Ref < AudioStream > audio_stream = get_edited_resource ( ) ;
if ( audio_stream . is_valid ( ) & & audio_stream - > get_length ( ) > 0.0 ) {
set_assign_button_min_size ( Size2 ( 1 , font - > get_height ( font_size ) * 3 ) ) ;
} else {
set_assign_button_min_size ( Size2 ( 1 , font - > get_height ( font_size ) * 1.5 ) ) ;
}
2022-08-13 23:21:24 +02:00
stream_preview_rect - > queue_redraw ( ) ;
2022-07-21 01:00:58 +02:00
}
void EditorAudioStreamPicker : : _preview_draw ( ) {
Ref < AudioStream > audio_stream = get_edited_resource ( ) ;
if ( ! audio_stream . is_valid ( ) ) {
2022-07-25 00:15:20 +02:00
get_assign_button ( ) - > set_text ( TTR ( " <empty> " ) ) ;
2022-07-21 01:00:58 +02:00
return ;
}
int font_size = get_theme_font_size ( SNAME ( " font_size " ) , SNAME ( " Label " ) ) ;
get_assign_button ( ) - > set_text ( " " ) ;
Size2i size = stream_preview_rect - > get_size ( ) ;
Ref < Font > font = get_theme_font ( SNAME ( " font " ) , SNAME ( " Label " ) ) ;
Rect2 rect ( Point2 ( ) , size ) ;
2023-05-12 06:50:58 +02:00
if ( audio_stream - > get_length ( ) > 0 & & size . width > 0 ) {
2022-07-21 01:00:58 +02:00
rect . size . height * = 0.5 ;
stream_preview_rect - > draw_rect ( rect , Color ( 0 , 0 , 0 , 1 ) ) ;
Ref < AudioStreamPreview > preview = AudioStreamPreviewGenerator : : get_singleton ( ) - > generate_preview ( audio_stream ) ;
float preview_len = preview - > get_length ( ) ;
2023-05-12 06:50:58 +02:00
Vector < Vector2 > points ;
points . resize ( size . width * 2 ) ;
2022-07-21 01:00:58 +02:00
for ( int i = 0 ; i < size . width ; i + + ) {
float ofs = i * preview_len / size . width ;
float ofs_n = ( i + 1 ) * preview_len / size . width ;
float max = preview - > get_max ( ofs , ofs_n ) * 0.5 + 0.5 ;
float min = preview - > get_min ( ofs , ofs_n ) * 0.5 + 0.5 ;
int idx = i ;
2023-05-12 06:50:58 +02:00
points . write [ idx * 2 + 0 ] = Vector2 ( i + 1 , rect . position . y + min * rect . size . y ) ;
points . write [ idx * 2 + 1 ] = Vector2 ( i + 1 , rect . position . y + max * rect . size . y ) ;
2022-07-21 01:00:58 +02:00
}
2023-05-12 06:50:58 +02:00
Vector < Color > colors = { get_theme_color ( SNAME ( " contrast_color_2 " ) , SNAME ( " Editor " ) ) } ;
2022-07-21 01:00:58 +02:00
2023-05-12 06:50:58 +02:00
RS : : get_singleton ( ) - > canvas_item_add_multiline ( stream_preview_rect - > get_canvas_item ( ) , points , colors ) ;
2022-07-21 01:00:58 +02:00
if ( tagged_frame_offset_count ) {
Color accent = get_theme_color ( SNAME ( " accent_color " ) , SNAME ( " Editor " ) ) ;
for ( uint32_t i = 0 ; i < tagged_frame_offset_count ; i + + ) {
int x = CLAMP ( tagged_frame_offsets [ i ] * size . width / preview_len , 0 , size . width ) ;
if ( x = = 0 ) {
continue ; // Because some may always return 0, ignore offset 0.
}
stream_preview_rect - > draw_rect ( Rect2i ( x , 0 , 2 , rect . size . height ) , accent ) ;
}
}
rect . position . y + = rect . size . height ;
}
Ref < Texture2D > icon ;
Color icon_modulate ( 1 , 1 , 1 , 1 ) ;
if ( tagged_frame_offset_count > 0 ) {
icon = get_theme_icon ( SNAME ( " Play " ) , SNAME ( " EditorIcons " ) ) ;
if ( ( OS : : get_singleton ( ) - > get_ticks_msec ( ) % 500 ) > 250 ) {
icon_modulate = Color ( 1 , 0.5 , 0.5 , 1 ) ; // get_theme_color(SNAME("accent_color"), SNAME("Editor"));
}
} else {
icon = EditorNode : : get_singleton ( ) - > get_object_icon ( audio_stream . operator - > ( ) , " Object " ) ;
}
String text ;
if ( ! audio_stream - > get_name ( ) . is_empty ( ) ) {
text = audio_stream - > get_name ( ) ;
} else if ( audio_stream - > get_path ( ) . is_resource_file ( ) ) {
text = audio_stream - > get_path ( ) . get_file ( ) ;
} else {
text = audio_stream - > get_class ( ) . replace_first ( " AudioStream " , " " ) ;
}
stream_preview_rect - > draw_texture ( icon , Point2i ( EDSCALE * 2 , rect . position . y + ( rect . size . height - icon - > get_height ( ) ) / 2 ) , icon_modulate ) ;
stream_preview_rect - > draw_string ( font , Point2i ( EDSCALE * 2 + icon - > get_width ( ) , rect . position . y + font - > get_ascent ( font_size ) + ( rect . size . height - font - > get_height ( font_size ) ) / 2 ) , text , HORIZONTAL_ALIGNMENT_CENTER , size . width - 4 * EDSCALE - icon - > get_width ( ) ) ;
}
EditorAudioStreamPicker : : EditorAudioStreamPicker ( ) :
EditorResourcePicker ( true ) {
stream_preview_rect = memnew ( Control ) ;
stream_preview_rect - > set_anchors_and_offsets_preset ( PRESET_FULL_RECT ) ;
stream_preview_rect - > set_offset ( SIDE_TOP , 1 ) ;
stream_preview_rect - > set_offset ( SIDE_BOTTOM , - 1 ) ;
stream_preview_rect - > set_offset ( SIDE_RIGHT , - 1 ) ;
stream_preview_rect - > set_mouse_filter ( MOUSE_FILTER_IGNORE ) ;
stream_preview_rect - > connect ( " draw " , callable_mp ( this , & EditorAudioStreamPicker : : _preview_draw ) ) ;
get_assign_button ( ) - > add_child ( stream_preview_rect ) ;
get_assign_button ( ) - > move_child ( stream_preview_rect , 0 ) ;
set_process_internal ( true ) ;
}