1/**************************************************************************/
2/* editor_node.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
31#include "editor_node.h"
32
33#include "core/config/project_settings.h"
34#include "core/input/input.h"
35#include "core/io/config_file.h"
36#include "core/io/file_access.h"
37#include "core/io/resource_loader.h"
38#include "core/io/resource_saver.h"
39#include "core/object/class_db.h"
40#include "core/object/message_queue.h"
41#include "core/os/keyboard.h"
42#include "core/os/os.h"
43#include "core/os/time.h"
44#include "core/string/print_string.h"
45#include "core/string/translation.h"
46#include "core/version.h"
47#include "editor/editor_string_names.h"
48#include "main/main.h"
49#include "scene/gui/color_picker.h"
50#include "scene/gui/dialogs.h"
51#include "scene/gui/file_dialog.h"
52#include "scene/gui/link_button.h"
53#include "scene/gui/menu_bar.h"
54#include "scene/gui/menu_button.h"
55#include "scene/gui/panel.h"
56#include "scene/gui/panel_container.h"
57#include "scene/gui/popup.h"
58#include "scene/gui/rich_text_label.h"
59#include "scene/gui/split_container.h"
60#include "scene/gui/tab_container.h"
61#include "scene/main/window.h"
62#include "scene/property_utils.h"
63#include "scene/resources/image_texture.h"
64#include "scene/resources/packed_scene.h"
65#include "scene/resources/portable_compressed_texture.h"
66#include "scene/theme/theme_db.h"
67#include "servers/display_server.h"
68#include "servers/navigation_server_3d.h"
69#include "servers/physics_server_2d.h"
70
71#include "editor/audio_stream_preview.h"
72#include "editor/debugger/editor_debugger_node.h"
73#include "editor/debugger/script_editor_debugger.h"
74#include "editor/dependency_editor.h"
75#include "editor/editor_about.h"
76#include "editor/editor_audio_buses.h"
77#include "editor/editor_build_profile.h"
78#include "editor/editor_command_palette.h"
79#include "editor/editor_data.h"
80#include "editor/editor_feature_profile.h"
81#include "editor/editor_folding.h"
82#include "editor/editor_help.h"
83#include "editor/editor_inspector.h"
84#include "editor/editor_interface.h"
85#include "editor/editor_layouts_dialog.h"
86#include "editor/editor_log.h"
87#include "editor/editor_native_shader_source_visualizer.h"
88#include "editor/editor_paths.h"
89#include "editor/editor_plugin.h"
90#include "editor/editor_properties.h"
91#include "editor/editor_property_name_processor.h"
92#include "editor/editor_quick_open.h"
93#include "editor/editor_resource_preview.h"
94#include "editor/editor_run.h"
95#include "editor/editor_run_native.h"
96#include "editor/editor_scale.h"
97#include "editor/editor_settings.h"
98#include "editor/editor_settings_dialog.h"
99#include "editor/editor_themes.h"
100#include "editor/editor_translation_parser.h"
101#include "editor/editor_undo_redo_manager.h"
102#include "editor/export/editor_export.h"
103#include "editor/export/export_template_manager.h"
104#include "editor/export/project_export.h"
105#include "editor/fbx_importer_manager.h"
106#include "editor/filesystem_dock.h"
107#include "editor/gui/editor_file_dialog.h"
108#include "editor/gui/editor_run_bar.h"
109#include "editor/gui/editor_scene_tabs.h"
110#include "editor/gui/editor_title_bar.h"
111#include "editor/gui/editor_toaster.h"
112#include "editor/history_dock.h"
113#include "editor/import/audio_stream_import_settings.h"
114#include "editor/import/dynamic_font_import_settings.h"
115#include "editor/import/editor_import_collada.h"
116#include "editor/import/resource_importer_bitmask.h"
117#include "editor/import/resource_importer_bmfont.h"
118#include "editor/import/resource_importer_csv_translation.h"
119#include "editor/import/resource_importer_dynamic_font.h"
120#include "editor/import/resource_importer_image.h"
121#include "editor/import/resource_importer_imagefont.h"
122#include "editor/import/resource_importer_layered_texture.h"
123#include "editor/import/resource_importer_obj.h"
124#include "editor/import/resource_importer_shader_file.h"
125#include "editor/import/resource_importer_texture.h"
126#include "editor/import/resource_importer_texture_atlas.h"
127#include "editor/import/resource_importer_wav.h"
128#include "editor/import/scene_import_settings.h"
129#include "editor/import_dock.h"
130#include "editor/inspector_dock.h"
131#include "editor/multi_node_edit.h"
132#include "editor/node_dock.h"
133#include "editor/plugin_config_dialog.h"
134#include "editor/plugins/animation_player_editor_plugin.h"
135#include "editor/plugins/asset_library_editor_plugin.h"
136#include "editor/plugins/canvas_item_editor_plugin.h"
137#include "editor/plugins/debugger_editor_plugin.h"
138#include "editor/plugins/dedicated_server_export_plugin.h"
139#include "editor/plugins/editor_preview_plugins.h"
140#include "editor/plugins/editor_resource_conversion_plugin.h"
141#include "editor/plugins/gdextension_export_plugin.h"
142#include "editor/plugins/material_editor_plugin.h"
143#include "editor/plugins/mesh_library_editor_plugin.h"
144#include "editor/plugins/node_3d_editor_plugin.h"
145#include "editor/plugins/packed_scene_translation_parser_plugin.h"
146#include "editor/plugins/root_motion_editor_plugin.h"
147#include "editor/plugins/script_text_editor.h"
148#include "editor/plugins/text_editor.h"
149#include "editor/plugins/version_control_editor_plugin.h"
150#include "editor/plugins/visual_shader_editor_plugin.h"
151#include "editor/progress_dialog.h"
152#include "editor/project_settings_editor.h"
153#include "editor/register_exporters.h"
154#include "editor/scene_tree_dock.h"
155#include "editor/window_wrapper.h"
156
157#include <stdio.h>
158#include <stdlib.h>
159
160EditorNode *EditorNode::singleton = nullptr;
161
162// The metadata key used to store and retrieve the version text to copy to the clipboard.
163static const String META_TEXT_TO_COPY = "text_to_copy";
164
165static const String EDITOR_NODE_CONFIG_SECTION = "EditorNode";
166
167void EditorNode::disambiguate_filenames(const Vector<String> p_full_paths, Vector<String> &r_filenames) {
168 ERR_FAIL_COND_MSG(p_full_paths.size() != r_filenames.size(), vformat("disambiguate_filenames requires two string vectors of same length (%d != %d).", p_full_paths.size(), r_filenames.size()));
169
170 // Keep track of a list of "index sets," i.e. sets of indices
171 // within disambiguated_scene_names which contain the same name.
172 Vector<RBSet<int>> index_sets;
173 HashMap<String, int> scene_name_to_set_index;
174 for (int i = 0; i < r_filenames.size(); i++) {
175 String scene_name = r_filenames[i];
176 if (!scene_name_to_set_index.has(scene_name)) {
177 index_sets.append(RBSet<int>());
178 scene_name_to_set_index.insert(r_filenames[i], index_sets.size() - 1);
179 }
180 index_sets.write[scene_name_to_set_index[scene_name]].insert(i);
181 }
182
183 // For each index set with a size > 1, we need to disambiguate.
184 for (int i = 0; i < index_sets.size(); i++) {
185 RBSet<int> iset = index_sets[i];
186 while (iset.size() > 1) {
187 // Append the parent folder to each scene name.
188 for (const int &E : iset) {
189 int set_idx = E;
190 String scene_name = r_filenames[set_idx];
191 String full_path = p_full_paths[set_idx];
192
193 // Get rid of file extensions and res:// prefixes.
194 scene_name = scene_name.get_basename();
195 if (full_path.begins_with("res://")) {
196 full_path = full_path.substr(6);
197 }
198 full_path = full_path.get_basename();
199
200 // Normalize trailing slashes when normalizing directory names.
201 scene_name = scene_name.trim_suffix("/");
202 full_path = full_path.trim_suffix("/");
203
204 int scene_name_size = scene_name.size();
205 int full_path_size = full_path.size();
206 int difference = full_path_size - scene_name_size;
207
208 // Find just the parent folder of the current path and append it.
209 // If the current name is foo.tscn, and the full path is /some/folder/foo.tscn
210 // then slash_idx is the second '/', so that we select just "folder", and
211 // append that to yield "folder/foo.tscn".
212 if (difference > 0) {
213 String parent = full_path.substr(0, difference);
214 int slash_idx = parent.rfind("/");
215 slash_idx = parent.rfind("/", slash_idx - 1);
216 parent = (slash_idx >= 0 && parent.length() > 1) ? parent.substr(slash_idx + 1) : parent;
217 r_filenames.write[set_idx] = parent + r_filenames[set_idx];
218 }
219 }
220
221 // Loop back through scene names and remove non-ambiguous names.
222 bool can_proceed = false;
223 RBSet<int>::Element *E = iset.front();
224 while (E) {
225 String scene_name = r_filenames[E->get()];
226 bool duplicate_found = false;
227 for (const int &F : iset) {
228 if (E->get() == F) {
229 continue;
230 }
231 String other_scene_name = r_filenames[F];
232 if (other_scene_name == scene_name) {
233 duplicate_found = true;
234 break;
235 }
236 }
237
238 RBSet<int>::Element *to_erase = duplicate_found ? nullptr : E;
239
240 // We need to check that we could actually append anymore names
241 // if we wanted to for disambiguation. If we can't, then we have
242 // to abort even with ambiguous names. We clean the full path
243 // and the scene name first to remove extensions so that this
244 // comparison actually works.
245 String path = p_full_paths[E->get()];
246
247 // Get rid of file extensions and res:// prefixes.
248 scene_name = scene_name.get_basename();
249 if (path.begins_with("res://")) {
250 path = path.substr(6);
251 }
252 path = path.get_basename();
253
254 // Normalize trailing slashes when normalizing directory names.
255 scene_name = scene_name.trim_suffix("/");
256 path = path.trim_suffix("/");
257
258 // We can proceed if the full path is longer than the scene name,
259 // meaning that there is at least one more parent folder we can
260 // tack onto the name.
261 can_proceed = can_proceed || (path.size() - scene_name.size()) >= 1;
262
263 E = E->next();
264 if (to_erase) {
265 iset.erase(to_erase);
266 }
267 }
268
269 if (!can_proceed) {
270 break;
271 }
272 }
273 }
274}
275
276void EditorNode::_version_control_menu_option(int p_idx) {
277 switch (vcs_actions_menu->get_item_id(p_idx)) {
278 case RUN_VCS_METADATA: {
279 VersionControlEditorPlugin::get_singleton()->popup_vcs_metadata_dialog();
280 } break;
281 case RUN_VCS_SETTINGS: {
282 VersionControlEditorPlugin::get_singleton()->popup_vcs_set_up_dialog(gui_base);
283 } break;
284 }
285}
286
287void EditorNode::_update_title() {
288 const String appname = GLOBAL_GET("application/config/name");
289 String title = (appname.is_empty() ? TTR("Unnamed Project") : appname);
290 const String edited = editor_data.get_edited_scene_root() ? editor_data.get_edited_scene_root()->get_scene_file_path() : String();
291 if (!edited.is_empty()) {
292 // Display the edited scene name before the program name so that it can be seen in the OS task bar.
293 title = vformat("%s - %s", edited.get_file(), title);
294 }
295 if (unsaved_cache) {
296 // Display the "modified" mark before anything else so that it can always be seen in the OS task bar.
297 title = vformat("(*) %s", title);
298 }
299 DisplayServer::get_singleton()->window_set_title(title + String(" - ") + VERSION_NAME);
300 if (project_title) {
301 project_title->set_text(title);
302 }
303}
304
305void EditorNode::shortcut_input(const Ref<InputEvent> &p_event) {
306 ERR_FAIL_COND(p_event.is_null());
307
308 Ref<InputEventKey> k = p_event;
309 if ((k.is_valid() && k->is_pressed() && !k->is_echo()) || Object::cast_to<InputEventShortcut>(*p_event)) {
310 EditorPlugin *old_editor = editor_plugin_screen;
311
312 if (ED_IS_SHORTCUT("editor/filter_files", p_event)) {
313 FileSystemDock::get_singleton()->focus_on_filter();
314 }
315
316 if (ED_IS_SHORTCUT("editor/editor_2d", p_event)) {
317 editor_select(EDITOR_2D);
318 } else if (ED_IS_SHORTCUT("editor/editor_3d", p_event)) {
319 editor_select(EDITOR_3D);
320 } else if (ED_IS_SHORTCUT("editor/editor_script", p_event)) {
321 editor_select(EDITOR_SCRIPT);
322 } else if (ED_IS_SHORTCUT("editor/editor_help", p_event)) {
323 emit_signal(SNAME("request_help_search"), "");
324 } else if (ED_IS_SHORTCUT("editor/editor_assetlib", p_event) && AssetLibraryEditorPlugin::is_available()) {
325 editor_select(EDITOR_ASSETLIB);
326 } else if (ED_IS_SHORTCUT("editor/editor_next", p_event)) {
327 _editor_select_next();
328 } else if (ED_IS_SHORTCUT("editor/editor_prev", p_event)) {
329 _editor_select_prev();
330 } else if (ED_IS_SHORTCUT("editor/command_palette", p_event)) {
331 _open_command_palette();
332 } else {
333 }
334
335 if (old_editor != editor_plugin_screen) {
336 get_tree()->get_root()->set_input_as_handled();
337 }
338 }
339}
340
341void EditorNode::_update_from_settings() {
342 _update_title();
343
344 int current_filter = GLOBAL_GET("rendering/textures/canvas_textures/default_texture_filter");
345 if (current_filter != scene_root->get_default_canvas_item_texture_filter()) {
346 Viewport::DefaultCanvasItemTextureFilter tf = (Viewport::DefaultCanvasItemTextureFilter)current_filter;
347 scene_root->set_default_canvas_item_texture_filter(tf);
348 }
349 int current_repeat = GLOBAL_GET("rendering/textures/canvas_textures/default_texture_repeat");
350 if (current_repeat != scene_root->get_default_canvas_item_texture_repeat()) {
351 Viewport::DefaultCanvasItemTextureRepeat tr = (Viewport::DefaultCanvasItemTextureRepeat)current_repeat;
352 scene_root->set_default_canvas_item_texture_repeat(tr);
353 }
354
355 RS::DOFBokehShape dof_shape = RS::DOFBokehShape(int(GLOBAL_GET("rendering/camera/depth_of_field/depth_of_field_bokeh_shape")));
356 RS::get_singleton()->camera_attributes_set_dof_blur_bokeh_shape(dof_shape);
357 RS::DOFBlurQuality dof_quality = RS::DOFBlurQuality(int(GLOBAL_GET("rendering/camera/depth_of_field/depth_of_field_bokeh_quality")));
358 bool dof_jitter = GLOBAL_GET("rendering/camera/depth_of_field/depth_of_field_use_jitter");
359 RS::get_singleton()->camera_attributes_set_dof_blur_quality(dof_quality, dof_jitter);
360 RS::get_singleton()->environment_set_ssao_quality(RS::EnvironmentSSAOQuality(int(GLOBAL_GET("rendering/environment/ssao/quality"))), GLOBAL_GET("rendering/environment/ssao/half_size"), GLOBAL_GET("rendering/environment/ssao/adaptive_target"), GLOBAL_GET("rendering/environment/ssao/blur_passes"), GLOBAL_GET("rendering/environment/ssao/fadeout_from"), GLOBAL_GET("rendering/environment/ssao/fadeout_to"));
361 RS::get_singleton()->screen_space_roughness_limiter_set_active(GLOBAL_GET("rendering/anti_aliasing/screen_space_roughness_limiter/enabled"), GLOBAL_GET("rendering/anti_aliasing/screen_space_roughness_limiter/amount"), GLOBAL_GET("rendering/anti_aliasing/screen_space_roughness_limiter/limit"));
362 bool glow_bicubic = int(GLOBAL_GET("rendering/environment/glow/upscale_mode")) > 0;
363 RS::get_singleton()->environment_set_ssil_quality(RS::EnvironmentSSILQuality(int(GLOBAL_GET("rendering/environment/ssil/quality"))), GLOBAL_GET("rendering/environment/ssil/half_size"), GLOBAL_GET("rendering/environment/ssil/adaptive_target"), GLOBAL_GET("rendering/environment/ssil/blur_passes"), GLOBAL_GET("rendering/environment/ssil/fadeout_from"), GLOBAL_GET("rendering/environment/ssil/fadeout_to"));
364 RS::get_singleton()->environment_glow_set_use_bicubic_upscale(glow_bicubic);
365 RS::EnvironmentSSRRoughnessQuality ssr_roughness_quality = RS::EnvironmentSSRRoughnessQuality(int(GLOBAL_GET("rendering/environment/screen_space_reflection/roughness_quality")));
366 RS::get_singleton()->environment_set_ssr_roughness_quality(ssr_roughness_quality);
367 RS::SubSurfaceScatteringQuality sss_quality = RS::SubSurfaceScatteringQuality(int(GLOBAL_GET("rendering/environment/subsurface_scattering/subsurface_scattering_quality")));
368 RS::get_singleton()->sub_surface_scattering_set_quality(sss_quality);
369 float sss_scale = GLOBAL_GET("rendering/environment/subsurface_scattering/subsurface_scattering_scale");
370 float sss_depth_scale = GLOBAL_GET("rendering/environment/subsurface_scattering/subsurface_scattering_depth_scale");
371 RS::get_singleton()->sub_surface_scattering_set_scale(sss_scale, sss_depth_scale);
372
373 uint32_t directional_shadow_size = GLOBAL_GET("rendering/lights_and_shadows/directional_shadow/size");
374 uint32_t directional_shadow_16_bits = GLOBAL_GET("rendering/lights_and_shadows/directional_shadow/16_bits");
375 RS::get_singleton()->directional_shadow_atlas_set_size(directional_shadow_size, directional_shadow_16_bits);
376
377 RS::ShadowQuality shadows_quality = RS::ShadowQuality(int(GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/soft_shadow_filter_quality")));
378 RS::get_singleton()->positional_soft_shadow_filter_set_quality(shadows_quality);
379 RS::ShadowQuality directional_shadow_quality = RS::ShadowQuality(int(GLOBAL_GET("rendering/lights_and_shadows/directional_shadow/soft_shadow_filter_quality")));
380 RS::get_singleton()->directional_soft_shadow_filter_set_quality(directional_shadow_quality);
381 float probe_update_speed = GLOBAL_GET("rendering/lightmapping/probe_capture/update_speed");
382 RS::get_singleton()->lightmap_set_probe_capture_update_speed(probe_update_speed);
383 RS::EnvironmentSDFGIFramesToConverge frames_to_converge = RS::EnvironmentSDFGIFramesToConverge(int(GLOBAL_GET("rendering/global_illumination/sdfgi/frames_to_converge")));
384 RS::get_singleton()->environment_set_sdfgi_frames_to_converge(frames_to_converge);
385 RS::EnvironmentSDFGIRayCount ray_count = RS::EnvironmentSDFGIRayCount(int(GLOBAL_GET("rendering/global_illumination/sdfgi/probe_ray_count")));
386 RS::get_singleton()->environment_set_sdfgi_ray_count(ray_count);
387 RS::VoxelGIQuality voxel_gi_quality = RS::VoxelGIQuality(int(GLOBAL_GET("rendering/global_illumination/voxel_gi/quality")));
388 RS::get_singleton()->voxel_gi_set_quality(voxel_gi_quality);
389 RS::get_singleton()->environment_set_volumetric_fog_volume_size(GLOBAL_GET("rendering/environment/volumetric_fog/volume_size"), GLOBAL_GET("rendering/environment/volumetric_fog/volume_depth"));
390 RS::get_singleton()->environment_set_volumetric_fog_filter_active(bool(GLOBAL_GET("rendering/environment/volumetric_fog/use_filter")));
391 RS::get_singleton()->canvas_set_shadow_texture_size(GLOBAL_GET("rendering/2d/shadow_atlas/size"));
392
393 bool use_half_res_gi = GLOBAL_GET("rendering/global_illumination/gi/use_half_resolution");
394 RS::get_singleton()->gi_set_use_half_resolution(use_half_res_gi);
395
396 bool snap_2d_transforms = GLOBAL_GET("rendering/2d/snap/snap_2d_transforms_to_pixel");
397 scene_root->set_snap_2d_transforms_to_pixel(snap_2d_transforms);
398 bool snap_2d_vertices = GLOBAL_GET("rendering/2d/snap/snap_2d_vertices_to_pixel");
399 scene_root->set_snap_2d_vertices_to_pixel(snap_2d_vertices);
400
401 Viewport::SDFOversize sdf_oversize = Viewport::SDFOversize(int(GLOBAL_GET("rendering/2d/sdf/oversize")));
402 scene_root->set_sdf_oversize(sdf_oversize);
403 Viewport::SDFScale sdf_scale = Viewport::SDFScale(int(GLOBAL_GET("rendering/2d/sdf/scale")));
404 scene_root->set_sdf_scale(sdf_scale);
405
406 Viewport::MSAA msaa = Viewport::MSAA(int(GLOBAL_GET("rendering/anti_aliasing/quality/msaa_2d")));
407 scene_root->set_msaa_2d(msaa);
408
409 bool use_hdr_2d = GLOBAL_GET("rendering/viewport/hdr_2d");
410 scene_root->set_use_hdr_2d(use_hdr_2d);
411
412 float mesh_lod_threshold = GLOBAL_GET("rendering/mesh_lod/lod_change/threshold_pixels");
413 scene_root->set_mesh_lod_threshold(mesh_lod_threshold);
414
415 RS::get_singleton()->decals_set_filter(RS::DecalFilter(int(GLOBAL_GET("rendering/textures/decals/filter"))));
416 RS::get_singleton()->light_projectors_set_filter(RS::LightProjectorFilter(int(GLOBAL_GET("rendering/textures/light_projectors/filter"))));
417
418 SceneTree *tree = get_tree();
419 tree->set_debug_collisions_color(GLOBAL_GET("debug/shapes/collision/shape_color"));
420 tree->set_debug_collision_contact_color(GLOBAL_GET("debug/shapes/collision/contact_color"));
421
422 ResourceImporterTexture::get_singleton()->update_imports();
423
424#ifdef DEBUG_ENABLED
425 NavigationServer3D::get_singleton()->set_debug_navigation_edge_connection_color(GLOBAL_GET("debug/shapes/navigation/edge_connection_color"));
426 NavigationServer3D::get_singleton()->set_debug_navigation_geometry_edge_color(GLOBAL_GET("debug/shapes/navigation/geometry_edge_color"));
427 NavigationServer3D::get_singleton()->set_debug_navigation_geometry_face_color(GLOBAL_GET("debug/shapes/navigation/geometry_face_color"));
428 NavigationServer3D::get_singleton()->set_debug_navigation_geometry_edge_disabled_color(GLOBAL_GET("debug/shapes/navigation/geometry_edge_disabled_color"));
429 NavigationServer3D::get_singleton()->set_debug_navigation_geometry_face_disabled_color(GLOBAL_GET("debug/shapes/navigation/geometry_face_disabled_color"));
430 NavigationServer3D::get_singleton()->set_debug_navigation_enable_edge_connections(GLOBAL_GET("debug/shapes/navigation/enable_edge_connections"));
431 NavigationServer3D::get_singleton()->set_debug_navigation_enable_edge_connections_xray(GLOBAL_GET("debug/shapes/navigation/enable_edge_connections_xray"));
432 NavigationServer3D::get_singleton()->set_debug_navigation_enable_edge_lines(GLOBAL_GET("debug/shapes/navigation/enable_edge_lines"));
433 NavigationServer3D::get_singleton()->set_debug_navigation_enable_edge_lines_xray(GLOBAL_GET("debug/shapes/navigation/enable_edge_lines_xray"));
434 NavigationServer3D::get_singleton()->set_debug_navigation_enable_geometry_face_random_color(GLOBAL_GET("debug/shapes/navigation/enable_geometry_face_random_color"));
435#endif // DEBUG_ENABLED
436}
437
438void EditorNode::_select_default_main_screen_plugin() {
439 if (EDITOR_3D < main_editor_buttons.size() && main_editor_buttons[EDITOR_3D]->is_visible()) {
440 // If the 3D editor is enabled, use this as the default.
441 editor_select(EDITOR_3D);
442 return;
443 }
444
445 // Switch to the first main screen plugin that is enabled. Usually this is
446 // 2D, but may be subsequent ones if 2D is disabled in the feature profile.
447 for (int i = 0; i < main_editor_buttons.size(); i++) {
448 Button *editor_button = main_editor_buttons[i];
449 if (editor_button->is_visible()) {
450 editor_select(i);
451 return;
452 }
453 }
454
455 editor_select(-1);
456}
457
458void EditorNode::_update_theme(bool p_skip_creation) {
459 if (!p_skip_creation) {
460 theme = create_custom_theme(theme);
461 DisplayServer::set_early_window_clear_color_override(true, theme->get_color(SNAME("background"), EditorStringName(Editor)));
462 }
463
464 List<Ref<Theme>> editor_themes;
465 editor_themes.push_back(theme);
466 editor_themes.push_back(ThemeDB::get_singleton()->get_default_theme());
467
468 ThemeContext *node_tc = ThemeDB::get_singleton()->get_theme_context(this);
469 if (node_tc) {
470 node_tc->set_themes(editor_themes);
471 } else {
472 ThemeDB::get_singleton()->create_theme_context(this, editor_themes);
473 }
474
475 Window *window = get_window();
476 if (window) {
477 ThemeContext *window_tc = ThemeDB::get_singleton()->get_theme_context(window);
478 if (window_tc) {
479 window_tc->set_themes(editor_themes);
480 } else {
481 ThemeDB::get_singleton()->create_theme_context(window, editor_themes);
482 }
483 }
484
485 if (CanvasItemEditor::get_singleton()->get_theme_preview() == CanvasItemEditor::THEME_PREVIEW_EDITOR) {
486 update_preview_themes(CanvasItemEditor::THEME_PREVIEW_EDITOR);
487 }
488
489 gui_base->add_theme_style_override("panel", theme->get_stylebox(SNAME("Background"), EditorStringName(EditorStyles)));
490 main_vbox->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, theme->get_constant(SNAME("window_border_margin"), EditorStringName(Editor)));
491 main_vbox->add_theme_constant_override("separation", theme->get_constant(SNAME("top_bar_separation"), EditorStringName(Editor)));
492
493 scene_root_parent->add_theme_style_override("panel", theme->get_stylebox(SNAME("Content"), EditorStringName(EditorStyles)));
494 bottom_panel->add_theme_style_override("panel", theme->get_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)));
495 main_menu->add_theme_style_override("hover", theme->get_stylebox(SNAME("MenuHover"), EditorStringName(EditorStyles)));
496 distraction_free->set_icon(theme->get_icon(SNAME("DistractionFree"), EditorStringName(EditorIcons)));
497 bottom_panel_raise->set_icon(theme->get_icon(SNAME("ExpandBottomDock"), EditorStringName(EditorIcons)));
498
499 if (gui_base->is_layout_rtl()) {
500 dock_tab_move_left->set_icon(theme->get_icon(SNAME("Forward"), EditorStringName(EditorIcons)));
501 dock_tab_move_right->set_icon(theme->get_icon(SNAME("Back"), EditorStringName(EditorIcons)));
502 } else {
503 dock_tab_move_left->set_icon(theme->get_icon(SNAME("Back"), EditorStringName(EditorIcons)));
504 dock_tab_move_right->set_icon(theme->get_icon(SNAME("Forward"), EditorStringName(EditorIcons)));
505 }
506
507 help_menu->set_item_icon(help_menu->get_item_index(HELP_SEARCH), theme->get_icon(SNAME("HelpSearch"), EditorStringName(EditorIcons)));
508 help_menu->set_item_icon(help_menu->get_item_index(HELP_DOCS), theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)));
509 help_menu->set_item_icon(help_menu->get_item_index(HELP_QA), theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)));
510 help_menu->set_item_icon(help_menu->get_item_index(HELP_REPORT_A_BUG), theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)));
511 help_menu->set_item_icon(help_menu->get_item_index(HELP_COPY_SYSTEM_INFO), theme->get_icon(SNAME("ActionCopy"), EditorStringName(EditorIcons)));
512 help_menu->set_item_icon(help_menu->get_item_index(HELP_SUGGEST_A_FEATURE), theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)));
513 help_menu->set_item_icon(help_menu->get_item_index(HELP_SEND_DOCS_FEEDBACK), theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)));
514 help_menu->set_item_icon(help_menu->get_item_index(HELP_COMMUNITY), theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)));
515 help_menu->set_item_icon(help_menu->get_item_index(HELP_ABOUT), theme->get_icon(SNAME("Godot"), EditorStringName(EditorIcons)));
516 help_menu->set_item_icon(help_menu->get_item_index(HELP_SUPPORT_GODOT_DEVELOPMENT), theme->get_icon(SNAME("Heart"), EditorStringName(EditorIcons)));
517
518 for (int i = 0; i < main_editor_buttons.size(); i++) {
519 main_editor_buttons.write[i]->add_theme_font_override("font", theme->get_font(SNAME("main_button_font"), EditorStringName(EditorFonts)));
520 main_editor_buttons.write[i]->add_theme_font_size_override("font_size", theme->get_font_size(SNAME("main_button_font_size"), EditorStringName(EditorFonts)));
521 }
522
523 if (EditorDebuggerNode::get_singleton()->is_visible()) {
524 bottom_panel->add_theme_style_override("panel", theme->get_stylebox(SNAME("BottomPanelDebuggerOverride"), EditorStringName(EditorStyles)));
525 }
526
527 for (int i = 0; i < main_editor_buttons.size(); i++) {
528 Button *tb = main_editor_buttons[i];
529 EditorPlugin *p_editor = editor_table[i];
530 Ref<Texture2D> icon = p_editor->get_icon();
531
532 if (icon.is_valid()) {
533 tb->set_icon(icon);
534 } else if (theme->has_icon(p_editor->get_name(), EditorStringName(EditorIcons))) {
535 tb->set_icon(theme->get_icon(p_editor->get_name(), EditorStringName(EditorIcons)));
536 }
537 }
538}
539
540void EditorNode::update_preview_themes(int p_mode) {
541 if (!scene_root->is_inside_tree()) {
542 return; // Too early.
543 }
544
545 List<Ref<Theme>> preview_themes;
546
547 switch (p_mode) {
548 case CanvasItemEditor::THEME_PREVIEW_PROJECT:
549 preview_themes.push_back(ThemeDB::get_singleton()->get_project_theme());
550 break;
551
552 case CanvasItemEditor::THEME_PREVIEW_EDITOR:
553 preview_themes.push_back(get_editor_theme());
554 break;
555
556 default:
557 break;
558 }
559
560 preview_themes.push_back(ThemeDB::get_singleton()->get_default_theme());
561
562 ThemeContext *preview_context = ThemeDB::get_singleton()->get_theme_context(scene_root);
563 if (preview_context) {
564 preview_context->set_themes(preview_themes);
565 } else {
566 ThemeDB::get_singleton()->create_theme_context(scene_root, preview_themes);
567 }
568}
569
570void EditorNode::_notification(int p_what) {
571 switch (p_what) {
572 case NOTIFICATION_PROCESS: {
573 if (opening_prev && !confirmation->is_visible()) {
574 opening_prev = false;
575 }
576
577 bool global_unsaved = EditorUndoRedoManager::get_singleton()->is_history_unsaved(EditorUndoRedoManager::GLOBAL_HISTORY);
578 bool scene_or_global_unsaved = global_unsaved || EditorUndoRedoManager::get_singleton()->is_history_unsaved(editor_data.get_current_edited_scene_history_id());
579 if (unsaved_cache != scene_or_global_unsaved) {
580 unsaved_cache = scene_or_global_unsaved;
581 _update_title();
582 }
583
584 if (editor_data.is_scene_changed(-1)) {
585 scene_tabs->update_scene_tabs();
586 }
587
588 // Update the animation frame of the update spinner.
589 uint64_t frame = Engine::get_singleton()->get_frames_drawn();
590 uint64_t tick = OS::get_singleton()->get_ticks_msec();
591
592 if (frame != update_spinner_step_frame && (tick - update_spinner_step_msec) > (1000 / 8)) {
593 update_spinner_step++;
594 if (update_spinner_step >= 8) {
595 update_spinner_step = 0;
596 }
597
598 update_spinner_step_msec = tick;
599 update_spinner_step_frame = frame + 1;
600
601 // Update the icon itself only when the spinner is visible.
602 if (EDITOR_GET("interface/editor/show_update_spinner")) {
603 update_spinner->set_icon(theme->get_icon("Progress" + itos(update_spinner_step + 1), EditorStringName(EditorIcons)));
604 }
605 }
606
607 editor_selection->update();
608
609 ResourceImporterTexture::get_singleton()->update_imports();
610
611 bottom_panel_updating = false;
612 } break;
613
614 case NOTIFICATION_ENTER_TREE: {
615 get_tree()->set_disable_node_threading(true); // No node threading while running editor.
616
617 Engine::get_singleton()->set_editor_hint(true);
618
619 Window *window = get_window();
620 if (window) {
621 // Handle macOS fullscreen and extend-to-title changes.
622 window->connect("titlebar_changed", callable_mp(this, &EditorNode::_titlebar_resized));
623 }
624
625 // Theme has already been created in the constructor, so we can skip that step.
626 _update_theme(true);
627
628 OS::get_singleton()->set_low_processor_usage_mode_sleep_usec(int(EDITOR_GET("interface/editor/low_processor_mode_sleep_usec")));
629 get_tree()->get_root()->set_as_audio_listener_3d(false);
630 get_tree()->get_root()->set_as_audio_listener_2d(false);
631 get_tree()->get_root()->set_snap_2d_transforms_to_pixel(false);
632 get_tree()->get_root()->set_snap_2d_vertices_to_pixel(false);
633 get_tree()->set_auto_accept_quit(false);
634#ifdef ANDROID_ENABLED
635 get_tree()->set_quit_on_go_back(false);
636#endif
637 get_tree()->get_root()->connect("files_dropped", callable_mp(this, &EditorNode::_dropped_files));
638
639 command_palette->register_shortcuts_as_command();
640
641 MessageQueue::get_singleton()->push_callable(callable_mp(this, &EditorNode::_begin_first_scan));
642
643 /* DO NOT LOAD SCENES HERE, WAIT FOR FILE SCANNING AND REIMPORT TO COMPLETE */
644 } break;
645
646 case NOTIFICATION_EXIT_TREE: {
647 if (progress_dialog) {
648 progress_dialog->queue_free();
649 }
650 if (load_error_dialog) {
651 load_error_dialog->queue_free();
652 }
653 if (execute_output_dialog) {
654 execute_output_dialog->queue_free();
655 }
656 if (warning) {
657 warning->queue_free();
658 }
659 if (accept) {
660 accept->queue_free();
661 }
662 if (save_accept) {
663 save_accept->queue_free();
664 }
665 editor_data.save_editor_external_data();
666 FileAccess::set_file_close_fail_notify_callback(nullptr);
667 log->deinit(); // Do not get messages anymore.
668 editor_data.clear_edited_scenes();
669 } break;
670
671 case NOTIFICATION_READY: {
672 {
673 started_timestamp = Time::get_singleton()->get_unix_time_from_system();
674 _initializing_plugins = true;
675 Vector<String> addons;
676 if (ProjectSettings::get_singleton()->has_setting("editor_plugins/enabled")) {
677 addons = GLOBAL_GET("editor_plugins/enabled");
678 }
679
680 for (int i = 0; i < addons.size(); i++) {
681 set_addon_plugin_enabled(addons[i], true);
682 }
683 _initializing_plugins = false;
684
685 if (!pending_addons.is_empty()) {
686 EditorFileSystem::get_singleton()->connect("script_classes_updated", callable_mp(this, &EditorNode::_enable_pending_addons));
687 }
688 }
689
690 RenderingServer::get_singleton()->viewport_set_disable_2d(get_scene_root()->get_viewport_rid(), true);
691 RenderingServer::get_singleton()->viewport_set_environment_mode(get_viewport()->get_viewport_rid(), RenderingServer::VIEWPORT_ENVIRONMENT_DISABLED);
692
693 feature_profile_manager->notify_changed();
694
695 _select_default_main_screen_plugin();
696
697 // Save the project after opening to mark it as last modified, except in headless mode.
698 if (DisplayServer::get_singleton()->window_can_draw()) {
699 ProjectSettings::get_singleton()->save();
700 }
701
702 _titlebar_resized();
703
704 // Set up a theme context for the 2D preview viewport using the stored preview theme.
705 CanvasItemEditor::ThemePreviewMode theme_preview_mode = (CanvasItemEditor::ThemePreviewMode)(int)EditorSettings::get_singleton()->get_project_metadata("2d_editor", "theme_preview", CanvasItemEditor::THEME_PREVIEW_PROJECT);
706 update_preview_themes(theme_preview_mode);
707
708 /* DO NOT LOAD SCENES HERE, WAIT FOR FILE SCANNING AND REIMPORT TO COMPLETE */
709 } break;
710
711 case NOTIFICATION_APPLICATION_FOCUS_IN: {
712 // Restore the original FPS cap after focusing back on the editor.
713 OS::get_singleton()->set_low_processor_usage_mode_sleep_usec(int(EDITOR_GET("interface/editor/low_processor_mode_sleep_usec")));
714
715 EditorFileSystem::get_singleton()->scan_changes();
716 _scan_external_changes();
717 } break;
718
719 case NOTIFICATION_APPLICATION_FOCUS_OUT: {
720 // Save on focus loss before applying the FPS limit to avoid slowing down the saving process.
721 if (EDITOR_GET("interface/editor/save_on_focus_loss")) {
722 _menu_option_confirm(FILE_SAVE_SCENE, false);
723 }
724
725 // Set a low FPS cap to decrease CPU/GPU usage while the editor is unfocused.
726 OS::get_singleton()->set_low_processor_usage_mode_sleep_usec(int(EDITOR_GET("interface/editor/unfocused_low_processor_mode_sleep_usec")));
727 } break;
728
729 case NOTIFICATION_WM_ABOUT: {
730 show_about();
731 } break;
732
733 case NOTIFICATION_WM_CLOSE_REQUEST: {
734 _menu_option_confirm(FILE_QUIT, false);
735 } break;
736
737 case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
738 FileDialog::set_default_show_hidden_files(EDITOR_GET("filesystem/file_dialog/show_hidden_files"));
739 EditorFileDialog::set_default_show_hidden_files(EDITOR_GET("filesystem/file_dialog/show_hidden_files"));
740 EditorFileDialog::set_default_display_mode((EditorFileDialog::DisplayMode)EDITOR_GET("filesystem/file_dialog/display_mode").operator int());
741
742 bool theme_changed =
743 EditorSettings::get_singleton()->check_changed_settings_in_group("interface/theme") ||
744 EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor/font") ||
745 EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor/main_font") ||
746 EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor/code_font") ||
747 EditorSettings::get_singleton()->check_changed_settings_in_group("text_editor/theme") ||
748 EditorSettings::get_singleton()->check_changed_settings_in_group("text_editor/help/help") ||
749 EditorSettings::get_singleton()->check_changed_settings_in_group("filesystem/file_dialog/thumbnail_size") ||
750 EditorSettings::get_singleton()->check_changed_settings_in_group("run/output/font_size") ||
751 EditorSettings::get_singleton()->check_changed_settings_in_group("interface/touchscreen/increase_scrollbar_touch_area") ||
752 EditorSettings::get_singleton()->check_changed_settings_in_group("interface/touchscreen/scale_gizmo_handles");
753
754 if (theme_changed) {
755 _update_theme();
756 }
757
758 scene_tabs->update_scene_tabs();
759 recent_scenes->reset_size();
760
761 _build_icon_type_cache();
762
763 HashSet<String> updated_textfile_extensions;
764 bool extensions_match = true;
765 const Vector<String> textfile_ext = ((String)(EDITOR_GET("docks/filesystem/textfile_extensions"))).split(",", false);
766 for (const String &E : textfile_ext) {
767 updated_textfile_extensions.insert(E);
768 if (extensions_match && !textfile_extensions.has(E)) {
769 extensions_match = false;
770 }
771 }
772
773 if (!extensions_match || updated_textfile_extensions.size() < textfile_extensions.size()) {
774 textfile_extensions = updated_textfile_extensions;
775 EditorFileSystem::get_singleton()->scan();
776 }
777
778 _update_update_spinner();
779 } break;
780 }
781}
782
783void EditorNode::_update_update_spinner() {
784 update_spinner->set_visible(EDITOR_GET("interface/editor/show_update_spinner"));
785
786 const bool update_continuously = EDITOR_GET("interface/editor/update_continuously");
787 PopupMenu *update_popup = update_spinner->get_popup();
788 update_popup->set_item_checked(update_popup->get_item_index(SETTINGS_UPDATE_CONTINUOUSLY), update_continuously);
789 update_popup->set_item_checked(update_popup->get_item_index(SETTINGS_UPDATE_WHEN_CHANGED), !update_continuously);
790
791 if (update_continuously) {
792 update_spinner->set_tooltip_text(TTR("Spins when the editor window redraws.\nUpdate Continuously is enabled, which can increase power usage. Click to disable it."));
793
794 // Use a different color for the update spinner when Update Continuously is enabled,
795 // as this feature should only be enabled for troubleshooting purposes.
796 // Make the icon modulate color overbright because icons are not completely white on a dark theme.
797 // On a light theme, icons are dark, so we need to modulate them with an even brighter color.
798 const bool dark_theme = EditorSettings::get_singleton()->is_dark_theme();
799 update_spinner->set_self_modulate(theme->get_color(SNAME("error_color"), EditorStringName(Editor)) * (dark_theme ? Color(1.1, 1.1, 1.1) : Color(4.25, 4.25, 4.25)));
800 } else {
801 update_spinner->set_tooltip_text(TTR("Spins when the editor window redraws."));
802 update_spinner->set_self_modulate(Color(1, 1, 1));
803 }
804
805 OS::get_singleton()->set_low_processor_usage_mode(!update_continuously);
806}
807
808void EditorNode::_on_plugin_ready(Object *p_script, const String &p_activate_name) {
809 Ref<Script> scr = Object::cast_to<Script>(p_script);
810 if (scr.is_null()) {
811 return;
812 }
813 if (p_activate_name.length()) {
814 set_addon_plugin_enabled(p_activate_name, true);
815 }
816 project_settings_editor->update_plugins();
817 project_settings_editor->hide();
818 push_item(scr.operator->());
819}
820
821void EditorNode::_remove_plugin_from_enabled(const String &p_name) {
822 ProjectSettings *ps = ProjectSettings::get_singleton();
823 PackedStringArray enabled_plugins = ps->get("editor_plugins/enabled");
824 for (int i = 0; i < enabled_plugins.size(); ++i) {
825 if (enabled_plugins.get(i) == p_name) {
826 enabled_plugins.remove_at(i);
827 break;
828 }
829 }
830 ps->set("editor_plugins/enabled", enabled_plugins);
831}
832
833void EditorNode::_plugin_over_edit(EditorPlugin *p_plugin, Object *p_object) {
834 if (p_object) {
835 editor_plugins_over->add_plugin(p_plugin);
836 p_plugin->make_visible(true);
837 p_plugin->edit(p_object);
838 } else {
839 editor_plugins_over->remove_plugin(p_plugin);
840 p_plugin->make_visible(false);
841 p_plugin->edit(nullptr);
842 }
843}
844
845void EditorNode::_resources_changed(const Vector<String> &p_resources) {
846 List<Ref<Resource>> changed;
847
848 int rc = p_resources.size();
849 for (int i = 0; i < rc; i++) {
850 Ref<Resource> res = ResourceCache::get_ref(p_resources.get(i));
851 if (res.is_null()) {
852 continue;
853 }
854
855 if (!res->editor_can_reload_from_file()) {
856 continue;
857 }
858 if (!res->get_path().is_resource_file() && !res->get_path().is_absolute_path()) {
859 continue;
860 }
861 if (!FileAccess::exists(res->get_path())) {
862 continue;
863 }
864
865 if (!res->get_import_path().is_empty()) {
866 // This is an imported resource, will be reloaded if reimported via the _resources_reimported() callback.
867 continue;
868 }
869
870 changed.push_back(res);
871 }
872
873 if (changed.size()) {
874 for (Ref<Resource> &res : changed) {
875 res->reload_from_file();
876 }
877 }
878}
879
880void EditorNode::_fs_changed() {
881 for (FileDialog *E : file_dialogs) {
882 E->invalidate();
883 }
884
885 for (EditorFileDialog *E : editor_file_dialogs) {
886 E->invalidate();
887 }
888
889 _mark_unsaved_scenes();
890
891 // FIXME: Move this to a cleaner location, it's hacky to do this in _fs_changed.
892 String export_error;
893 Error err = OK;
894 if (!export_defer.preset.is_empty() && !EditorFileSystem::get_singleton()->is_scanning()) {
895 String preset_name = export_defer.preset;
896 // Ensures export_project does not loop infinitely, because notifications may
897 // come during the export.
898 export_defer.preset = "";
899 Ref<EditorExportPreset> export_preset;
900 for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); ++i) {
901 export_preset = EditorExport::get_singleton()->get_export_preset(i);
902 if (export_preset->get_name() == preset_name) {
903 break;
904 }
905 export_preset.unref();
906 }
907
908 if (export_preset.is_null()) {
909 Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
910 if (da->file_exists("res://export_presets.cfg")) {
911 err = FAILED;
912 export_error = vformat(
913 "Invalid export preset name: %s.\nThe following presets were detected in this project's `export_presets.cfg`:\n\n",
914 preset_name);
915 for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); ++i) {
916 // Write the preset name between double quotes since it needs to be written between quotes on the command line if it contains spaces.
917 export_error += vformat(" \"%s\"\n", EditorExport::get_singleton()->get_export_preset(i)->get_name());
918 }
919 } else {
920 err = FAILED;
921 export_error = "This project doesn't have an `export_presets.cfg` file at its root.\nCreate an export preset from the \"Project > Export\" dialog and try again.";
922 }
923 } else {
924 Ref<EditorExportPlatform> platform = export_preset->get_platform();
925 const String export_path = export_defer.path.is_empty() ? export_preset->get_export_path() : export_defer.path;
926 if (export_path.is_empty()) {
927 err = FAILED;
928 export_error = vformat("Export preset \"%s\" doesn't have a default export path, and none was specified.", preset_name);
929 } else if (platform.is_null()) {
930 err = FAILED;
931 export_error = vformat("Export preset \"%s\" doesn't have a matching platform.", preset_name);
932 } else {
933 if (export_defer.pack_only) { // Only export .pck or .zip data pack.
934 if (export_path.ends_with(".zip")) {
935 err = platform->export_zip(export_preset, export_defer.debug, export_path);
936 } else if (export_path.ends_with(".pck")) {
937 err = platform->export_pack(export_preset, export_defer.debug, export_path);
938 }
939 } else { // Normal project export.
940 String config_error;
941 bool missing_templates;
942 if (!platform->can_export(export_preset, config_error, missing_templates, export_defer.debug)) {
943 ERR_PRINT(vformat("Cannot export project with preset \"%s\" due to configuration errors:\n%s", preset_name, config_error));
944 err = missing_templates ? ERR_FILE_NOT_FOUND : ERR_UNCONFIGURED;
945 } else {
946 platform->clear_messages();
947 err = platform->export_project(export_preset, export_defer.debug, export_path);
948 }
949 }
950 if (err != OK) {
951 export_error = vformat("Project export for preset \"%s\" failed.", preset_name);
952 } else if (platform->get_worst_message_type() >= EditorExportPlatform::EXPORT_MESSAGE_WARNING) {
953 export_error = vformat("Project export for preset \"%s\" completed with warnings.", preset_name);
954 }
955 }
956 }
957
958 if (err != OK) {
959 ERR_PRINT(export_error);
960 _exit_editor(EXIT_FAILURE);
961 } else if (!export_error.is_empty()) {
962 WARN_PRINT(export_error);
963 }
964 _exit_editor(EXIT_SUCCESS);
965 }
966}
967
968void EditorNode::_resources_reimported(const Vector<String> &p_resources) {
969 List<String> scenes;
970 int current_tab = scene_tabs->get_current_tab();
971
972 for (int i = 0; i < p_resources.size(); i++) {
973 String file_type = ResourceLoader::get_resource_type(p_resources[i]);
974 if (file_type == "PackedScene") {
975 scenes.push_back(p_resources[i]);
976 // Reload later if needed, first go with normal resources.
977 continue;
978 }
979
980 if (!ResourceCache::has(p_resources[i])) {
981 // Not loaded, no need to reload.
982 continue;
983 }
984 // Reload normally.
985 Ref<Resource> resource = ResourceCache::get_ref(p_resources[i]);
986 if (resource.is_valid()) {
987 resource->reload_from_file();
988 }
989 }
990
991 for (const String &E : scenes) {
992 reload_scene(E);
993 reload_instances_with_path_in_edited_scenes(E);
994 }
995
996 scene_tabs->set_current_tab(current_tab);
997}
998
999void EditorNode::_sources_changed(bool p_exist) {
1000 if (waiting_for_first_scan) {
1001 waiting_for_first_scan = false;
1002
1003 OS::get_singleton()->benchmark_end_measure("editor_scan_and_import");
1004
1005 // Reload the global shader variables, but this time
1006 // loading textures, as they are now properly imported.
1007 RenderingServer::get_singleton()->global_shader_parameters_load_settings(true);
1008
1009 // Start preview thread now that it's safe.
1010 if (!singleton->cmdline_export_mode) {
1011 EditorResourcePreview::get_singleton()->start();
1012 }
1013
1014 _load_editor_layout();
1015
1016 if (!defer_load_scene.is_empty()) {
1017 OS::get_singleton()->benchmark_begin_measure("editor_load_scene");
1018 load_scene(defer_load_scene);
1019 defer_load_scene = "";
1020 OS::get_singleton()->benchmark_end_measure("editor_load_scene");
1021
1022 OS::get_singleton()->benchmark_dump();
1023 }
1024 }
1025}
1026
1027void EditorNode::_scan_external_changes() {
1028 disk_changed_list->clear();
1029 TreeItem *r = disk_changed_list->create_item();
1030 disk_changed_list->set_hide_root(true);
1031 bool need_reload = false;
1032
1033 // Check if any edited scene has changed.
1034
1035 for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
1036 Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
1037 if (editor_data.get_scene_path(i) == "" || !da->file_exists(editor_data.get_scene_path(i))) {
1038 continue;
1039 }
1040
1041 uint64_t last_date = editor_data.get_scene_modified_time(i);
1042 uint64_t date = FileAccess::get_modified_time(editor_data.get_scene_path(i));
1043
1044 if (date > last_date) {
1045 TreeItem *ti = disk_changed_list->create_item(r);
1046 ti->set_text(0, editor_data.get_scene_path(i).get_file());
1047 need_reload = true;
1048 }
1049 }
1050
1051 String project_settings_path = ProjectSettings::get_singleton()->get_resource_path().path_join("project.godot");
1052 if (FileAccess::get_modified_time(project_settings_path) > ProjectSettings::get_singleton()->get_last_saved_time()) {
1053 TreeItem *ti = disk_changed_list->create_item(r);
1054 ti->set_text(0, "project.godot");
1055 need_reload = true;
1056 }
1057
1058 if (need_reload) {
1059 disk_changed->call_deferred(SNAME("popup_centered_ratio"), 0.3);
1060 }
1061}
1062
1063void EditorNode::_resave_scenes(String p_str) {
1064 save_all_scenes();
1065 ProjectSettings::get_singleton()->save();
1066 disk_changed->hide();
1067}
1068
1069void EditorNode::_reload_modified_scenes() {
1070 int current_idx = editor_data.get_edited_scene();
1071
1072 for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
1073 if (editor_data.get_scene_path(i) == "") {
1074 continue;
1075 }
1076
1077 uint64_t last_date = editor_data.get_scene_modified_time(i);
1078 uint64_t date = FileAccess::get_modified_time(editor_data.get_scene_path(i));
1079
1080 if (date > last_date) {
1081 String filename = editor_data.get_scene_path(i);
1082 editor_data.set_edited_scene(i);
1083 _remove_edited_scene(false);
1084
1085 Error err = load_scene(filename, false, false, true, false, true);
1086 if (err != OK) {
1087 ERR_PRINT(vformat("Failed to load scene: %s", filename));
1088 }
1089 editor_data.move_edited_scene_to_index(i);
1090 }
1091 }
1092
1093 _set_current_scene(current_idx);
1094 scene_tabs->update_scene_tabs();
1095 disk_changed->hide();
1096}
1097
1098void EditorNode::_reload_project_settings() {
1099 ProjectSettings::get_singleton()->setup(ProjectSettings::get_singleton()->get_resource_path(), String(), true);
1100}
1101
1102void EditorNode::_vp_resized() {
1103}
1104
1105void EditorNode::_titlebar_resized() {
1106 DisplayServer::get_singleton()->window_set_window_buttons_offset(Vector2i(title_bar->get_global_position().y + title_bar->get_size().y / 2, title_bar->get_global_position().y + title_bar->get_size().y / 2), DisplayServer::MAIN_WINDOW_ID);
1107 const Vector3i &margin = DisplayServer::get_singleton()->window_get_safe_title_margins(DisplayServer::MAIN_WINDOW_ID);
1108 if (left_menu_spacer) {
1109 int w = (gui_base->is_layout_rtl()) ? margin.y : margin.x;
1110 left_menu_spacer->set_custom_minimum_size(Size2(w, 0));
1111 }
1112 if (right_menu_spacer) {
1113 int w = (gui_base->is_layout_rtl()) ? margin.x : margin.y;
1114 right_menu_spacer->set_custom_minimum_size(Size2(w, 0));
1115 }
1116 if (title_bar) {
1117 title_bar->set_custom_minimum_size(Size2(0, margin.z - title_bar->get_global_position().y));
1118 }
1119}
1120
1121void EditorNode::_version_button_pressed() {
1122 DisplayServer::get_singleton()->clipboard_set(version_btn->get_meta(META_TEXT_TO_COPY));
1123}
1124
1125void EditorNode::_update_undo_redo_allowed() {
1126 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
1127 file_menu->set_item_disabled(file_menu->get_item_index(EDIT_UNDO), !undo_redo->has_undo());
1128 file_menu->set_item_disabled(file_menu->get_item_index(EDIT_REDO), !undo_redo->has_redo());
1129}
1130
1131void EditorNode::_node_renamed() {
1132 if (InspectorDock::get_inspector_singleton()) {
1133 InspectorDock::get_inspector_singleton()->update_tree();
1134 }
1135}
1136
1137void EditorNode::_editor_select_next() {
1138 int editor = _get_current_main_editor();
1139
1140 do {
1141 if (editor == editor_table.size() - 1) {
1142 editor = 0;
1143 } else {
1144 editor++;
1145 }
1146 } while (!main_editor_buttons[editor]->is_visible());
1147
1148 editor_select(editor);
1149}
1150
1151void EditorNode::_open_command_palette() {
1152 command_palette->open_popup();
1153}
1154
1155void EditorNode::_editor_select_prev() {
1156 int editor = _get_current_main_editor();
1157
1158 do {
1159 if (editor == 0) {
1160 editor = editor_table.size() - 1;
1161 } else {
1162 editor--;
1163 }
1164 } while (!main_editor_buttons[editor]->is_visible());
1165
1166 editor_select(editor);
1167}
1168
1169Error EditorNode::load_resource(const String &p_resource, bool p_ignore_broken_deps) {
1170 dependency_errors.clear();
1171
1172 Error err;
1173
1174 Ref<Resource> res;
1175 if (ResourceLoader::exists(p_resource, "")) {
1176 res = ResourceLoader::load(p_resource, "", ResourceFormatLoader::CACHE_MODE_REUSE, &err);
1177 } else if (textfile_extensions.has(p_resource.get_extension())) {
1178 res = ScriptEditor::get_singleton()->open_file(p_resource);
1179 }
1180 ERR_FAIL_COND_V(!res.is_valid(), ERR_CANT_OPEN);
1181
1182 if (!p_ignore_broken_deps && dependency_errors.has(p_resource)) {
1183 Vector<String> errors;
1184 for (const String &E : dependency_errors[p_resource]) {
1185 errors.push_back(E);
1186 }
1187 dependency_error->show(DependencyErrorDialog::MODE_RESOURCE, p_resource, errors);
1188 dependency_errors.erase(p_resource);
1189
1190 return ERR_FILE_MISSING_DEPENDENCIES;
1191 }
1192
1193 InspectorDock::get_singleton()->edit_resource(res);
1194 return OK;
1195}
1196
1197void EditorNode::edit_node(Node *p_node) {
1198 push_item(p_node);
1199}
1200
1201void EditorNode::edit_resource(const Ref<Resource> &p_resource) {
1202 InspectorDock::get_singleton()->edit_resource(p_resource);
1203}
1204
1205void EditorNode::save_resource_in_path(const Ref<Resource> &p_resource, const String &p_path) {
1206 editor_data.apply_changes_in_editors();
1207
1208 if (saving_resources_in_path.has(p_resource)) {
1209 return;
1210 }
1211 saving_resources_in_path.insert(p_resource);
1212
1213 int flg = 0;
1214 if (EDITOR_GET("filesystem/on_save/compress_binary_resources")) {
1215 flg |= ResourceSaver::FLAG_COMPRESS;
1216 }
1217
1218 String path = ProjectSettings::get_singleton()->localize_path(p_path);
1219 Error err = ResourceSaver::save(p_resource, path, flg | ResourceSaver::FLAG_REPLACE_SUBRESOURCE_PATHS);
1220
1221 if (err != OK) {
1222 if (ResourceLoader::is_imported(p_resource->get_path())) {
1223 show_accept(TTR("Imported resources can't be saved."), TTR("OK"));
1224 } else {
1225 show_accept(TTR("Error saving resource!"), TTR("OK"));
1226 }
1227
1228 saving_resources_in_path.erase(p_resource);
1229 return;
1230 }
1231
1232 ((Resource *)p_resource.ptr())->set_path(path);
1233 saving_resources_in_path.erase(p_resource);
1234
1235 _resource_saved(p_resource, path);
1236
1237 emit_signal(SNAME("resource_saved"), p_resource);
1238 editor_data.notify_resource_saved(p_resource);
1239}
1240
1241void EditorNode::save_resource(const Ref<Resource> &p_resource) {
1242 // If the resource has been imported, ask the user to use a different path in order to save it.
1243 String path = p_resource->get_path();
1244 if (path.is_resource_file() && !FileAccess::exists(path + ".import")) {
1245 save_resource_in_path(p_resource, p_resource->get_path());
1246 } else {
1247 save_resource_as(p_resource);
1248 }
1249}
1250
1251void EditorNode::save_resource_as(const Ref<Resource> &p_resource, const String &p_at_path) {
1252 {
1253 String path = p_resource->get_path();
1254 if (!path.is_resource_file()) {
1255 int srpos = path.find("::");
1256 if (srpos != -1) {
1257 String base = path.substr(0, srpos);
1258 if (!get_edited_scene() || get_edited_scene()->get_scene_file_path() != base) {
1259 show_warning(TTR("This resource can't be saved because it does not belong to the edited scene. Make it unique first."));
1260 return;
1261 }
1262 }
1263 } else {
1264 if (FileAccess::exists(path + ".import")) {
1265 show_warning(TTR("This resource can't be saved because it was imported from another file. Make it unique first."));
1266 return;
1267 }
1268 }
1269 }
1270
1271 file->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
1272 saving_resource = p_resource;
1273
1274 current_menu_option = RESOURCE_SAVE_AS;
1275 List<String> extensions;
1276 Ref<PackedScene> sd = memnew(PackedScene);
1277 ResourceSaver::get_recognized_extensions(p_resource, &extensions);
1278 file->clear_filters();
1279
1280 List<String> preferred;
1281 for (const String &E : extensions) {
1282 if (p_resource->is_class("Script") && (E == "tres" || E == "res")) {
1283 // This serves no purpose and confused people.
1284 continue;
1285 }
1286 file->add_filter("*." + E, E.to_upper());
1287 preferred.push_back(E);
1288 }
1289 // Lowest priority extension.
1290 List<String>::Element *res_element = preferred.find("res");
1291 if (res_element) {
1292 preferred.move_to_back(res_element);
1293 }
1294 // Highest priority extension.
1295 List<String>::Element *tres_element = preferred.find("tres");
1296 if (tres_element) {
1297 preferred.move_to_front(tres_element);
1298 }
1299
1300 if (!p_at_path.is_empty()) {
1301 file->set_current_dir(p_at_path);
1302 if (p_resource->get_path().is_resource_file()) {
1303 file->set_current_file(p_resource->get_path().get_file());
1304 } else {
1305 if (extensions.size()) {
1306 String resource_name_snake_case = p_resource->get_class().to_snake_case();
1307 file->set_current_file("new_" + resource_name_snake_case + "." + preferred.front()->get().to_lower());
1308 } else {
1309 file->set_current_file(String());
1310 }
1311 }
1312 } else if (!p_resource->get_path().is_empty()) {
1313 file->set_current_path(p_resource->get_path());
1314 if (extensions.size()) {
1315 String ext = p_resource->get_path().get_extension().to_lower();
1316 if (extensions.find(ext) == nullptr) {
1317 file->set_current_path(p_resource->get_path().replacen("." + ext, "." + extensions.front()->get()));
1318 }
1319 }
1320 } else if (preferred.size()) {
1321 String existing;
1322 if (extensions.size()) {
1323 String resource_name_snake_case = p_resource->get_class().to_snake_case();
1324 existing = "new_" + resource_name_snake_case + "." + preferred.front()->get().to_lower();
1325 }
1326 file->set_current_path(existing);
1327 }
1328 file->set_title(TTR("Save Resource As..."));
1329 file->popup_file_dialog();
1330}
1331
1332void EditorNode::_menu_option(int p_option) {
1333 _menu_option_confirm(p_option, false);
1334}
1335
1336void EditorNode::_menu_confirm_current() {
1337 _menu_option_confirm(current_menu_option, true);
1338}
1339
1340void EditorNode::trigger_menu_option(int p_option, bool p_confirmed) {
1341 _menu_option_confirm(p_option, p_confirmed);
1342}
1343
1344void EditorNode::_dialog_display_save_error(String p_file, Error p_error) {
1345 if (p_error) {
1346 switch (p_error) {
1347 case ERR_FILE_CANT_WRITE: {
1348 show_accept(TTR("Can't open file for writing:") + " " + p_file.get_extension(), TTR("OK"));
1349 } break;
1350 case ERR_FILE_UNRECOGNIZED: {
1351 show_accept(TTR("Requested file format unknown:") + " " + p_file.get_extension(), TTR("OK"));
1352 } break;
1353 default: {
1354 show_accept(TTR("Error while saving."), TTR("OK"));
1355 } break;
1356 }
1357 }
1358}
1359
1360void EditorNode::_dialog_display_load_error(String p_file, Error p_error) {
1361 if (p_error) {
1362 switch (p_error) {
1363 case ERR_CANT_OPEN: {
1364 show_accept(vformat(TTR("Can't open file '%s'. The file could have been moved or deleted."), p_file.get_file()), TTR("OK"));
1365 } break;
1366 case ERR_PARSE_ERROR: {
1367 show_accept(vformat(TTR("Error while parsing file '%s'."), p_file.get_file()), TTR("OK"));
1368 } break;
1369 case ERR_FILE_CORRUPT: {
1370 show_accept(vformat(TTR("Scene file '%s' appears to be invalid/corrupt."), p_file.get_file()), TTR("OK"));
1371 } break;
1372 case ERR_FILE_NOT_FOUND: {
1373 show_accept(vformat(TTR("Missing file '%s' or one of its dependencies."), p_file.get_file()), TTR("OK"));
1374 } break;
1375 default: {
1376 show_accept(vformat(TTR("Error while loading file '%s'."), p_file.get_file()), TTR("OK"));
1377 } break;
1378 }
1379 }
1380}
1381
1382void EditorNode::_load_editor_plugin_states_from_config(const Ref<ConfigFile> &p_config_file) {
1383 Node *scene = editor_data.get_edited_scene_root();
1384
1385 if (!scene) {
1386 return;
1387 }
1388
1389 List<String> esl;
1390 p_config_file->get_section_keys("editor_states", &esl);
1391
1392 Dictionary md;
1393 for (const String &E : esl) {
1394 Variant st = p_config_file->get_value("editor_states", E);
1395 if (st.get_type() != Variant::NIL) {
1396 md[E] = st;
1397 }
1398 }
1399
1400 editor_data.set_editor_plugin_states(md);
1401}
1402
1403void EditorNode::_save_editor_states(const String &p_file, int p_idx) {
1404 Node *scene = editor_data.get_edited_scene_root(p_idx);
1405
1406 if (!scene) {
1407 return;
1408 }
1409
1410 String path = EditorPaths::get_singleton()->get_project_settings_dir().path_join(p_file.get_file() + "-editstate-" + p_file.md5_text() + ".cfg");
1411
1412 Ref<ConfigFile> cf;
1413 cf.instantiate();
1414
1415 Dictionary md;
1416 if (p_idx < 0 || editor_data.get_edited_scene() == p_idx) {
1417 md = editor_data.get_editor_plugin_states();
1418 } else {
1419 md = editor_data.get_scene_editor_states(p_idx);
1420 }
1421
1422 List<Variant> keys;
1423 md.get_key_list(&keys);
1424 for (const Variant &E : keys) {
1425 cf->set_value("editor_states", E, md[E]);
1426 }
1427
1428 // Save the currently selected nodes.
1429
1430 List<Node *> selection = editor_selection->get_full_selected_node_list();
1431 TypedArray<NodePath> selection_paths;
1432 for (Node *selected_node : selection) {
1433 selection_paths.push_back(selected_node->get_path());
1434 }
1435 cf->set_value("editor_states", "selected_nodes", selection_paths);
1436
1437 Error err = cf->save(path);
1438 ERR_FAIL_COND_MSG(err != OK, "Cannot save config file to '" + path + "'.");
1439}
1440
1441bool EditorNode::_find_and_save_resource(Ref<Resource> p_res, HashMap<Ref<Resource>, bool> &processed, int32_t flags) {
1442 if (p_res.is_null()) {
1443 return false;
1444 }
1445
1446 if (processed.has(p_res)) {
1447 return processed[p_res];
1448 }
1449
1450 bool changed = p_res->is_edited();
1451 p_res->set_edited(false);
1452
1453 bool subchanged = _find_and_save_edited_subresources(p_res.ptr(), processed, flags);
1454
1455 if (p_res->get_path().is_resource_file()) {
1456 if (changed || subchanged) {
1457 ResourceSaver::save(p_res, p_res->get_path(), flags);
1458 }
1459 processed[p_res] = false; // Because it's a file.
1460 return false;
1461 } else {
1462 processed[p_res] = changed;
1463 return changed;
1464 }
1465}
1466
1467bool EditorNode::_find_and_save_edited_subresources(Object *obj, HashMap<Ref<Resource>, bool> &processed, int32_t flags) {
1468 bool ret_changed = false;
1469 List<PropertyInfo> pi;
1470 obj->get_property_list(&pi);
1471 for (const PropertyInfo &E : pi) {
1472 if (!(E.usage & PROPERTY_USAGE_STORAGE)) {
1473 continue;
1474 }
1475
1476 switch (E.type) {
1477 case Variant::OBJECT: {
1478 Ref<Resource> res = obj->get(E.name);
1479
1480 if (_find_and_save_resource(res, processed, flags)) {
1481 ret_changed = true;
1482 }
1483
1484 } break;
1485 case Variant::ARRAY: {
1486 Array varray = obj->get(E.name);
1487 int len = varray.size();
1488 for (int i = 0; i < len; i++) {
1489 const Variant &v = varray.get(i);
1490 Ref<Resource> res = v;
1491 if (_find_and_save_resource(res, processed, flags)) {
1492 ret_changed = true;
1493 }
1494 }
1495
1496 } break;
1497 case Variant::DICTIONARY: {
1498 Dictionary d = obj->get(E.name);
1499 List<Variant> keys;
1500 d.get_key_list(&keys);
1501 for (const Variant &F : keys) {
1502 Variant v = d[F];
1503 Ref<Resource> res = v;
1504 if (_find_and_save_resource(res, processed, flags)) {
1505 ret_changed = true;
1506 }
1507 }
1508 } break;
1509 default: {
1510 }
1511 }
1512 }
1513
1514 return ret_changed;
1515}
1516
1517void EditorNode::_save_edited_subresources(Node *scene, HashMap<Ref<Resource>, bool> &processed, int32_t flags) {
1518 _find_and_save_edited_subresources(scene, processed, flags);
1519
1520 for (int i = 0; i < scene->get_child_count(); i++) {
1521 Node *n = scene->get_child(i);
1522 if (n->get_owner() != editor_data.get_edited_scene_root()) {
1523 continue;
1524 }
1525 _save_edited_subresources(n, processed, flags);
1526 }
1527}
1528
1529void EditorNode::_find_node_types(Node *p_node, int &count_2d, int &count_3d) {
1530 if (p_node->is_class("Viewport") || (p_node != editor_data.get_edited_scene_root() && p_node->get_owner() != editor_data.get_edited_scene_root())) {
1531 return;
1532 }
1533
1534 if (p_node->is_class("CanvasItem")) {
1535 count_2d++;
1536 } else if (p_node->is_class("Node3D")) {
1537 count_3d++;
1538 }
1539
1540 for (int i = 0; i < p_node->get_child_count(); i++) {
1541 _find_node_types(p_node->get_child(i), count_2d, count_3d);
1542 }
1543}
1544
1545void EditorNode::_save_scene_with_preview(String p_file, int p_idx) {
1546 EditorProgress save("save", TTR("Saving Scene"), 4);
1547
1548 if (editor_data.get_edited_scene_root() != nullptr) {
1549 save.step(TTR("Analyzing"), 0);
1550
1551 int c2d = 0;
1552 int c3d = 0;
1553
1554 _find_node_types(editor_data.get_edited_scene_root(), c2d, c3d);
1555
1556 save.step(TTR("Creating Thumbnail"), 1);
1557 // Current view?
1558
1559 Ref<Image> img;
1560 // If neither 3D or 2D nodes are present, make a 1x1 black texture.
1561 // We cannot fallback on the 2D editor, because it may not have been used yet,
1562 // which would result in an invalid texture.
1563 if (c3d == 0 && c2d == 0) {
1564 img.instantiate();
1565 img->initialize_data(1, 1, false, Image::FORMAT_RGB8);
1566 } else if (c3d < c2d) {
1567 Ref<ViewportTexture> viewport_texture = scene_root->get_texture();
1568 if (viewport_texture->get_width() > 0 && viewport_texture->get_height() > 0) {
1569 img = viewport_texture->get_image();
1570 }
1571 } else {
1572 // The 3D editor may be disabled as a feature, but scenes can still be opened.
1573 // This check prevents the preview from regenerating in case those scenes are then saved.
1574 // The preview will be generated if no feature profile is set (as the 3D editor is enabled by default).
1575 Ref<EditorFeatureProfile> profile = feature_profile_manager->get_current_profile();
1576 if (!profile.is_valid() || !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_3D)) {
1577 img = Node3DEditor::get_singleton()->get_editor_viewport(0)->get_viewport_node()->get_texture()->get_image();
1578 }
1579 }
1580
1581 if (img.is_valid() && img->get_width() > 0 && img->get_height() > 0) {
1582 img = img->duplicate();
1583
1584 save.step(TTR("Creating Thumbnail"), 2);
1585 save.step(TTR("Creating Thumbnail"), 3);
1586
1587 int preview_size = EDITOR_GET("filesystem/file_dialog/thumbnail_size");
1588 preview_size *= EDSCALE;
1589
1590 // Consider a square region.
1591 int vp_size = MIN(img->get_width(), img->get_height());
1592 int x = (img->get_width() - vp_size) / 2;
1593 int y = (img->get_height() - vp_size) / 2;
1594
1595 if (vp_size < preview_size) {
1596 // Just square it.
1597 img->crop_from_point(x, y, vp_size, vp_size);
1598 } else {
1599 int ratio = vp_size / preview_size;
1600 int size = preview_size * MAX(1, ratio / 2);
1601
1602 x = (img->get_width() - size) / 2;
1603 y = (img->get_height() - size) / 2;
1604
1605 img->crop_from_point(x, y, size, size);
1606 img->resize(preview_size, preview_size, Image::INTERPOLATE_LANCZOS);
1607 }
1608 img->convert(Image::FORMAT_RGB8);
1609
1610 // Save thumbnail directly, as thumbnailer may not update due to actual scene not changing md5.
1611 String temp_path = EditorPaths::get_singleton()->get_cache_dir();
1612 String cache_base = ProjectSettings::get_singleton()->globalize_path(p_file).md5_text();
1613 cache_base = temp_path.path_join("resthumb-" + cache_base);
1614
1615 // Does not have it, try to load a cached thumbnail.
1616 post_process_preview(img);
1617 img->save_png(cache_base + ".png");
1618 }
1619 }
1620
1621 save.step(TTR("Saving Scene"), 4);
1622 _save_scene(p_file, p_idx);
1623
1624 if (!singleton->cmdline_export_mode) {
1625 EditorResourcePreview::get_singleton()->check_for_invalidation(p_file);
1626 }
1627}
1628
1629bool EditorNode::_validate_scene_recursive(const String &p_filename, Node *p_node) {
1630 for (int i = 0; i < p_node->get_child_count(); i++) {
1631 Node *child = p_node->get_child(i);
1632 if (child->get_scene_file_path() == p_filename) {
1633 return true;
1634 }
1635
1636 if (_validate_scene_recursive(p_filename, child)) {
1637 return true;
1638 }
1639 }
1640
1641 return false;
1642}
1643
1644int EditorNode::_save_external_resources() {
1645 // Save external resources and its subresources if any was modified.
1646
1647 int flg = 0;
1648 if (EDITOR_GET("filesystem/on_save/compress_binary_resources")) {
1649 flg |= ResourceSaver::FLAG_COMPRESS;
1650 }
1651 flg |= ResourceSaver::FLAG_REPLACE_SUBRESOURCE_PATHS;
1652
1653 HashSet<String> edited_resources;
1654 int saved = 0;
1655 List<Ref<Resource>> cached;
1656 ResourceCache::get_cached_resources(&cached);
1657
1658 for (Ref<Resource> res : cached) {
1659 if (!res->is_edited()) {
1660 continue;
1661 }
1662
1663 String path = res->get_path();
1664 if (path.begins_with("res://")) {
1665 int subres_pos = path.find("::");
1666 if (subres_pos == -1) {
1667 // Actual resource.
1668 edited_resources.insert(path);
1669 } else {
1670 edited_resources.insert(path.substr(0, subres_pos));
1671 }
1672 }
1673
1674 res->set_edited(false);
1675 }
1676
1677 for (const String &E : edited_resources) {
1678 Ref<Resource> res = ResourceCache::get_ref(E);
1679 if (!res.is_valid()) {
1680 continue; // Maybe it was erased in a thread, who knows.
1681 }
1682 Ref<PackedScene> ps = res;
1683 if (ps.is_valid()) {
1684 continue; // Do not save PackedScenes, this will mess up the editor.
1685 }
1686 ResourceSaver::save(res, res->get_path(), flg);
1687 saved++;
1688 }
1689
1690 EditorUndoRedoManager::get_singleton()->set_history_as_saved(EditorUndoRedoManager::GLOBAL_HISTORY);
1691
1692 return saved;
1693}
1694
1695static void _reset_animation_players(Node *p_node, List<Ref<AnimatedValuesBackup>> *r_anim_backups) {
1696 for (int i = 0; i < p_node->get_child_count(); i++) {
1697 AnimationPlayer *player = Object::cast_to<AnimationPlayer>(p_node->get_child(i));
1698 if (player && player->is_reset_on_save_enabled() && player->can_apply_reset()) {
1699 Ref<AnimatedValuesBackup> old_values = player->apply_reset();
1700 if (old_values.is_valid()) {
1701 r_anim_backups->push_back(old_values);
1702 }
1703 }
1704 _reset_animation_players(p_node->get_child(i), r_anim_backups);
1705 }
1706}
1707
1708void EditorNode::_save_scene(String p_file, int idx) {
1709 Node *scene = editor_data.get_edited_scene_root(idx);
1710
1711 if (!scene) {
1712 show_accept(TTR("This operation can't be done without a tree root."), TTR("OK"));
1713 return;
1714 }
1715
1716 if (!scene->get_scene_file_path().is_empty() && _validate_scene_recursive(scene->get_scene_file_path(), scene)) {
1717 show_accept(TTR("This scene can't be saved because there is a cyclic instance inclusion.\nPlease resolve it and then attempt to save again."), TTR("OK"));
1718 return;
1719 }
1720
1721 scene->propagate_notification(NOTIFICATION_EDITOR_PRE_SAVE);
1722
1723 editor_data.apply_changes_in_editors();
1724 List<Ref<AnimatedValuesBackup>> anim_backups;
1725 _reset_animation_players(scene, &anim_backups);
1726 save_default_environment();
1727
1728 _save_editor_states(p_file, idx);
1729
1730 Ref<PackedScene> sdata;
1731
1732 if (ResourceCache::has(p_file)) {
1733 // Something may be referencing this resource and we are good with that.
1734 // We must update it, but also let the previous scene state go, as
1735 // old version still work for referencing changes in instantiated or inherited scenes.
1736
1737 sdata = ResourceCache::get_ref(p_file);
1738 if (sdata.is_valid()) {
1739 sdata->recreate_state();
1740 } else {
1741 sdata.instantiate();
1742 }
1743 } else {
1744 sdata.instantiate();
1745 }
1746 Error err = sdata->pack(scene);
1747
1748 if (err != OK) {
1749 show_accept(TTR("Couldn't save scene. Likely dependencies (instances or inheritance) couldn't be satisfied."), TTR("OK"));
1750 return;
1751 }
1752
1753 int flg = 0;
1754 if (EDITOR_GET("filesystem/on_save/compress_binary_resources")) {
1755 flg |= ResourceSaver::FLAG_COMPRESS;
1756 }
1757 flg |= ResourceSaver::FLAG_REPLACE_SUBRESOURCE_PATHS;
1758
1759 err = ResourceSaver::save(sdata, p_file, flg);
1760
1761 // This needs to be emitted before saving external resources.
1762 emit_signal(SNAME("scene_saved"), p_file);
1763
1764 _save_external_resources();
1765 editor_data.save_editor_external_data();
1766
1767 for (Ref<AnimatedValuesBackup> &E : anim_backups) {
1768 E->restore();
1769 }
1770
1771 if (err == OK) {
1772 scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(p_file));
1773 editor_data.set_scene_as_saved(idx);
1774 editor_data.set_scene_modified_time(idx, FileAccess::get_modified_time(p_file));
1775
1776 editor_folding.save_scene_folding(scene, p_file);
1777
1778 _update_title();
1779 scene_tabs->update_scene_tabs();
1780 } else {
1781 _dialog_display_save_error(p_file, err);
1782 }
1783
1784 scene->propagate_notification(NOTIFICATION_EDITOR_POST_SAVE);
1785}
1786
1787void EditorNode::save_all_scenes() {
1788 project_run_bar->stop_playing();
1789 _save_all_scenes();
1790}
1791
1792void EditorNode::save_scene_list(Vector<String> p_scene_filenames) {
1793 for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
1794 Node *scene = editor_data.get_edited_scene_root(i);
1795
1796 if (scene && (p_scene_filenames.find(scene->get_scene_file_path()) >= 0)) {
1797 _save_scene(scene->get_scene_file_path(), i);
1798 }
1799 }
1800}
1801
1802void EditorNode::save_before_run() {
1803 current_menu_option = FILE_SAVE_AND_RUN;
1804 _menu_option_confirm(FILE_SAVE_AS_SCENE, true);
1805 file->set_title(TTR("Save scene before running..."));
1806}
1807
1808void EditorNode::try_autosave() {
1809 if (!bool(EDITOR_GET("run/auto_save/save_before_running"))) {
1810 return;
1811 }
1812
1813 if (unsaved_cache) {
1814 Node *scene = editor_data.get_edited_scene_root();
1815
1816 if (scene && !scene->get_scene_file_path().is_empty()) { // Only autosave if there is a scene and if it has a path.
1817 _save_scene_with_preview(scene->get_scene_file_path());
1818 }
1819 }
1820 _menu_option(FILE_SAVE_ALL_SCENES);
1821 editor_data.save_editor_external_data();
1822}
1823
1824void EditorNode::restart_editor() {
1825 exiting = true;
1826
1827 if (project_run_bar->is_playing()) {
1828 project_run_bar->stop_playing();
1829 }
1830
1831 String to_reopen;
1832 if (get_tree()->get_edited_scene_root()) {
1833 to_reopen = get_tree()->get_edited_scene_root()->get_scene_file_path();
1834 }
1835
1836 _exit_editor(EXIT_SUCCESS);
1837
1838 List<String> args;
1839
1840 for (const String &a : Main::get_forwardable_cli_arguments(Main::CLI_SCOPE_TOOL)) {
1841 args.push_back(a);
1842 }
1843
1844 args.push_back("--path");
1845 args.push_back(ProjectSettings::get_singleton()->get_resource_path());
1846
1847 args.push_back("-e");
1848
1849 if (!to_reopen.is_empty()) {
1850 args.push_back(to_reopen);
1851 }
1852
1853 OS::get_singleton()->set_restart_on_exit(true, args);
1854}
1855
1856void EditorNode::_save_all_scenes() {
1857 bool all_saved = true;
1858 for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
1859 Node *scene = editor_data.get_edited_scene_root(i);
1860 if (scene) {
1861 if (!scene->get_scene_file_path().is_empty() && DirAccess::exists(scene->get_scene_file_path().get_base_dir())) {
1862 if (i != editor_data.get_edited_scene()) {
1863 _save_scene(scene->get_scene_file_path(), i);
1864 } else {
1865 _save_scene_with_preview(scene->get_scene_file_path());
1866 }
1867 } else if (!scene->get_scene_file_path().is_empty()) {
1868 all_saved = false;
1869 }
1870 }
1871 }
1872
1873 if (!all_saved) {
1874 show_warning(TTR("Could not save one or more scenes!"), TTR("Save All Scenes"));
1875 }
1876 save_default_environment();
1877}
1878
1879void EditorNode::_mark_unsaved_scenes() {
1880 for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
1881 Node *node = editor_data.get_edited_scene_root(i);
1882 if (!node) {
1883 continue;
1884 }
1885
1886 String path = node->get_scene_file_path();
1887 if (!path.is_empty() && !FileAccess::exists(path)) {
1888 // Mark scene tab as unsaved if the file is gone.
1889 EditorUndoRedoManager::get_singleton()->set_history_as_unsaved(editor_data.get_scene_history_id(i));
1890 }
1891 }
1892
1893 _update_title();
1894 scene_tabs->update_scene_tabs();
1895}
1896
1897void EditorNode::_dialog_action(String p_file) {
1898 switch (current_menu_option) {
1899 case FILE_NEW_INHERITED_SCENE: {
1900 Node *scene = editor_data.get_edited_scene_root();
1901 // If the previous scene is rootless, just close it in favor of the new one.
1902 if (!scene) {
1903 _menu_option_confirm(FILE_CLOSE, true);
1904 }
1905
1906 load_scene(p_file, false, true);
1907 } break;
1908 case FILE_OPEN_SCENE: {
1909 load_scene(p_file);
1910 } break;
1911 case SETTINGS_PICK_MAIN_SCENE: {
1912 ProjectSettings::get_singleton()->set("application/run/main_scene", p_file);
1913 ProjectSettings::get_singleton()->save();
1914 // TODO: Would be nice to show the project manager opened with the highlighted field.
1915
1916 project_run_bar->play_main_scene((bool)pick_main_scene->get_meta("from_native", false));
1917 } break;
1918 case FILE_CLOSE:
1919 case SCENE_TAB_CLOSE:
1920 case FILE_SAVE_SCENE:
1921 case FILE_SAVE_AS_SCENE: {
1922 int scene_idx = (current_menu_option == FILE_SAVE_SCENE || current_menu_option == FILE_SAVE_AS_SCENE) ? -1 : tab_closing_idx;
1923
1924 if (file->get_file_mode() == EditorFileDialog::FILE_MODE_SAVE_FILE) {
1925 bool same_open_scene = false;
1926 for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
1927 if (editor_data.get_scene_path(i) == p_file && i != scene_idx) {
1928 same_open_scene = true;
1929 }
1930 }
1931
1932 if (same_open_scene) {
1933 show_warning(TTR("Can't overwrite scene that is still open!"));
1934 return;
1935 }
1936
1937 save_default_environment();
1938 _save_scene_with_preview(p_file, scene_idx);
1939 _add_to_recent_scenes(p_file);
1940 save_editor_layout_delayed();
1941
1942 if (scene_idx != -1) {
1943 _discard_changes();
1944 } else {
1945 // Update the path of the edited scene to ensure later do/undo action history matches.
1946 editor_data.set_scene_path(editor_data.get_edited_scene(), p_file);
1947 }
1948 }
1949
1950 } break;
1951
1952 case FILE_SAVE_AND_RUN: {
1953 if (file->get_file_mode() == EditorFileDialog::FILE_MODE_SAVE_FILE) {
1954 save_default_environment();
1955 _save_scene_with_preview(p_file);
1956 project_run_bar->play_custom_scene(p_file);
1957 }
1958 } break;
1959
1960 case FILE_SAVE_AND_RUN_MAIN_SCENE: {
1961 ProjectSettings::get_singleton()->set("application/run/main_scene", p_file);
1962 ProjectSettings::get_singleton()->save();
1963
1964 if (file->get_file_mode() == EditorFileDialog::FILE_MODE_SAVE_FILE) {
1965 save_default_environment();
1966 _save_scene_with_preview(p_file);
1967 project_run_bar->play_main_scene((bool)pick_main_scene->get_meta("from_native", false));
1968 }
1969 } break;
1970
1971 case FILE_EXPORT_MESH_LIBRARY: {
1972 Ref<MeshLibrary> ml;
1973 if (file_export_lib_merge->is_pressed() && FileAccess::exists(p_file)) {
1974 ml = ResourceLoader::load(p_file, "MeshLibrary");
1975
1976 if (ml.is_null()) {
1977 show_accept(TTR("Can't load MeshLibrary for merging!"), TTR("OK"));
1978 return;
1979 }
1980 }
1981
1982 if (ml.is_null()) {
1983 ml = Ref<MeshLibrary>(memnew(MeshLibrary));
1984 }
1985
1986 MeshLibraryEditor::update_library_file(editor_data.get_edited_scene_root(), ml, true, file_export_lib_apply_xforms->is_pressed());
1987
1988 Error err = ResourceSaver::save(ml, p_file);
1989 if (err) {
1990 show_accept(TTR("Error saving MeshLibrary!"), TTR("OK"));
1991 return;
1992 } else if (ResourceCache::has(p_file)) {
1993 // Make sure MeshLibrary is updated in the editor.
1994 ResourceLoader::load(p_file)->reload_from_file();
1995 }
1996
1997 } break;
1998
1999 case RESOURCE_SAVE:
2000 case RESOURCE_SAVE_AS: {
2001 ERR_FAIL_COND(saving_resource.is_null());
2002 save_resource_in_path(saving_resource, p_file);
2003 saving_resource = Ref<Resource>();
2004 ObjectID current_id = editor_history.get_current();
2005 Object *current_obj = current_id.is_valid() ? ObjectDB::get_instance(current_id) : nullptr;
2006 ERR_FAIL_NULL(current_obj);
2007 current_obj->notify_property_list_changed();
2008 } break;
2009 case SETTINGS_LAYOUT_SAVE: {
2010 if (p_file.is_empty()) {
2011 return;
2012 }
2013
2014 Ref<ConfigFile> config;
2015 config.instantiate();
2016 Error err = config->load(EditorSettings::get_singleton()->get_editor_layouts_config());
2017
2018 if (err == ERR_FILE_CANT_OPEN || err == ERR_FILE_NOT_FOUND) {
2019 config.instantiate();
2020 } else if (err != OK) {
2021 show_warning(TTR("An error occurred while trying to save the editor layout.\nMake sure the editor's user data path is writable."));
2022 return;
2023 }
2024
2025 _save_docks_to_config(config, p_file);
2026
2027 config->save(EditorSettings::get_singleton()->get_editor_layouts_config());
2028
2029 layout_dialog->hide();
2030 _update_layouts_menu();
2031
2032 if (p_file == "Default") {
2033 show_warning(TTR("Default editor layout overridden.\nTo restore the Default layout to its base settings, use the Delete Layout option and delete the Default layout."));
2034 }
2035
2036 } break;
2037 case SETTINGS_LAYOUT_DELETE: {
2038 if (p_file.is_empty()) {
2039 return;
2040 }
2041
2042 Ref<ConfigFile> config;
2043 config.instantiate();
2044 Error err = config->load(EditorSettings::get_singleton()->get_editor_layouts_config());
2045
2046 if (err != OK || !config->has_section(p_file)) {
2047 show_warning(TTR("Layout name not found!"));
2048 return;
2049 }
2050
2051 // Erase key values.
2052 List<String> keys;
2053 config->get_section_keys(p_file, &keys);
2054 for (const String &key : keys) {
2055 config->set_value(p_file, key, Variant());
2056 }
2057
2058 config->save(EditorSettings::get_singleton()->get_editor_layouts_config());
2059
2060 layout_dialog->hide();
2061 _update_layouts_menu();
2062
2063 if (p_file == "Default") {
2064 show_warning(TTR("Restored the Default layout to its base settings."));
2065 }
2066
2067 } break;
2068 default: {
2069 // Save scene?
2070 if (file->get_file_mode() == EditorFileDialog::FILE_MODE_SAVE_FILE) {
2071 _save_scene_with_preview(p_file);
2072 }
2073
2074 } break;
2075 }
2076}
2077
2078bool EditorNode::_is_class_editor_disabled_by_feature_profile(const StringName &p_class) {
2079 Ref<EditorFeatureProfile> profile = EditorFeatureProfileManager::get_singleton()->get_current_profile();
2080 if (profile.is_null()) {
2081 return false;
2082 }
2083
2084 StringName class_name = p_class;
2085
2086 while (class_name != StringName()) {
2087 if (profile->is_class_disabled(class_name)) {
2088 return true;
2089 }
2090 if (profile->is_class_editor_disabled(class_name)) {
2091 return true;
2092 }
2093 class_name = ClassDB::get_parent_class(class_name);
2094 }
2095
2096 return false;
2097}
2098
2099void EditorNode::edit_item(Object *p_object, Object *p_editing_owner) {
2100 ERR_FAIL_NULL(p_editing_owner);
2101
2102 // Editing for this type of object may be disabled by user's feature profile.
2103 if (!p_object || _is_class_editor_disabled_by_feature_profile(p_object->get_class())) {
2104 // Nothing to edit, clean up the owner context and return.
2105 hide_unused_editors(p_editing_owner);
2106 return;
2107 }
2108
2109 // Get a list of editor plugins that can handle this type of object.
2110 Vector<EditorPlugin *> available_plugins = editor_data.get_handling_sub_editors(p_object);
2111 if (available_plugins.is_empty()) {
2112 // None, clean up the owner context and return.
2113 hide_unused_editors(p_editing_owner);
2114 return;
2115 }
2116
2117 ObjectID owner_id = p_editing_owner->get_instance_id();
2118
2119 // Remove editor plugins no longer used by this editing owner. Keep the ones that can
2120 // still be reused by the new edited object.
2121
2122 List<EditorPlugin *> to_remove;
2123 for (EditorPlugin *plugin : active_plugins[owner_id]) {
2124 if (!available_plugins.has(plugin)) {
2125 to_remove.push_back(plugin);
2126 _plugin_over_edit(plugin, nullptr);
2127 }
2128 }
2129
2130 for (EditorPlugin *plugin : to_remove) {
2131 active_plugins[owner_id].erase(plugin);
2132 }
2133
2134 // Send the edited object to the plugins.
2135 for (EditorPlugin *plugin : available_plugins) {
2136 if (active_plugins[owner_id].has(plugin)) {
2137 // Plugin was already active, just change the object.
2138 plugin->edit(p_object);
2139 continue;
2140 }
2141
2142 // If plugin is already associated with another owner, remove it from there first.
2143 for (KeyValue<ObjectID, HashSet<EditorPlugin *>> &kv : active_plugins) {
2144 if (kv.key != owner_id) {
2145 EditorPropertyResource *epres = Object::cast_to<EditorPropertyResource>(ObjectDB::get_instance(kv.key));
2146 if (epres && kv.value.has(plugin)) {
2147 // If it's resource property editing the same resource type, fold it.
2148 epres->fold_resource();
2149 }
2150 kv.value.erase(plugin);
2151 }
2152 }
2153
2154 // Activate previously inactive plugin and edit the object.
2155 active_plugins[owner_id].insert(plugin);
2156 _plugin_over_edit(plugin, p_object);
2157 }
2158}
2159
2160void EditorNode::push_node_item(Node *p_node) {
2161 if (p_node || Object::cast_to<Node>(InspectorDock::get_inspector_singleton()->get_edited_object()) || Object::cast_to<MultiNodeEdit>(InspectorDock::get_inspector_singleton()->get_edited_object())) {
2162 // Don't push null if the currently edited object is not a Node.
2163 push_item(p_node);
2164 }
2165}
2166
2167void EditorNode::push_item(Object *p_object, const String &p_property, bool p_inspector_only) {
2168 if (!p_object) {
2169 InspectorDock::get_inspector_singleton()->edit(nullptr);
2170 NodeDock::get_singleton()->set_node(nullptr);
2171 SceneTreeDock::get_singleton()->set_selected(nullptr);
2172 InspectorDock::get_singleton()->update(nullptr);
2173 hide_unused_editors();
2174 return;
2175 }
2176
2177 ObjectID id = p_object->get_instance_id();
2178 if (id != editor_history.get_current()) {
2179 if (p_inspector_only) {
2180 editor_history.add_object(id, String(), true);
2181 } else if (p_property.is_empty()) {
2182 editor_history.add_object(id);
2183 } else {
2184 editor_history.add_object(id, p_property);
2185 }
2186 }
2187
2188 _edit_current();
2189}
2190
2191void EditorNode::save_default_environment() {
2192 Ref<Environment> fallback = get_tree()->get_root()->get_world_3d()->get_fallback_environment();
2193
2194 if (fallback.is_valid() && fallback->get_path().is_resource_file()) {
2195 HashMap<Ref<Resource>, bool> processed;
2196 _find_and_save_edited_subresources(fallback.ptr(), processed, 0);
2197 save_resource_in_path(fallback, fallback->get_path());
2198 }
2199}
2200
2201void EditorNode::hide_unused_editors(const Object *p_editing_owner) {
2202 if (p_editing_owner) {
2203 const ObjectID id = p_editing_owner->get_instance_id();
2204 for (EditorPlugin *plugin : active_plugins[id]) {
2205 _plugin_over_edit(plugin, nullptr);
2206 }
2207 active_plugins.erase(id);
2208 } else {
2209 // If no editing owner is provided, this method will go over all owners and check if they are valid.
2210 // This is to sweep properties that were removed from the inspector.
2211 List<ObjectID> to_remove;
2212 for (KeyValue<ObjectID, HashSet<EditorPlugin *>> &kv : active_plugins) {
2213 if (!ObjectDB::get_instance(kv.key)) {
2214 to_remove.push_back(kv.key);
2215 for (EditorPlugin *plugin : kv.value) {
2216 _plugin_over_edit(plugin, nullptr);
2217 }
2218 }
2219 }
2220
2221 for (const ObjectID &id : to_remove) {
2222 active_plugins.erase(id);
2223 }
2224 }
2225}
2226
2227static bool overrides_external_editor(Object *p_object) {
2228 Script *script = Object::cast_to<Script>(p_object);
2229
2230 if (!script) {
2231 return false;
2232 }
2233
2234 return script->get_language()->overrides_external_editor();
2235}
2236
2237void EditorNode::_edit_current(bool p_skip_foreign) {
2238 ObjectID current_id = editor_history.get_current();
2239 Object *current_obj = current_id.is_valid() ? ObjectDB::get_instance(current_id) : nullptr;
2240
2241 Ref<Resource> res = Object::cast_to<Resource>(current_obj);
2242 if (p_skip_foreign && res.is_valid()) {
2243 const int current_tab = scene_tabs->get_current_tab();
2244 if (res->get_path().find("::") > -1 && res->get_path().get_slice("::", 0) != editor_data.get_scene_path(current_tab)) {
2245 // Trying to edit resource that belongs to another scene; abort.
2246 current_obj = nullptr;
2247 }
2248 }
2249
2250 bool inspector_only = editor_history.is_current_inspector_only();
2251 this->current = current_obj;
2252
2253 if (!current_obj) {
2254 SceneTreeDock::get_singleton()->set_selected(nullptr);
2255 InspectorDock::get_inspector_singleton()->edit(nullptr);
2256 NodeDock::get_singleton()->set_node(nullptr);
2257 InspectorDock::get_singleton()->update(nullptr);
2258 hide_unused_editors();
2259 return;
2260 }
2261
2262 // Update the use folding setting and state.
2263 bool disable_folding = bool(EDITOR_GET("interface/inspector/disable_folding"));
2264 if (InspectorDock::get_inspector_singleton()->is_using_folding() == disable_folding) {
2265 InspectorDock::get_inspector_singleton()->set_use_folding(!disable_folding, false);
2266 }
2267
2268 bool is_resource = current_obj->is_class("Resource");
2269 bool is_node = current_obj->is_class("Node");
2270 bool stay_in_script_editor_on_node_selected = bool(EDITOR_GET("text_editor/behavior/navigation/stay_in_script_editor_on_node_selected"));
2271 bool skip_main_plugin = false;
2272
2273 String editable_info; // None by default.
2274 bool info_is_warning = false;
2275
2276 if (current_obj->has_method("_is_read_only")) {
2277 if (current_obj->call("_is_read_only")) {
2278 editable_info = TTR("This object is marked as read-only, so it's not editable.");
2279 }
2280 }
2281
2282 if (is_resource) {
2283 Resource *current_res = Object::cast_to<Resource>(current_obj);
2284 ERR_FAIL_NULL(current_res);
2285
2286 InspectorDock::get_inspector_singleton()->edit(current_res);
2287 SceneTreeDock::get_singleton()->set_selected(nullptr);
2288 NodeDock::get_singleton()->set_node(nullptr);
2289 InspectorDock::get_singleton()->update(nullptr);
2290 ImportDock::get_singleton()->set_edit_path(current_res->get_path());
2291
2292 int subr_idx = current_res->get_path().find("::");
2293 if (subr_idx != -1) {
2294 String base_path = current_res->get_path().substr(0, subr_idx);
2295 if (!base_path.is_resource_file()) {
2296 if (FileAccess::exists(base_path + ".import")) {
2297 if (get_edited_scene() && get_edited_scene()->get_scene_file_path() == base_path) {
2298 info_is_warning = true;
2299 }
2300 editable_info = TTR("This resource belongs to a scene that was imported, so it's not editable.\nPlease read the documentation relevant to importing scenes to better understand this workflow.");
2301 } else {
2302 if ((!get_edited_scene() || get_edited_scene()->get_scene_file_path() != base_path) && ResourceLoader::get_resource_type(base_path) == "PackedScene") {
2303 editable_info = TTR("This resource belongs to a scene that was instantiated or inherited.\nChanges to it must be made inside the original scene.");
2304 }
2305 }
2306 } else {
2307 if (FileAccess::exists(base_path + ".import")) {
2308 editable_info = TTR("This resource belongs to a scene that was imported, so it's not editable.\nPlease read the documentation relevant to importing scenes to better understand this workflow.");
2309 }
2310 }
2311 } else if (current_res->get_path().is_resource_file()) {
2312 if (FileAccess::exists(current_res->get_path() + ".import")) {
2313 editable_info = TTR("This resource was imported, so it's not editable. Change its settings in the import panel and then re-import.");
2314 }
2315 }
2316 } else if (is_node) {
2317 Node *current_node = Object::cast_to<Node>(current_obj);
2318 ERR_FAIL_NULL(current_node);
2319
2320 InspectorDock::get_inspector_singleton()->edit(current_node);
2321 if (current_node->is_inside_tree()) {
2322 NodeDock::get_singleton()->set_node(current_node);
2323 SceneTreeDock::get_singleton()->set_selected(current_node);
2324 InspectorDock::get_singleton()->update(current_node);
2325 if (!inspector_only && !skip_main_plugin) {
2326 skip_main_plugin = stay_in_script_editor_on_node_selected && !ScriptEditor::get_singleton()->is_editor_floating() && ScriptEditor::get_singleton()->is_visible_in_tree();
2327 }
2328 } else {
2329 NodeDock::get_singleton()->set_node(nullptr);
2330 SceneTreeDock::get_singleton()->set_selected(nullptr);
2331 InspectorDock::get_singleton()->update(nullptr);
2332 }
2333
2334 if (get_edited_scene() && !get_edited_scene()->get_scene_file_path().is_empty()) {
2335 String source_scene = get_edited_scene()->get_scene_file_path();
2336 if (FileAccess::exists(source_scene + ".import")) {
2337 editable_info = TTR("This scene was imported, so changes to it won't be kept.\nInstantiating or inheriting it will allow you to make changes to it.\nPlease read the documentation relevant to importing scenes to better understand this workflow.");
2338 info_is_warning = true;
2339 }
2340 }
2341
2342 } else {
2343 Node *selected_node = nullptr;
2344
2345 if (current_obj->is_class("EditorDebuggerRemoteObject")) {
2346 disable_folding = true;
2347 } else if (current_obj->is_class("MultiNodeEdit")) {
2348 Node *scene = get_edited_scene();
2349 if (scene) {
2350 MultiNodeEdit *multi_node_edit = Object::cast_to<MultiNodeEdit>(current_obj);
2351 int node_count = multi_node_edit->get_node_count();
2352 if (node_count > 0) {
2353 List<Node *> multi_nodes;
2354 for (int node_index = 0; node_index < node_count; ++node_index) {
2355 Node *node = scene->get_node(multi_node_edit->get_node(node_index));
2356 if (node) {
2357 multi_nodes.push_back(node);
2358 }
2359 }
2360 if (!multi_nodes.is_empty()) {
2361 // Pick the top-most node.
2362 multi_nodes.sort_custom<Node::Comparator>();
2363 selected_node = multi_nodes.front()->get();
2364 }
2365 }
2366 }
2367 }
2368
2369 InspectorDock::get_inspector_singleton()->edit(current_obj);
2370 NodeDock::get_singleton()->set_node(nullptr);
2371 SceneTreeDock::get_singleton()->set_selected(selected_node);
2372 InspectorDock::get_singleton()->update(nullptr);
2373 }
2374
2375 InspectorDock::get_singleton()->set_info(
2376 info_is_warning ? TTR("Changes may be lost!") : TTR("This object is read-only."),
2377 editable_info,
2378 info_is_warning);
2379
2380 Object *editor_owner = is_node ? (Object *)SceneTreeDock::get_singleton() : is_resource ? (Object *)InspectorDock::get_inspector_singleton()
2381 : (Object *)this;
2382
2383 // Take care of the main editor plugin.
2384
2385 if (!inspector_only) {
2386 EditorPlugin *main_plugin = editor_data.get_handling_main_editor(current_obj);
2387
2388 int plugin_index = 0;
2389 for (; plugin_index < editor_table.size(); plugin_index++) {
2390 if (editor_table[plugin_index] == main_plugin) {
2391 if (!main_editor_buttons[plugin_index]->is_visible()) {
2392 main_plugin = nullptr; // If button is not visible, then no plugin is active.
2393 }
2394
2395 break;
2396 }
2397 }
2398
2399 ObjectID editor_owner_id = editor_owner->get_instance_id();
2400 if (main_plugin && !skip_main_plugin) {
2401 // Special case if use of external editor is true.
2402 Resource *current_res = Object::cast_to<Resource>(current_obj);
2403 if (main_plugin->get_name() == "Script" && current_res && !current_res->is_built_in() && (bool(EDITOR_GET("text_editor/external/use_external_editor")) || overrides_external_editor(current_obj))) {
2404 if (!changing_scene) {
2405 main_plugin->edit(current_obj);
2406 }
2407 }
2408
2409 else if (main_plugin != editor_plugin_screen && (!ScriptEditor::get_singleton() || !ScriptEditor::get_singleton()->is_visible_in_tree() || ScriptEditor::get_singleton()->can_take_away_focus())) {
2410 // Unedit previous plugin.
2411 editor_plugin_screen->edit(nullptr);
2412 active_plugins[editor_owner_id].erase(editor_plugin_screen);
2413 // Update screen main_plugin.
2414 editor_select(plugin_index);
2415 main_plugin->edit(current_obj);
2416 } else {
2417 editor_plugin_screen->edit(current_obj);
2418 }
2419 is_main_screen_editing = true;
2420 } else if (!main_plugin && editor_plugin_screen && is_main_screen_editing) {
2421 editor_plugin_screen->edit(nullptr);
2422 is_main_screen_editing = false;
2423 }
2424
2425 edit_item(current_obj, editor_owner);
2426 }
2427
2428 InspectorDock::get_singleton()->update(current_obj);
2429}
2430
2431void EditorNode::_android_build_source_selected(const String &p_file) {
2432 export_template_manager->install_android_template_from_file(p_file);
2433}
2434
2435void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
2436 if (!p_confirmed) { // FIXME: this may be a hack.
2437 current_menu_option = (MenuOptions)p_option;
2438 }
2439
2440 switch (p_option) {
2441 case FILE_NEW_SCENE: {
2442 new_scene();
2443
2444 } break;
2445 case FILE_NEW_INHERITED_SCENE:
2446 case FILE_OPEN_SCENE: {
2447 file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
2448 List<String> extensions;
2449 ResourceLoader::get_recognized_extensions_for_type("PackedScene", &extensions);
2450 file->clear_filters();
2451 for (int i = 0; i < extensions.size(); i++) {
2452 file->add_filter("*." + extensions[i], extensions[i].to_upper());
2453 }
2454
2455 Node *scene = editor_data.get_edited_scene_root();
2456 if (scene) {
2457 file->set_current_path(scene->get_scene_file_path());
2458 };
2459 file->set_title(p_option == FILE_OPEN_SCENE ? TTR("Open Scene") : TTR("Open Base Scene"));
2460 file->popup_file_dialog();
2461
2462 } break;
2463 case FILE_QUICK_OPEN: {
2464 quick_open->popup_dialog("Resource", true);
2465 quick_open->set_title(TTR("Quick Open..."));
2466
2467 } break;
2468 case FILE_QUICK_OPEN_SCENE: {
2469 quick_open->popup_dialog("PackedScene", true);
2470 quick_open->set_title(TTR("Quick Open Scene..."));
2471
2472 } break;
2473 case FILE_QUICK_OPEN_SCRIPT: {
2474 quick_open->popup_dialog("Script", true);
2475 quick_open->set_title(TTR("Quick Open Script..."));
2476
2477 } break;
2478 case FILE_OPEN_PREV: {
2479 if (previous_scenes.is_empty()) {
2480 break;
2481 }
2482 opening_prev = true;
2483 open_request(previous_scenes.back()->get());
2484 previous_scenes.pop_back();
2485
2486 } break;
2487 case FILE_CLOSE_OTHERS: {
2488 tab_closing_menu_option = -1;
2489 for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
2490 if (i == editor_data.get_edited_scene()) {
2491 continue;
2492 }
2493 tabs_to_close.push_back(editor_data.get_scene_path(i));
2494 }
2495 _proceed_closing_scene_tabs();
2496 } break;
2497 case FILE_CLOSE_RIGHT: {
2498 tab_closing_menu_option = -1;
2499 for (int i = editor_data.get_edited_scene() + 1; i < editor_data.get_edited_scene_count(); i++) {
2500 tabs_to_close.push_back(editor_data.get_scene_path(i));
2501 }
2502 _proceed_closing_scene_tabs();
2503 } break;
2504 case FILE_CLOSE_ALL: {
2505 tab_closing_menu_option = -1;
2506 for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
2507 tabs_to_close.push_back(editor_data.get_scene_path(i));
2508 }
2509 _proceed_closing_scene_tabs();
2510 } break;
2511 case FILE_CLOSE: {
2512 _scene_tab_closed(editor_data.get_edited_scene());
2513 } break;
2514 case SCENE_TAB_CLOSE:
2515 case FILE_SAVE_SCENE: {
2516 int scene_idx = (p_option == FILE_SAVE_SCENE) ? -1 : tab_closing_idx;
2517 Node *scene = editor_data.get_edited_scene_root(scene_idx);
2518 if (scene && !scene->get_scene_file_path().is_empty()) {
2519 if (DirAccess::exists(scene->get_scene_file_path().get_base_dir())) {
2520 if (scene_idx != editor_data.get_edited_scene()) {
2521 _save_scene_with_preview(scene->get_scene_file_path(), scene_idx);
2522 } else {
2523 _save_scene_with_preview(scene->get_scene_file_path());
2524 }
2525
2526 if (scene_idx != -1) {
2527 _discard_changes();
2528 }
2529 save_editor_layout_delayed();
2530 } else {
2531 show_save_accept(vformat(TTR("%s no longer exists! Please specify a new save location."), scene->get_scene_file_path().get_base_dir()), TTR("OK"));
2532 }
2533 break;
2534 }
2535 [[fallthrough]];
2536 }
2537 case FILE_SAVE_AS_SCENE: {
2538 int scene_idx = (p_option == FILE_SAVE_SCENE || p_option == FILE_SAVE_AS_SCENE) ? -1 : tab_closing_idx;
2539
2540 Node *scene = editor_data.get_edited_scene_root(scene_idx);
2541
2542 if (!scene) {
2543 if (p_option == FILE_SAVE_SCENE) {
2544 // Pressing Ctrl + S saves the current script if a scene is currently open, but it won't if the scene has no root node.
2545 // Work around this by explicitly saving the script in this case (similar to pressing Ctrl + Alt + S).
2546 ScriptEditor::get_singleton()->save_current_script();
2547 }
2548
2549 const int saved = _save_external_resources();
2550 if (saved > 0) {
2551 show_accept(
2552 vformat(TTR("The current scene has no root node, but %d modified external resource(s) were saved anyway."), saved),
2553 TTR("OK"));
2554 } else if (p_option == FILE_SAVE_AS_SCENE) {
2555 // Don't show this dialog when pressing Ctrl + S to avoid interfering with script saving.
2556 show_accept(
2557 TTR("A root node is required to save the scene. You can add a root node using the Scene tree dock."),
2558 TTR("OK"));
2559 }
2560
2561 break;
2562 }
2563
2564 file->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
2565
2566 List<String> extensions;
2567 Ref<PackedScene> sd = memnew(PackedScene);
2568 ResourceSaver::get_recognized_extensions(sd, &extensions);
2569 file->clear_filters();
2570 for (int i = 0; i < extensions.size(); i++) {
2571 file->add_filter("*." + extensions[i], extensions[i].to_upper());
2572 }
2573
2574 if (!scene->get_scene_file_path().is_empty()) {
2575 String path = scene->get_scene_file_path();
2576 file->set_current_path(path);
2577 if (extensions.size()) {
2578 String ext = path.get_extension().to_lower();
2579 if (extensions.find(ext) == nullptr) {
2580 file->set_current_path(path.replacen("." + ext, "." + extensions.front()->get()));
2581 }
2582 }
2583 } else if (extensions.size()) {
2584 String root_name = scene->get_name();
2585 root_name = EditorNode::adjust_scene_name_casing(root_name);
2586 file->set_current_path(root_name + "." + extensions.front()->get().to_lower());
2587 }
2588 file->popup_file_dialog();
2589 file->set_title(TTR("Save Scene As..."));
2590
2591 } break;
2592
2593 case FILE_SAVE_ALL_SCENES: {
2594 _save_all_scenes();
2595 } break;
2596
2597 case FILE_RUN_SCENE: {
2598 project_run_bar->play_current_scene();
2599 } break;
2600
2601 case FILE_EXPORT_PROJECT: {
2602 project_export->popup_export();
2603 } break;
2604
2605 case FILE_EXTERNAL_OPEN_SCENE: {
2606 if (unsaved_cache && !p_confirmed) {
2607 confirmation->set_ok_button_text(TTR("Open"));
2608 confirmation->set_text(TTR("Current scene not saved. Open anyway?"));
2609 confirmation->popup_centered();
2610 break;
2611 }
2612
2613 bool oprev = opening_prev;
2614 Error err = load_scene(external_file);
2615 if (err == OK && oprev) {
2616 previous_scenes.pop_back();
2617 opening_prev = false;
2618 }
2619
2620 } break;
2621
2622 case EDIT_UNDO: {
2623 if ((int)Input::get_singleton()->get_mouse_button_mask() & 0x7) {
2624 log->add_message(TTR("Can't undo while mouse buttons are pressed."), EditorLog::MSG_TYPE_EDITOR);
2625 } else {
2626 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
2627 String action = undo_redo->get_current_action_name();
2628 int id = undo_redo->get_current_action_history_id();
2629 if (!undo_redo->undo()) {
2630 log->add_message(TTR("Nothing to undo."), EditorLog::MSG_TYPE_EDITOR);
2631 } else if (!action.is_empty()) {
2632 switch (id) {
2633 case EditorUndoRedoManager::GLOBAL_HISTORY:
2634 log->add_message(vformat(TTR("Global Undo: %s"), action), EditorLog::MSG_TYPE_EDITOR);
2635 break;
2636 case EditorUndoRedoManager::REMOTE_HISTORY:
2637 log->add_message(vformat(TTR("Remote Undo: %s"), action), EditorLog::MSG_TYPE_EDITOR);
2638 break;
2639 default:
2640 log->add_message(vformat(TTR("Scene Undo: %s"), action), EditorLog::MSG_TYPE_EDITOR);
2641 }
2642 }
2643 }
2644 } break;
2645 case EDIT_REDO: {
2646 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
2647 if ((int)Input::get_singleton()->get_mouse_button_mask() & 0x7) {
2648 log->add_message(TTR("Can't redo while mouse buttons are pressed."), EditorLog::MSG_TYPE_EDITOR);
2649 } else {
2650 if (!undo_redo->redo()) {
2651 log->add_message(TTR("Nothing to redo."), EditorLog::MSG_TYPE_EDITOR);
2652 } else {
2653 String action = undo_redo->get_current_action_name();
2654 if (action.is_empty()) {
2655 break;
2656 }
2657
2658 switch (undo_redo->get_current_action_history_id()) {
2659 case EditorUndoRedoManager::GLOBAL_HISTORY:
2660 log->add_message(vformat(TTR("Global Redo: %s"), action), EditorLog::MSG_TYPE_EDITOR);
2661 break;
2662 case EditorUndoRedoManager::REMOTE_HISTORY:
2663 log->add_message(vformat(TTR("Remote Redo: %s"), action), EditorLog::MSG_TYPE_EDITOR);
2664 break;
2665 default:
2666 log->add_message(vformat(TTR("Scene Redo: %s"), action), EditorLog::MSG_TYPE_EDITOR);
2667 }
2668 }
2669 }
2670 } break;
2671
2672 case EDIT_RELOAD_SAVED_SCENE: {
2673 Node *scene = get_edited_scene();
2674
2675 if (!scene) {
2676 break;
2677 }
2678
2679 String filename = scene->get_scene_file_path();
2680
2681 if (filename.is_empty()) {
2682 show_warning(TTR("Can't reload a scene that was never saved."));
2683 break;
2684 }
2685
2686 if (unsaved_cache && !p_confirmed) {
2687 confirmation->set_ok_button_text(TTR("Reload Saved Scene"));
2688 confirmation->set_text(
2689 TTR("The current scene has unsaved changes.\nReload the saved scene anyway? This action cannot be undone."));
2690 confirmation->popup_centered();
2691 break;
2692 }
2693
2694 int cur_idx = editor_data.get_edited_scene();
2695 _remove_edited_scene();
2696 Error err = load_scene(filename);
2697 if (err != OK) {
2698 ERR_PRINT("Failed to load scene");
2699 }
2700 editor_data.move_edited_scene_to_index(cur_idx);
2701 EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_current_edited_scene_history_id());
2702 scene_tabs->set_current_tab(cur_idx);
2703
2704 } break;
2705
2706 case FILE_SHOW_IN_FILESYSTEM: {
2707 String path = editor_data.get_scene_path(editor_data.get_edited_scene());
2708 if (!path.is_empty()) {
2709 FileSystemDock::get_singleton()->navigate_to_path(path);
2710 }
2711 } break;
2712
2713 case RUN_SETTINGS: {
2714 project_settings_editor->popup_project_settings();
2715 } break;
2716 case FILE_INSTALL_ANDROID_SOURCE: {
2717 if (p_confirmed) {
2718 export_template_manager->install_android_template();
2719 } else {
2720 if (DirAccess::exists("res://android/build")) {
2721 remove_android_build_template->popup_centered();
2722 } else if (export_template_manager->can_install_android_template()) {
2723 install_android_build_template->popup_centered();
2724 } else {
2725 gradle_build_manage_templates->popup_centered();
2726 }
2727 }
2728 } break;
2729 case TOOLS_BUILD_PROFILE_MANAGER: {
2730 build_profile_manager->popup_centered_clamped(Size2(700, 800) * EDSCALE, 0.8);
2731 } break;
2732 case RUN_USER_DATA_FOLDER: {
2733 // Ensure_user_data_dir() to prevent the edge case: "Open User Data Folder" won't work after the project was renamed in ProjectSettingsEditor unless the project is saved.
2734 OS::get_singleton()->ensure_user_data_dir();
2735 OS::get_singleton()->shell_show_in_file_manager(OS::get_singleton()->get_user_data_dir(), true);
2736 } break;
2737 case FILE_EXPLORE_ANDROID_BUILD_TEMPLATES: {
2738 OS::get_singleton()->shell_show_in_file_manager(ProjectSettings::get_singleton()->get_resource_path().path_join("android"), true);
2739 } break;
2740 case FILE_QUIT:
2741 case RUN_PROJECT_MANAGER:
2742 case RELOAD_CURRENT_PROJECT: {
2743 if (p_confirmed && plugin_to_save) {
2744 plugin_to_save->save_external_data();
2745 p_confirmed = false;
2746 }
2747
2748 if (!p_confirmed) {
2749 bool save_each = EDITOR_GET("interface/editor/save_each_scene_on_quit");
2750 if (_next_unsaved_scene(!save_each) == -1) {
2751 if (EditorUndoRedoManager::get_singleton()->is_history_unsaved(EditorUndoRedoManager::GLOBAL_HISTORY)) {
2752 if (p_option == RELOAD_CURRENT_PROJECT) {
2753 save_confirmation->set_ok_button_text(TTR("Save & Reload"));
2754 save_confirmation->set_text(TTR("Save modified resources before reloading?"));
2755 } else {
2756 save_confirmation->set_ok_button_text(TTR("Save & Quit"));
2757 save_confirmation->set_text(TTR("Save modified resources before closing?"));
2758 }
2759 save_confirmation->reset_size();
2760 save_confirmation->popup_centered();
2761 break;
2762 }
2763
2764 plugin_to_save = nullptr;
2765 for (int i = 0; i < editor_data.get_editor_plugin_count(); i++) {
2766 const String unsaved_status = editor_data.get_editor_plugin(i)->get_unsaved_status();
2767 if (!unsaved_status.is_empty()) {
2768 if (p_option == RELOAD_CURRENT_PROJECT) {
2769 save_confirmation->set_ok_button_text(TTR("Save & Reload"));
2770 save_confirmation->set_text(unsaved_status);
2771 } else {
2772 save_confirmation->set_ok_button_text(TTR("Save & Quit"));
2773 save_confirmation->set_text(unsaved_status);
2774 }
2775 save_confirmation->reset_size();
2776 save_confirmation->popup_centered();
2777 plugin_to_save = editor_data.get_editor_plugin(i);
2778 break;
2779 }
2780 }
2781
2782 if (plugin_to_save) {
2783 break;
2784 }
2785
2786 _discard_changes();
2787 break;
2788 }
2789
2790 if (save_each) {
2791 tab_closing_menu_option = current_menu_option;
2792 for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
2793 tabs_to_close.push_back(editor_data.get_scene_path(i));
2794 }
2795 _proceed_closing_scene_tabs();
2796 } else {
2797 String unsaved_scenes;
2798 int i = _next_unsaved_scene(true, 0);
2799 while (i != -1) {
2800 unsaved_scenes += "\n " + editor_data.get_edited_scene_root(i)->get_scene_file_path();
2801 i = _next_unsaved_scene(true, ++i);
2802 }
2803 if (p_option == RELOAD_CURRENT_PROJECT) {
2804 save_confirmation->set_ok_button_text(TTR("Save & Reload"));
2805 save_confirmation->set_text(TTR("Save changes to the following scene(s) before reloading?") + unsaved_scenes);
2806 } else {
2807 save_confirmation->set_ok_button_text(TTR("Save & Quit"));
2808 save_confirmation->set_text((p_option == FILE_QUIT ? TTR("Save changes to the following scene(s) before quitting?") : TTR("Save changes to the following scene(s) before opening Project Manager?")) + unsaved_scenes);
2809 }
2810 save_confirmation->reset_size();
2811 save_confirmation->popup_centered();
2812 }
2813
2814 DisplayServer::get_singleton()->window_request_attention();
2815 break;
2816 }
2817 _save_external_resources();
2818 _discard_changes();
2819 } break;
2820 case SETTINGS_UPDATE_CONTINUOUSLY: {
2821 EditorSettings::get_singleton()->set("interface/editor/update_continuously", true);
2822 _update_update_spinner();
2823 show_accept(TTR("This option is deprecated. Situations where refresh must be forced are now considered a bug. Please report."), TTR("OK"));
2824 } break;
2825 case SETTINGS_UPDATE_WHEN_CHANGED: {
2826 EditorSettings::get_singleton()->set("interface/editor/update_continuously", false);
2827 _update_update_spinner();
2828 } break;
2829 case SETTINGS_UPDATE_SPINNER_HIDE: {
2830 EditorSettings::get_singleton()->set("interface/editor/show_update_spinner", false);
2831 _update_update_spinner();
2832 } break;
2833 case SETTINGS_PREFERENCES: {
2834 editor_settings_dialog->popup_edit_settings();
2835 } break;
2836 case SETTINGS_EDITOR_DATA_FOLDER: {
2837 OS::get_singleton()->shell_show_in_file_manager(EditorPaths::get_singleton()->get_data_dir(), true);
2838 } break;
2839 case SETTINGS_EDITOR_CONFIG_FOLDER: {
2840 OS::get_singleton()->shell_show_in_file_manager(EditorPaths::get_singleton()->get_config_dir(), true);
2841 } break;
2842 case SETTINGS_MANAGE_EXPORT_TEMPLATES: {
2843 export_template_manager->popup_manager();
2844 } break;
2845 case SETTINGS_MANAGE_FBX_IMPORTER: {
2846#if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED)
2847 fbx_importer_manager->show_dialog();
2848#endif
2849 } break;
2850 case SETTINGS_INSTALL_ANDROID_BUILD_TEMPLATE: {
2851 gradle_build_manage_templates->hide();
2852 file_android_build_source->popup_centered_ratio();
2853 } break;
2854 case SETTINGS_MANAGE_FEATURE_PROFILES: {
2855 feature_profile_manager->popup_centered_clamped(Size2(900, 800) * EDSCALE, 0.8);
2856 } break;
2857 case SETTINGS_TOGGLE_FULLSCREEN: {
2858 DisplayServer::get_singleton()->window_set_mode(DisplayServer::get_singleton()->window_get_mode() == DisplayServer::WINDOW_MODE_FULLSCREEN ? DisplayServer::WINDOW_MODE_WINDOWED : DisplayServer::WINDOW_MODE_FULLSCREEN);
2859
2860 } break;
2861 case EDITOR_SCREENSHOT: {
2862 screenshot_timer->start();
2863 } break;
2864 case SETTINGS_PICK_MAIN_SCENE: {
2865 file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
2866 List<String> extensions;
2867 ResourceLoader::get_recognized_extensions_for_type("PackedScene", &extensions);
2868 file->clear_filters();
2869 for (int i = 0; i < extensions.size(); i++) {
2870 file->add_filter("*." + extensions[i], extensions[i].to_upper());
2871 }
2872
2873 Node *scene = editor_data.get_edited_scene_root();
2874 if (scene) {
2875 file->set_current_path(scene->get_scene_file_path());
2876 };
2877 file->set_title(TTR("Pick a Main Scene"));
2878 file->popup_file_dialog();
2879
2880 } break;
2881 case HELP_SEARCH: {
2882 emit_signal(SNAME("request_help_search"), "");
2883 } break;
2884 case HELP_COMMAND_PALETTE: {
2885 command_palette->open_popup();
2886 } break;
2887 case HELP_DOCS: {
2888 OS::get_singleton()->shell_open(VERSION_DOCS_URL "/");
2889 } break;
2890 case HELP_QA: {
2891 OS::get_singleton()->shell_open("https://godotengine.org/qa/");
2892 } break;
2893 case HELP_REPORT_A_BUG: {
2894 OS::get_singleton()->shell_open("https://github.com/godotengine/godot/issues");
2895 } break;
2896 case HELP_COPY_SYSTEM_INFO: {
2897 String info = _get_system_info();
2898 DisplayServer::get_singleton()->clipboard_set(info);
2899 } break;
2900 case HELP_SUGGEST_A_FEATURE: {
2901 OS::get_singleton()->shell_open("https://github.com/godotengine/godot-proposals#readme");
2902 } break;
2903 case HELP_SEND_DOCS_FEEDBACK: {
2904 OS::get_singleton()->shell_open("https://github.com/godotengine/godot-docs/issues");
2905 } break;
2906 case HELP_COMMUNITY: {
2907 OS::get_singleton()->shell_open("https://godotengine.org/community");
2908 } break;
2909 case HELP_ABOUT: {
2910 about->popup_centered(Size2(780, 500) * EDSCALE);
2911 } break;
2912 case HELP_SUPPORT_GODOT_DEVELOPMENT: {
2913 OS::get_singleton()->shell_open("https://godotengine.org/donate");
2914 } break;
2915 case SET_RENDERER_NAME_SAVE_AND_RESTART: {
2916 ProjectSettings::get_singleton()->set("rendering/renderer/rendering_method", renderer_request);
2917 ProjectSettings::get_singleton()->save();
2918
2919 save_all_scenes();
2920 restart_editor();
2921 } break;
2922 }
2923}
2924
2925String EditorNode::adjust_scene_name_casing(const String &root_name) {
2926 switch (GLOBAL_GET("editor/naming/scene_name_casing").operator int()) {
2927 case SCENE_NAME_CASING_AUTO:
2928 // Use casing of the root node.
2929 break;
2930 case SCENE_NAME_CASING_PASCAL_CASE:
2931 return root_name.to_pascal_case();
2932 case SCENE_NAME_CASING_SNAKE_CASE:
2933 return root_name.replace("-", "_").to_snake_case();
2934 }
2935 return root_name;
2936}
2937
2938void EditorNode::_request_screenshot() {
2939 _screenshot();
2940}
2941
2942void EditorNode::_screenshot(bool p_use_utc) {
2943 String name = "editor_screenshot_" + Time::get_singleton()->get_datetime_string_from_system(p_use_utc).replace(":", "") + ".png";
2944 NodePath path = String("user://") + name;
2945 _save_screenshot(path);
2946 if (EDITOR_GET("interface/editor/automatically_open_screenshots")) {
2947 OS::get_singleton()->shell_show_in_file_manager(ProjectSettings::get_singleton()->globalize_path(path), true);
2948 }
2949}
2950
2951void EditorNode::_save_screenshot(NodePath p_path) {
2952 Control *editor_main_screen = EditorInterface::get_singleton()->get_editor_main_screen();
2953 ERR_FAIL_NULL_MSG(editor_main_screen, "Cannot get the editor main screen control.");
2954 Viewport *viewport = editor_main_screen->get_viewport();
2955 ERR_FAIL_NULL_MSG(viewport, "Cannot get a viewport from the editor main screen.");
2956 Ref<ViewportTexture> texture = viewport->get_texture();
2957 ERR_FAIL_COND_MSG(texture.is_null(), "Cannot get a viewport texture from the editor main screen.");
2958 Ref<Image> img = texture->get_image();
2959 ERR_FAIL_COND_MSG(img.is_null(), "Cannot get an image from a viewport texture of the editor main screen.");
2960 Error error = img->save_png(p_path);
2961 ERR_FAIL_COND_MSG(error != OK, "Cannot save screenshot to file '" + p_path + "'.");
2962}
2963
2964void EditorNode::_tool_menu_option(int p_idx) {
2965 switch (tool_menu->get_item_id(p_idx)) {
2966 case TOOLS_ORPHAN_RESOURCES: {
2967 orphan_resources->show();
2968 } break;
2969 case TOOLS_CUSTOM: {
2970 if (tool_menu->get_item_submenu(p_idx) == "") {
2971 Callable callback = tool_menu->get_item_metadata(p_idx);
2972 Callable::CallError ce;
2973 Variant result;
2974 callback.callp(nullptr, 0, result, ce);
2975
2976 if (ce.error != Callable::CallError::CALL_OK) {
2977 String err = Variant::get_callable_error_text(callback, nullptr, 0, ce);
2978 ERR_PRINT("Error calling function from tool menu: " + err);
2979 }
2980 } // Else it's a submenu so don't do anything.
2981 } break;
2982 }
2983}
2984
2985void EditorNode::_export_as_menu_option(int p_idx) {
2986 if (p_idx == 0) { // MeshLibrary
2987 current_menu_option = FILE_EXPORT_MESH_LIBRARY;
2988
2989 if (!editor_data.get_edited_scene_root()) {
2990 show_accept(TTR("This operation can't be done without a scene."), TTR("OK"));
2991 return;
2992 }
2993
2994 List<String> extensions;
2995 Ref<MeshLibrary> ml(memnew(MeshLibrary));
2996 ResourceSaver::get_recognized_extensions(ml, &extensions);
2997 file_export_lib->clear_filters();
2998 for (const String &E : extensions) {
2999 file_export_lib->add_filter("*." + E);
3000 }
3001
3002 file_export_lib->popup_file_dialog();
3003 file_export_lib->set_title(TTR("Export Mesh Library"));
3004 } else { // Custom menu options added by plugins
3005 if (export_as_menu->get_item_submenu(p_idx).is_empty()) { // If not a submenu
3006 Callable callback = export_as_menu->get_item_metadata(p_idx);
3007 Callable::CallError ce;
3008 Variant result;
3009 callback.callp(nullptr, 0, result, ce);
3010
3011 if (ce.error != Callable::CallError::CALL_OK) {
3012 String err = Variant::get_callable_error_text(callback, nullptr, 0, ce);
3013 ERR_PRINT("Error calling function from export_as menu: " + err);
3014 }
3015 }
3016 }
3017}
3018
3019int EditorNode::_next_unsaved_scene(bool p_valid_filename, int p_start) {
3020 for (int i = p_start; i < editor_data.get_edited_scene_count(); i++) {
3021 if (!editor_data.get_edited_scene_root(i)) {
3022 continue;
3023 }
3024
3025 String scene_filename = editor_data.get_edited_scene_root(i)->get_scene_file_path();
3026 if (p_valid_filename && scene_filename.is_empty()) {
3027 continue;
3028 }
3029
3030 bool unsaved = EditorUndoRedoManager::get_singleton()->is_history_unsaved(editor_data.get_scene_history_id(i));
3031 if (unsaved) {
3032 return i;
3033 } else {
3034 for (int j = 0; j < editor_data.get_editor_plugin_count(); j++) {
3035 if (!editor_data.get_editor_plugin(j)->get_unsaved_status(scene_filename).is_empty()) {
3036 return i;
3037 }
3038 }
3039 }
3040 }
3041 return -1;
3042}
3043
3044void EditorNode::_exit_editor(int p_exit_code) {
3045 exiting = true;
3046 resource_preview->stop(); // Stop early to avoid crashes.
3047 _save_editor_layout();
3048
3049 // Dim the editor window while it's quitting to make it clearer that it's busy.
3050 dim_editor(true);
3051
3052 get_tree()->quit(p_exit_code);
3053}
3054
3055void EditorNode::_discard_changes(const String &p_str) {
3056 switch (current_menu_option) {
3057 case FILE_CLOSE:
3058 case SCENE_TAB_CLOSE: {
3059 Node *scene = editor_data.get_edited_scene_root(tab_closing_idx);
3060 if (scene != nullptr) {
3061 String scene_filename = scene->get_scene_file_path();
3062 if (!scene_filename.is_empty()) {
3063 previous_scenes.push_back(scene_filename);
3064 }
3065 }
3066
3067 // Don't close tabs when exiting the editor (required for "restore_scenes_on_load" setting).
3068 if (!_is_closing_editor()) {
3069 _remove_scene(tab_closing_idx);
3070 scene_tabs->update_scene_tabs();
3071 }
3072 _proceed_closing_scene_tabs();
3073 } break;
3074 case FILE_QUIT: {
3075 project_run_bar->stop_playing();
3076 _exit_editor(EXIT_SUCCESS);
3077
3078 } break;
3079 case RUN_PROJECT_MANAGER: {
3080 project_run_bar->stop_playing();
3081 _exit_editor(EXIT_SUCCESS);
3082 String exec = OS::get_singleton()->get_executable_path();
3083
3084 List<String> args;
3085 for (const String &a : Main::get_forwardable_cli_arguments(Main::CLI_SCOPE_TOOL)) {
3086 args.push_back(a);
3087 }
3088
3089 String exec_base_dir = exec.get_base_dir();
3090 if (!exec_base_dir.is_empty()) {
3091 args.push_back("--path");
3092 args.push_back(exec_base_dir);
3093 }
3094 args.push_back("--project-manager");
3095
3096 Error err = OS::get_singleton()->create_instance(args);
3097 ERR_FAIL_COND(err);
3098 } break;
3099 case RELOAD_CURRENT_PROJECT: {
3100 restart_editor();
3101 } break;
3102 }
3103}
3104
3105void EditorNode::_update_file_menu_opened() {
3106 file_menu->set_item_disabled(file_menu->get_item_index(FILE_OPEN_PREV), previous_scenes.is_empty());
3107 _update_undo_redo_allowed();
3108}
3109
3110void EditorNode::_update_file_menu_closed() {
3111 file_menu->set_item_disabled(file_menu->get_item_index(FILE_OPEN_PREV), false);
3112}
3113
3114VBoxContainer *EditorNode::get_main_screen_control() {
3115 return main_screen_vbox;
3116}
3117
3118void EditorNode::editor_select(int p_which) {
3119 static bool selecting = false;
3120 if (selecting || changing_scene) {
3121 return;
3122 }
3123
3124 ERR_FAIL_INDEX(p_which, editor_table.size());
3125
3126 if (!main_editor_buttons[p_which]->is_visible()) { // Button hidden, no editor.
3127 return;
3128 }
3129
3130 selecting = true;
3131
3132 for (int i = 0; i < main_editor_buttons.size(); i++) {
3133 main_editor_buttons[i]->set_pressed(i == p_which);
3134 }
3135
3136 selecting = false;
3137
3138 EditorPlugin *new_editor = editor_table[p_which];
3139 ERR_FAIL_NULL(new_editor);
3140
3141 if (editor_plugin_screen == new_editor) {
3142 return;
3143 }
3144
3145 if (editor_plugin_screen) {
3146 editor_plugin_screen->make_visible(false);
3147 }
3148
3149 editor_plugin_screen = new_editor;
3150 editor_plugin_screen->make_visible(true);
3151 editor_plugin_screen->selected_notify();
3152
3153 int plugin_count = editor_data.get_editor_plugin_count();
3154 for (int i = 0; i < plugin_count; i++) {
3155 editor_data.get_editor_plugin(i)->notify_main_screen_changed(editor_plugin_screen->get_name());
3156 }
3157
3158 if (EDITOR_GET("interface/editor/separate_distraction_mode")) {
3159 if (p_which == EDITOR_SCRIPT) {
3160 set_distraction_free_mode(script_distraction_free);
3161 } else {
3162 set_distraction_free_mode(scene_distraction_free);
3163 }
3164 }
3165}
3166
3167void EditorNode::select_editor_by_name(const String &p_name) {
3168 ERR_FAIL_COND(p_name.is_empty());
3169
3170 for (int i = 0; i < main_editor_buttons.size(); i++) {
3171 if (main_editor_buttons[i]->get_text() == p_name) {
3172 editor_select(i);
3173 return;
3174 }
3175 }
3176
3177 ERR_FAIL_MSG("The editor name '" + p_name + "' was not found.");
3178}
3179
3180void EditorNode::add_editor_plugin(EditorPlugin *p_editor, bool p_config_changed) {
3181 if (p_editor->has_main_screen()) {
3182 Button *tb = memnew(Button);
3183 tb->set_flat(true);
3184 tb->set_toggle_mode(true);
3185 tb->connect("pressed", callable_mp(singleton, &EditorNode::editor_select).bind(singleton->main_editor_buttons.size()));
3186 tb->set_name(p_editor->get_name());
3187 tb->set_text(p_editor->get_name());
3188
3189 Ref<Texture2D> icon = p_editor->get_icon();
3190 if (icon.is_valid()) {
3191 tb->set_icon(icon);
3192 // Make sure the control is updated if the icon is reimported.
3193 icon->connect_changed(callable_mp((Control *)tb, &Control::update_minimum_size));
3194 } else if (singleton->theme->has_icon(p_editor->get_name(), EditorStringName(EditorIcons))) {
3195 tb->set_icon(singleton->theme->get_icon(p_editor->get_name(), EditorStringName(EditorIcons)));
3196 }
3197
3198 tb->add_theme_font_override("font", singleton->theme->get_font(SNAME("main_button_font"), EditorStringName(EditorFonts)));
3199 tb->add_theme_font_size_override("font_size", singleton->theme->get_font_size(SNAME("main_button_font_size"), EditorStringName(EditorFonts)));
3200
3201 singleton->main_editor_buttons.push_back(tb);
3202 singleton->main_editor_button_hb->add_child(tb);
3203 singleton->editor_table.push_back(p_editor);
3204
3205 singleton->distraction_free->move_to_front();
3206 }
3207 singleton->editor_data.add_editor_plugin(p_editor);
3208 singleton->add_child(p_editor);
3209 if (p_config_changed) {
3210 p_editor->enable_plugin();
3211 }
3212}
3213
3214void EditorNode::remove_editor_plugin(EditorPlugin *p_editor, bool p_config_changed) {
3215 if (p_editor->has_main_screen()) {
3216 // Remove the main editor button and update the bindings of
3217 // all buttons behind it to point to the correct main window.
3218 for (int i = singleton->main_editor_buttons.size() - 1; i >= 0; i--) {
3219 if (p_editor->get_name() == singleton->main_editor_buttons[i]->get_text()) {
3220 if (singleton->main_editor_buttons[i]->is_pressed()) {
3221 singleton->editor_select(EDITOR_SCRIPT);
3222 }
3223
3224 memdelete(singleton->main_editor_buttons[i]);
3225 singleton->main_editor_buttons.remove_at(i);
3226
3227 break;
3228 } else {
3229 singleton->main_editor_buttons[i]->disconnect("pressed", callable_mp(singleton, &EditorNode::editor_select));
3230 singleton->main_editor_buttons[i]->connect("pressed", callable_mp(singleton, &EditorNode::editor_select).bind(i - 1));
3231 }
3232 }
3233
3234 singleton->editor_table.erase(p_editor);
3235 }
3236 p_editor->make_visible(false);
3237 p_editor->clear();
3238 if (p_config_changed) {
3239 p_editor->disable_plugin();
3240 }
3241 singleton->editor_plugins_over->remove_plugin(p_editor);
3242 singleton->editor_plugins_force_over->remove_plugin(p_editor);
3243 singleton->editor_plugins_force_input_forwarding->remove_plugin(p_editor);
3244 singleton->remove_child(p_editor);
3245 singleton->editor_data.remove_editor_plugin(p_editor);
3246
3247 for (KeyValue<ObjectID, HashSet<EditorPlugin *>> &kv : singleton->active_plugins) {
3248 kv.value.erase(p_editor);
3249 }
3250}
3251
3252void EditorNode::add_extension_editor_plugin(const StringName &p_class_name) {
3253 ERR_FAIL_COND_MSG(!ClassDB::class_exists(p_class_name), vformat("No such editor plugin registered: %s", p_class_name));
3254 ERR_FAIL_COND_MSG(!ClassDB::is_parent_class(p_class_name, SNAME("EditorPlugin")), vformat("Class is not an editor plugin: %s", p_class_name));
3255 ERR_FAIL_COND_MSG(singleton->editor_data.has_extension_editor_plugin(p_class_name), vformat("Editor plugin already added for class: %s", p_class_name));
3256
3257 EditorPlugin *plugin = Object::cast_to<EditorPlugin>(ClassDB::instantiate(p_class_name));
3258 singleton->editor_data.add_extension_editor_plugin(p_class_name, plugin);
3259 add_editor_plugin(plugin);
3260}
3261
3262void EditorNode::remove_extension_editor_plugin(const StringName &p_class_name) {
3263 // If we're exiting, the editor plugins will get cleaned up anyway, so don't do anything.
3264 if (singleton->exiting) {
3265 return;
3266 }
3267
3268 ERR_FAIL_COND_MSG(!singleton->editor_data.has_extension_editor_plugin(p_class_name), vformat("No editor plugin added for class: %s", p_class_name));
3269
3270 EditorPlugin *plugin = singleton->editor_data.get_extension_editor_plugin(p_class_name);
3271 remove_editor_plugin(plugin);
3272 memfree(plugin);
3273 singleton->editor_data.remove_extension_editor_plugin(p_class_name);
3274}
3275
3276void EditorNode::_update_addon_config() {
3277 if (_initializing_plugins) {
3278 return;
3279 }
3280
3281 Vector<String> enabled_addons;
3282
3283 for (const KeyValue<String, EditorPlugin *> &E : addon_name_to_plugin) {
3284 enabled_addons.push_back(E.key);
3285 }
3286
3287 if (enabled_addons.size() == 0) {
3288 ProjectSettings::get_singleton()->set("editor_plugins/enabled", Variant());
3289 } else {
3290 enabled_addons.sort();
3291 ProjectSettings::get_singleton()->set("editor_plugins/enabled", enabled_addons);
3292 }
3293
3294 project_settings_editor->queue_save();
3295}
3296
3297void EditorNode::set_addon_plugin_enabled(const String &p_addon, bool p_enabled, bool p_config_changed) {
3298 String addon_path = p_addon;
3299
3300 if (!addon_path.begins_with("res://")) {
3301 addon_path = "res://addons/" + addon_path + "/plugin.cfg";
3302 }
3303
3304 ERR_FAIL_COND(p_enabled && addon_name_to_plugin.has(addon_path));
3305 ERR_FAIL_COND(!p_enabled && !addon_name_to_plugin.has(addon_path));
3306
3307 if (!p_enabled) {
3308 EditorPlugin *addon = addon_name_to_plugin[addon_path];
3309 remove_editor_plugin(addon, p_config_changed);
3310 memdelete(addon);
3311 addon_name_to_plugin.erase(addon_path);
3312 _update_addon_config();
3313 return;
3314 }
3315
3316 Ref<ConfigFile> cf;
3317 cf.instantiate();
3318 if (!DirAccess::exists(addon_path.get_base_dir())) {
3319 _remove_plugin_from_enabled(addon_path);
3320 WARN_PRINT("Addon '" + addon_path + "' failed to load. No directory found. Removing from enabled plugins.");
3321 return;
3322 }
3323 Error err = cf->load(addon_path);
3324 if (err != OK) {
3325 show_warning(vformat(TTR("Unable to enable addon plugin at: '%s' parsing of config failed."), addon_path));
3326 return;
3327 }
3328
3329 String plugin_version;
3330 if (cf->has_section_key("plugin", "version")) {
3331 plugin_version = cf->get_value("plugin", "version");
3332 }
3333
3334 if (!cf->has_section_key("plugin", "script")) {
3335 show_warning(vformat(TTR("Unable to find script field for addon plugin at: '%s'."), addon_path));
3336 return;
3337 }
3338
3339 String script_path = cf->get_value("plugin", "script");
3340 Ref<Script> scr; // We need to save it for creating "ep" below.
3341
3342 // Only try to load the script if it has a name. Else, the plugin has no init script.
3343 if (script_path.length() > 0) {
3344 script_path = addon_path.get_base_dir().path_join(script_path);
3345 scr = ResourceLoader::load(script_path, "Script", ResourceFormatLoader::CACHE_MODE_IGNORE);
3346
3347 if (scr.is_null()) {
3348 show_warning(vformat(TTR("Unable to load addon script from path: '%s'."), script_path));
3349 return;
3350 }
3351
3352 // Errors in the script cause the base_type to be an empty StringName.
3353 if (scr->get_instance_base_type() == StringName()) {
3354 if (_initializing_plugins) {
3355 // However, if it happens during initialization, waiting for file scan might help.
3356 pending_addons.push_back(p_addon);
3357 return;
3358 }
3359
3360 show_warning(vformat(TTR("Unable to load addon script from path: '%s'. This might be due to a code error in that script.\nDisabling the addon at '%s' to prevent further errors."), script_path, addon_path));
3361 _remove_plugin_from_enabled(addon_path);
3362 return;
3363 }
3364
3365 // Plugin init scripts must inherit from EditorPlugin and be tools.
3366 if (String(scr->get_instance_base_type()) != "EditorPlugin") {
3367 show_warning(vformat(TTR("Unable to load addon script from path: '%s' Base type is not EditorPlugin."), script_path));
3368 return;
3369 }
3370
3371 if (!scr->is_tool()) {
3372 show_warning(vformat(TTR("Unable to load addon script from path: '%s' Script is not in tool mode."), script_path));
3373 return;
3374 }
3375 }
3376
3377 EditorPlugin *ep = memnew(EditorPlugin);
3378 ep->set_script(scr);
3379 ep->set_plugin_version(plugin_version);
3380 addon_name_to_plugin[addon_path] = ep;
3381 add_editor_plugin(ep, p_config_changed);
3382
3383 _update_addon_config();
3384}
3385
3386bool EditorNode::is_addon_plugin_enabled(const String &p_addon) const {
3387 if (p_addon.begins_with("res://")) {
3388 return addon_name_to_plugin.has(p_addon);
3389 }
3390
3391 return addon_name_to_plugin.has("res://addons/" + p_addon + "/plugin.cfg");
3392}
3393
3394void EditorNode::_remove_edited_scene(bool p_change_tab) {
3395 // When scene gets closed no node is edited anymore, so make sure the editors are notified before nodes are freed.
3396 hide_unused_editors(SceneTreeDock::get_singleton());
3397
3398 int new_index = editor_data.get_edited_scene();
3399 int old_index = new_index;
3400
3401 if (new_index > 0) {
3402 new_index = new_index - 1;
3403 } else if (editor_data.get_edited_scene_count() > 1) {
3404 new_index = 1;
3405 } else {
3406 editor_data.add_edited_scene(-1);
3407 new_index = 1;
3408 }
3409
3410 if (p_change_tab) {
3411 _set_current_scene(new_index);
3412 }
3413 editor_data.remove_scene(old_index);
3414 _update_title();
3415 scene_tabs->update_scene_tabs();
3416}
3417
3418void EditorNode::_remove_scene(int index, bool p_change_tab) {
3419 // Clear icon cache in case some scripts are no longer needed.
3420 // FIXME: Ideally the cache should never be cleared and only updated on per-script basis, when an icon changes.
3421 editor_data.clear_script_icon_cache();
3422
3423 if (editor_data.get_edited_scene() == index) {
3424 // Scene to remove is current scene.
3425 _remove_edited_scene(p_change_tab);
3426 } else {
3427 // Scene to remove is not active scene.
3428 editor_data.remove_scene(index);
3429 }
3430}
3431
3432void EditorNode::set_edited_scene(Node *p_scene) {
3433 Node *old_edited_scene_root = get_editor_data().get_edited_scene_root();
3434 if (old_edited_scene_root) {
3435 if (old_edited_scene_root->get_parent() == scene_root) {
3436 scene_root->remove_child(old_edited_scene_root);
3437 }
3438 old_edited_scene_root->disconnect(SNAME("replacing_by"), callable_mp(this, &EditorNode::set_edited_scene));
3439 }
3440 get_editor_data().set_edited_scene_root(p_scene);
3441
3442 if (Object::cast_to<Popup>(p_scene)) {
3443 Object::cast_to<Popup>(p_scene)->show();
3444 }
3445 SceneTreeDock::get_singleton()->set_edited_scene(p_scene);
3446 if (get_tree()) {
3447 get_tree()->set_edited_scene_root(p_scene);
3448 }
3449
3450 if (p_scene) {
3451 if (p_scene->get_parent() != scene_root) {
3452 scene_root->add_child(p_scene, true);
3453 }
3454 p_scene->connect(SNAME("replacing_by"), callable_mp(this, &EditorNode::set_edited_scene));
3455 }
3456}
3457
3458int EditorNode::_get_current_main_editor() {
3459 for (int i = 0; i < editor_table.size(); i++) {
3460 if (editor_table[i] == editor_plugin_screen) {
3461 return i;
3462 }
3463 }
3464
3465 return 0;
3466}
3467
3468Dictionary EditorNode::_get_main_scene_state() {
3469 Dictionary state;
3470 state["main_tab"] = _get_current_main_editor();
3471 state["scene_tree_offset"] = SceneTreeDock::get_singleton()->get_tree_editor()->get_scene_tree()->get_vscroll_bar()->get_value();
3472 state["property_edit_offset"] = InspectorDock::get_inspector_singleton()->get_scroll_offset();
3473 state["node_filter"] = SceneTreeDock::get_singleton()->get_filter();
3474 return state;
3475}
3476
3477void EditorNode::_set_main_scene_state(Dictionary p_state, Node *p_for_scene) {
3478 if (get_edited_scene() != p_for_scene && p_for_scene != nullptr) {
3479 return; // Not for this scene.
3480 }
3481
3482 changing_scene = false;
3483
3484 int current_tab = -1;
3485 for (int i = 0; i < editor_table.size(); i++) {
3486 if (editor_plugin_screen == editor_table[i]) {
3487 current_tab = i;
3488 break;
3489 }
3490 }
3491
3492 if (p_state.has("editor_index")) {
3493 int index = p_state["editor_index"];
3494 if (current_tab < 2) { // If currently in spatial/2d, only switch to spatial/2d. If currently in script, stay there.
3495 if (index < 2 || !get_edited_scene()) {
3496 editor_select(index);
3497 }
3498 }
3499 }
3500
3501 if (get_edited_scene()) {
3502 if (current_tab < 2) {
3503 Node *editor_node = SceneTreeDock::get_singleton()->get_tree_editor()->get_selected();
3504 editor_node = editor_node == nullptr ? get_edited_scene() : editor_node;
3505
3506 if (Object::cast_to<CanvasItem>(editor_node)) {
3507 editor_select(EDITOR_2D);
3508 } else if (Object::cast_to<Node3D>(editor_node)) {
3509 editor_select(EDITOR_3D);
3510 }
3511 }
3512 }
3513
3514 if (p_state.has("scene_tree_offset")) {
3515 SceneTreeDock::get_singleton()->get_tree_editor()->get_scene_tree()->get_vscroll_bar()->set_value(p_state["scene_tree_offset"]);
3516 }
3517 if (p_state.has("property_edit_offset")) {
3518 InspectorDock::get_inspector_singleton()->set_scroll_offset(p_state["property_edit_offset"]);
3519 }
3520
3521 if (p_state.has("node_filter")) {
3522 SceneTreeDock::get_singleton()->set_filter(p_state["node_filter"]);
3523 }
3524
3525 // This should only happen at the very end.
3526
3527 EditorDebuggerNode::get_singleton()->update_live_edit_root();
3528 ScriptEditor::get_singleton()->set_scene_root_script(editor_data.get_scene_root_script(editor_data.get_edited_scene()));
3529 editor_data.notify_edited_scene_changed();
3530 emit_signal(SNAME("scene_changed"));
3531}
3532
3533bool EditorNode::is_changing_scene() const {
3534 return changing_scene;
3535}
3536
3537void EditorNode::_set_current_scene(int p_idx) {
3538 if (p_idx == editor_data.get_edited_scene()) {
3539 return; // Pointless.
3540 }
3541
3542 _set_current_scene_nocheck(p_idx);
3543}
3544
3545void EditorNode::_set_current_scene_nocheck(int p_idx) {
3546 // Save the folding in case the scene gets reloaded.
3547 if (editor_data.get_scene_path(p_idx) != "" && editor_data.get_edited_scene_root(p_idx)) {
3548 editor_folding.save_scene_folding(editor_data.get_edited_scene_root(p_idx), editor_data.get_scene_path(p_idx));
3549 }
3550
3551 if (editor_data.check_and_update_scene(p_idx)) {
3552 if (editor_data.get_scene_path(p_idx) != "") {
3553 editor_folding.load_scene_folding(editor_data.get_edited_scene_root(p_idx), editor_data.get_scene_path(p_idx));
3554 }
3555
3556 EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_scene_history_id(p_idx));
3557 }
3558
3559 changing_scene = true;
3560 editor_data.save_edited_scene_state(editor_selection, &editor_history, _get_main_scene_state());
3561
3562 if (get_editor_data().get_edited_scene_root()) {
3563 if (get_editor_data().get_edited_scene_root()->get_parent() == scene_root) {
3564 scene_root->remove_child(get_editor_data().get_edited_scene_root());
3565 }
3566 }
3567
3568 editor_selection->clear();
3569 editor_data.set_edited_scene(p_idx);
3570
3571 Node *new_scene = editor_data.get_edited_scene_root();
3572
3573 if (Popup *p = Object::cast_to<Popup>(new_scene)) {
3574 p->show();
3575 }
3576
3577 SceneTreeDock::get_singleton()->set_edited_scene(new_scene);
3578 if (get_tree()) {
3579 get_tree()->set_edited_scene_root(new_scene);
3580 }
3581
3582 if (new_scene) {
3583 if (new_scene->get_parent() != scene_root) {
3584 scene_root->add_child(new_scene, true);
3585 }
3586 }
3587
3588 Dictionary state = editor_data.restore_edited_scene_state(editor_selection, &editor_history);
3589 _edit_current(true);
3590
3591 _update_title();
3592 scene_tabs->update_scene_tabs();
3593
3594 if (tabs_to_close.is_empty()) {
3595 call_deferred(SNAME("_set_main_scene_state"), state, get_edited_scene()); // Do after everything else is done setting up.
3596 }
3597}
3598
3599void EditorNode::setup_color_picker(ColorPicker *p_picker) {
3600 p_picker->set_editor_settings(EditorSettings::get_singleton());
3601 int default_color_mode = EDITOR_GET("interface/inspector/default_color_picker_mode");
3602 int picker_shape = EDITOR_GET("interface/inspector/default_color_picker_shape");
3603 p_picker->set_color_mode((ColorPicker::ColorModeType)default_color_mode);
3604 p_picker->set_picker_shape((ColorPicker::PickerShapeType)picker_shape);
3605}
3606
3607bool EditorNode::is_scene_open(const String &p_path) {
3608 for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
3609 if (editor_data.get_scene_path(i) == p_path) {
3610 return true;
3611 }
3612 }
3613
3614 return false;
3615}
3616
3617void EditorNode::fix_dependencies(const String &p_for_file) {
3618 dependency_fixer->edit(p_for_file);
3619}
3620
3621int EditorNode::new_scene() {
3622 int idx = editor_data.add_edited_scene(-1);
3623 // Remove placeholder empty scene.
3624 if (editor_data.get_edited_scene_count() > 1) {
3625 for (int i = 0; i < editor_data.get_edited_scene_count() - 1; i++) {
3626 bool unsaved = EditorUndoRedoManager::get_singleton()->is_history_unsaved(editor_data.get_scene_history_id(i));
3627 if (!unsaved && editor_data.get_scene_path(i).is_empty() && editor_data.get_edited_scene_root(i) == nullptr) {
3628 editor_data.remove_scene(i);
3629 idx--;
3630 }
3631 }
3632 }
3633 idx = MAX(idx, 0);
3634
3635 _set_current_scene(idx);
3636 editor_data.clear_editor_states();
3637 scene_tabs->update_scene_tabs();
3638 return idx;
3639}
3640
3641Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, bool p_set_inherited, bool p_clear_errors, bool p_force_open_imported, bool p_silent_change_tab) {
3642 if (!is_inside_tree()) {
3643 defer_load_scene = p_scene;
3644 return OK;
3645 }
3646
3647 if (!p_set_inherited) {
3648 for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
3649 if (editor_data.get_scene_path(i) == p_scene) {
3650 _set_current_scene(i);
3651 return OK;
3652 }
3653 }
3654
3655 if (!p_force_open_imported && FileAccess::exists(p_scene + ".import")) {
3656 open_imported->set_text(vformat(TTR("Scene '%s' was automatically imported, so it can't be modified.\nTo make changes to it, a new inherited scene can be created."), p_scene.get_file()));
3657 open_imported->popup_centered();
3658 new_inherited_button->grab_focus();
3659 open_import_request = p_scene;
3660 return OK;
3661 }
3662 }
3663
3664 if (p_clear_errors) {
3665 load_errors->clear();
3666 }
3667
3668 String lpath = ProjectSettings::get_singleton()->localize_path(p_scene);
3669
3670 if (!lpath.begins_with("res://")) {
3671 show_accept(TTR("Error loading scene, it must be inside the project path. Use 'Import' to open the scene, then save it inside the project path."), TTR("OK"));
3672 opening_prev = false;
3673 return ERR_FILE_NOT_FOUND;
3674 }
3675
3676 int prev = editor_data.get_edited_scene();
3677 int idx = editor_data.add_edited_scene(-1);
3678
3679 if (!editor_data.get_edited_scene_root() && editor_data.get_edited_scene_count() == 2) {
3680 _remove_edited_scene();
3681 } else if (p_silent_change_tab) {
3682 _set_current_scene_nocheck(idx);
3683 } else {
3684 _set_current_scene(idx);
3685 }
3686
3687 dependency_errors.clear();
3688
3689 Error err;
3690 Ref<PackedScene> sdata = ResourceLoader::load(lpath, "", ResourceFormatLoader::CACHE_MODE_REPLACE, &err);
3691 if (!sdata.is_valid()) {
3692 _dialog_display_load_error(lpath, err);
3693 opening_prev = false;
3694
3695 if (prev != -1) {
3696 _set_current_scene(prev);
3697 editor_data.remove_scene(idx);
3698 }
3699 return ERR_FILE_NOT_FOUND;
3700 }
3701
3702 if (!p_ignore_broken_deps && dependency_errors.has(lpath)) {
3703 current_menu_option = -1;
3704 Vector<String> errors;
3705 for (const String &E : dependency_errors[lpath]) {
3706 errors.push_back(E);
3707 }
3708 dependency_error->show(DependencyErrorDialog::MODE_SCENE, lpath, errors);
3709 opening_prev = false;
3710
3711 if (prev != -1) {
3712 _set_current_scene(prev);
3713 editor_data.remove_scene(idx);
3714 }
3715 return ERR_FILE_MISSING_DEPENDENCIES;
3716 }
3717
3718 dependency_errors.erase(lpath); // At least not self path.
3719
3720 for (KeyValue<String, HashSet<String>> &E : dependency_errors) {
3721 String txt = vformat(TTR("Scene '%s' has broken dependencies:"), E.key) + "\n";
3722 for (const String &F : E.value) {
3723 txt += "\t" + F + "\n";
3724 }
3725 add_io_error(txt);
3726 }
3727
3728 if (ResourceCache::has(lpath)) {
3729 // Used from somewhere else? No problem! Update state and replace sdata.
3730 Ref<PackedScene> ps = ResourceCache::get_ref(lpath);
3731 if (ps.is_valid()) {
3732 ps->replace_state(sdata->get_state());
3733 ps->set_last_modified_time(sdata->get_last_modified_time());
3734 sdata = ps;
3735 }
3736
3737 } else {
3738 sdata->set_path(lpath, true); // Take over path.
3739 }
3740
3741 Node *new_scene = sdata->instantiate(p_set_inherited ? PackedScene::GEN_EDIT_STATE_MAIN_INHERITED : PackedScene::GEN_EDIT_STATE_MAIN);
3742
3743 if (!new_scene) {
3744 sdata.unref();
3745 _dialog_display_load_error(lpath, ERR_FILE_CORRUPT);
3746 opening_prev = false;
3747 if (prev != -1) {
3748 _set_current_scene(prev);
3749 editor_data.remove_scene(idx);
3750 }
3751 return ERR_FILE_CORRUPT;
3752 }
3753
3754 if (p_set_inherited) {
3755 Ref<SceneState> state = sdata->get_state();
3756 state->set_path(lpath);
3757 new_scene->set_scene_inherited_state(state);
3758 new_scene->set_scene_file_path(String());
3759 }
3760
3761 new_scene->set_scene_instance_state(Ref<SceneState>());
3762
3763 set_edited_scene(new_scene);
3764
3765 String config_file_path = EditorPaths::get_singleton()->get_project_settings_dir().path_join(p_scene.get_file() + "-editstate-" + p_scene.md5_text() + ".cfg");
3766 Ref<ConfigFile> editor_state_cf;
3767 editor_state_cf.instantiate();
3768 Error editor_state_cf_err = editor_state_cf->load(config_file_path);
3769 if (editor_state_cf_err == OK || editor_state_cf->has_section("editor_states")) {
3770 _load_editor_plugin_states_from_config(editor_state_cf);
3771 }
3772
3773 _update_title();
3774 scene_tabs->update_scene_tabs();
3775 _add_to_recent_scenes(lpath);
3776
3777 if (editor_folding.has_folding_data(lpath)) {
3778 editor_folding.load_scene_folding(new_scene, lpath);
3779 } else if (EDITOR_GET("interface/inspector/auto_unfold_foreign_scenes")) {
3780 editor_folding.unfold_scene(new_scene);
3781 editor_folding.save_scene_folding(new_scene, lpath);
3782 }
3783
3784 opening_prev = false;
3785
3786 EditorDebuggerNode::get_singleton()->update_live_edit_root();
3787
3788 // Tell everything to edit this object, unless we're in the process of restoring scenes.
3789 // If we are, we'll edit it after the restoration is done.
3790 if (!restoring_scenes) {
3791 push_item(new_scene);
3792 }
3793
3794 // Load the selected nodes.
3795 if (editor_state_cf->has_section_key("editor_states", "selected_nodes")) {
3796 TypedArray<NodePath> selected_node_list = editor_state_cf->get_value("editor_states", "selected_nodes", TypedArray<String>());
3797
3798 for (int i = 0; i < selected_node_list.size(); i++) {
3799 Node *selected_node = new_scene->get_node_or_null(selected_node_list[i]);
3800 if (selected_node) {
3801 editor_selection->add_node(selected_node);
3802 }
3803 }
3804 }
3805
3806 if (!restoring_scenes) {
3807 save_editor_layout_delayed();
3808 }
3809
3810 return OK;
3811}
3812
3813HashMap<StringName, Variant> EditorNode::get_modified_properties_for_node(Node *p_node) {
3814 HashMap<StringName, Variant> modified_property_map;
3815
3816 List<PropertyInfo> pinfo;
3817 p_node->get_property_list(&pinfo);
3818 for (const PropertyInfo &E : pinfo) {
3819 if (E.usage & PROPERTY_USAGE_STORAGE) {
3820 bool is_valid_revert = false;
3821 Variant revert_value = EditorPropertyRevert::get_property_revert_value(p_node, E.name, &is_valid_revert);
3822 Variant current_value = p_node->get(E.name);
3823 if (is_valid_revert) {
3824 if (PropertyUtils::is_property_value_different(current_value, revert_value)) {
3825 modified_property_map[E.name] = current_value;
3826 }
3827 }
3828 }
3829 }
3830
3831 return modified_property_map;
3832}
3833
3834void EditorNode::update_ownership_table_for_addition_node_ancestors(Node *p_current_node, HashMap<Node *, Node *> &p_ownership_table) {
3835 p_ownership_table.insert(p_current_node, p_current_node->get_owner());
3836
3837 for (int i = 0; i < p_current_node->get_child_count(); i++) {
3838 Node *child = p_current_node->get_child(i);
3839 update_ownership_table_for_addition_node_ancestors(child, p_ownership_table);
3840 }
3841}
3842
3843void EditorNode::update_diff_data_for_node(
3844 Node *p_edited_scene,
3845 Node *p_root,
3846 Node *p_node,
3847 HashMap<NodePath, ModificationNodeEntry> &p_modification_table,
3848 List<AdditiveNodeEntry> &p_addition_list) {
3849 bool node_part_of_subscene = p_node != p_edited_scene &&
3850 p_edited_scene->get_scene_inherited_state().is_valid() &&
3851 p_edited_scene->get_scene_inherited_state()->find_node_by_path(p_edited_scene->get_path_to(p_node)) >= 0;
3852
3853 // Loop through the owners until either we reach the root node or nullptr
3854 Node *valid_node_owner = p_node->get_owner();
3855 while (valid_node_owner) {
3856 if (valid_node_owner == p_root) {
3857 break;
3858 }
3859 valid_node_owner = valid_node_owner->get_owner();
3860 }
3861
3862 if ((valid_node_owner == p_root && (p_root != p_edited_scene || !p_edited_scene->get_scene_file_path().is_empty())) || node_part_of_subscene || p_node == p_root) {
3863 HashMap<StringName, Variant> modified_properties = get_modified_properties_for_node(p_node);
3864
3865 // Find all valid connections to other nodes.
3866 List<Connection> connections_to;
3867 p_node->get_all_signal_connections(&connections_to);
3868
3869 List<ConnectionWithNodePath> valid_connections_to;
3870 for (const Connection &c : connections_to) {
3871 Node *connection_target_node = Object::cast_to<Node>(c.callable.get_object());
3872 if (connection_target_node) {
3873 // TODO: add support for reinstating custom callables
3874 if (!c.callable.is_custom()) {
3875 ConnectionWithNodePath connection_to;
3876 connection_to.connection = c;
3877 connection_to.node_path = p_node->get_path_to(connection_target_node);
3878 valid_connections_to.push_back(connection_to);
3879 }
3880 }
3881 }
3882
3883 // Find all valid connections from other nodes.
3884 List<Connection> connections_from;
3885 p_node->get_signals_connected_to_this(&connections_from);
3886
3887 List<Connection> valid_connections_from;
3888 for (const Connection &c : connections_from) {
3889 Node *source_node = Object::cast_to<Node>(c.signal.get_object());
3890
3891 Node *valid_source_owner = nullptr;
3892 if (source_node) {
3893 valid_source_owner = source_node->get_owner();
3894 while (valid_source_owner) {
3895 if (valid_source_owner == p_root) {
3896 break;
3897 }
3898 valid_source_owner = valid_source_owner->get_owner();
3899 }
3900 }
3901
3902 if (!source_node || valid_source_owner == nullptr) {
3903 // TODO: add support for reinstating custom callables
3904 if (!c.callable.is_custom()) {
3905 valid_connections_from.push_back(c);
3906 }
3907 }
3908 }
3909
3910 // Find all node groups.
3911 List<Node::GroupInfo> groups;
3912 p_node->get_groups(&groups);
3913
3914 if (!modified_properties.is_empty() || !valid_connections_to.is_empty() || !valid_connections_from.is_empty() || !groups.is_empty()) {
3915 ModificationNodeEntry modification_node_entry;
3916 modification_node_entry.property_table = modified_properties;
3917 modification_node_entry.connections_to = valid_connections_to;
3918 modification_node_entry.connections_from = valid_connections_from;
3919 modification_node_entry.groups = groups;
3920
3921 p_modification_table[p_root->get_path_to(p_node)] = modification_node_entry;
3922 }
3923 } else {
3924 AdditiveNodeEntry new_additive_node_entry;
3925 new_additive_node_entry.node = p_node;
3926 new_additive_node_entry.parent = p_root->get_path_to(p_node->get_parent());
3927 new_additive_node_entry.owner = p_node->get_owner();
3928 new_additive_node_entry.index = p_node->get_index();
3929
3930 Node2D *node_2d = Object::cast_to<Node2D>(p_node);
3931 if (node_2d) {
3932 new_additive_node_entry.transform_2d = node_2d->get_relative_transform_to_parent(node_2d->get_parent());
3933 }
3934 Node3D *node_3d = Object::cast_to<Node3D>(p_node);
3935 if (node_3d) {
3936 new_additive_node_entry.transform_3d = node_3d->get_relative_transform(node_3d->get_parent());
3937 }
3938
3939 // Gathers the ownership of all ancestor nodes for later use.
3940 HashMap<Node *, Node *> ownership_table;
3941 for (int i = 0; i < p_node->get_child_count(); i++) {
3942 Node *child = p_node->get_child(i);
3943 update_ownership_table_for_addition_node_ancestors(child, ownership_table);
3944 }
3945
3946 new_additive_node_entry.ownership_table = ownership_table;
3947
3948 p_addition_list.push_back(new_additive_node_entry);
3949
3950 return;
3951 }
3952
3953 for (int i = 0; i < p_node->get_child_count(); i++) {
3954 Node *child = p_node->get_child(i);
3955 update_diff_data_for_node(p_edited_scene, p_root, child, p_modification_table, p_addition_list);
3956 }
3957}
3958//
3959
3960void EditorNode::open_request(const String &p_path) {
3961 if (!opening_prev) {
3962 List<String>::Element *prev_scene_item = previous_scenes.find(p_path);
3963 if (prev_scene_item != nullptr) {
3964 prev_scene_item->erase();
3965 }
3966 }
3967
3968 load_scene(p_path); // As it will be opened in separate tab.
3969}
3970
3971bool EditorNode::has_previous_scenes() const {
3972 return !previous_scenes.is_empty();
3973}
3974
3975void EditorNode::edit_foreign_resource(Ref<Resource> p_resource) {
3976 load_scene(p_resource->get_path().get_slice("::", 0));
3977 InspectorDock::get_singleton()->call_deferred("edit_resource", p_resource);
3978}
3979
3980bool EditorNode::is_resource_read_only(Ref<Resource> p_resource, bool p_foreign_resources_are_writable) {
3981 ERR_FAIL_COND_V(p_resource.is_null(), false);
3982
3983 String path = p_resource->get_path();
3984 if (!path.is_resource_file()) {
3985 // If the resource name contains '::', that means it is a subresource embedded in another resource.
3986 int srpos = path.find("::");
3987 if (srpos != -1) {
3988 String base = path.substr(0, srpos);
3989 // If the base resource is a packed scene, we treat it as read-only if it is not the currently edited scene.
3990 if (ResourceLoader::get_resource_type(base) == "PackedScene") {
3991 if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
3992 // If we have not flagged foreign resources as writable or the base scene the resource is
3993 // part was imported, it can be considered read-only.
3994 if (!p_foreign_resources_are_writable || FileAccess::exists(base + ".import")) {
3995 return true;
3996 }
3997 }
3998 } else {
3999 // If a corresponding .import file exists for the base file, we assume it to be imported and should therefore treated as read-only.
4000 if (FileAccess::exists(base + ".import")) {
4001 return true;
4002 }
4003 }
4004 }
4005 } else {
4006 // The resource is not a subresource, but if it has an .import file, it's imported so treat it as read only.
4007 if (FileAccess::exists(path + ".import")) {
4008 return true;
4009 }
4010 }
4011
4012 return false;
4013}
4014
4015void EditorNode::request_instantiate_scene(const String &p_path) {
4016 SceneTreeDock::get_singleton()->instantiate(p_path);
4017}
4018
4019void EditorNode::request_instantiate_scenes(const Vector<String> &p_files) {
4020 SceneTreeDock::get_singleton()->instantiate_scenes(p_files);
4021}
4022
4023void EditorNode::_inherit_request(String p_file) {
4024 current_menu_option = FILE_NEW_INHERITED_SCENE;
4025 _dialog_action(p_file);
4026}
4027
4028void EditorNode::_instantiate_request(const Vector<String> &p_files) {
4029 request_instantiate_scenes(p_files);
4030}
4031
4032void EditorNode::_close_messages() {
4033 old_split_ofs = center_split->get_split_offset();
4034 center_split->set_split_offset(0);
4035}
4036
4037void EditorNode::_show_messages() {
4038 center_split->set_split_offset(old_split_ofs);
4039}
4040
4041void EditorNode::_add_to_recent_scenes(const String &p_scene) {
4042 Array rc = EditorSettings::get_singleton()->get_project_metadata("recent_files", "scenes", Array());
4043 if (rc.has(p_scene)) {
4044 rc.erase(p_scene);
4045 }
4046 rc.push_front(p_scene);
4047 if (rc.size() > 10) {
4048 rc.resize(10);
4049 }
4050
4051 EditorSettings::get_singleton()->set_project_metadata("recent_files", "scenes", rc);
4052 _update_recent_scenes();
4053}
4054
4055void EditorNode::_open_recent_scene(int p_idx) {
4056 if (p_idx == recent_scenes->get_item_count() - 1) {
4057 EditorSettings::get_singleton()->set_project_metadata("recent_files", "scenes", Array());
4058 call_deferred(SNAME("_update_recent_scenes"));
4059 } else {
4060 Array rc = EditorSettings::get_singleton()->get_project_metadata("recent_files", "scenes", Array());
4061 ERR_FAIL_INDEX(p_idx, rc.size());
4062
4063 if (load_scene(rc[p_idx]) != OK) {
4064 rc.remove_at(p_idx);
4065 EditorSettings::get_singleton()->set_project_metadata("recent_files", "scenes", rc);
4066 _update_recent_scenes();
4067 }
4068 }
4069}
4070
4071void EditorNode::_update_recent_scenes() {
4072 Array rc = EditorSettings::get_singleton()->get_project_metadata("recent_files", "scenes", Array());
4073 recent_scenes->clear();
4074
4075 String path;
4076 for (int i = 0; i < rc.size(); i++) {
4077 path = rc[i];
4078 recent_scenes->add_item(path.replace("res://", ""), i);
4079 }
4080
4081 recent_scenes->add_separator();
4082 recent_scenes->add_shortcut(ED_SHORTCUT("editor/clear_recent", TTR("Clear Recent Scenes")));
4083 recent_scenes->reset_size();
4084}
4085
4086void EditorNode::_quick_opened() {
4087 Vector<String> files = quick_open->get_selected_files();
4088
4089 bool open_scene_dialog = quick_open->get_base_type() == "PackedScene";
4090 for (int i = 0; i < files.size(); i++) {
4091 String res_path = files[i];
4092 if (open_scene_dialog || ClassDB::is_parent_class(ResourceLoader::get_resource_type(res_path), "PackedScene")) {
4093 open_request(res_path);
4094 } else {
4095 load_resource(res_path);
4096 }
4097 }
4098}
4099
4100void EditorNode::_project_run_started() {
4101 if (bool(EDITOR_GET("run/output/always_clear_output_on_play"))) {
4102 log->clear();
4103 }
4104
4105 if (bool(EDITOR_GET("run/output/always_open_output_on_play"))) {
4106 make_bottom_panel_item_visible(log);
4107 }
4108}
4109
4110void EditorNode::_project_run_stopped() {
4111 if (!bool(EDITOR_GET("run/output/always_close_output_on_stop"))) {
4112 return;
4113 }
4114
4115 for (int i = 0; i < bottom_panel_items.size(); i++) {
4116 if (bottom_panel_items[i].control == log) {
4117 _bottom_panel_switch(false, i);
4118 break;
4119 }
4120 }
4121}
4122
4123void EditorNode::notify_all_debug_sessions_exited() {
4124 project_run_bar->stop_playing();
4125}
4126
4127void EditorNode::add_io_error(const String &p_error) {
4128 DEV_ASSERT(Thread::get_caller_id() == Thread::get_main_id());
4129 singleton->load_errors->add_image(singleton->theme->get_icon(SNAME("Error"), EditorStringName(EditorIcons)));
4130 singleton->load_errors->add_text(p_error + "\n");
4131 EditorInterface::get_singleton()->popup_dialog_centered_ratio(singleton->load_error_dialog, 0.5);
4132}
4133
4134void EditorNode::add_io_warning(const String &p_warning) {
4135 DEV_ASSERT(Thread::get_caller_id() == Thread::get_main_id());
4136 singleton->load_errors->add_image(singleton->theme->get_icon(SNAME("Warning"), EditorStringName(EditorIcons)));
4137 singleton->load_errors->add_text(p_warning + "\n");
4138 EditorInterface::get_singleton()->popup_dialog_centered_ratio(singleton->load_error_dialog, 0.5);
4139}
4140
4141bool EditorNode::_find_scene_in_use(Node *p_node, const String &p_path) const {
4142 if (p_node->get_scene_file_path() == p_path) {
4143 return true;
4144 }
4145
4146 for (int i = 0; i < p_node->get_child_count(); i++) {
4147 if (_find_scene_in_use(p_node->get_child(i), p_path)) {
4148 return true;
4149 }
4150 }
4151
4152 return false;
4153}
4154
4155bool EditorNode::is_scene_in_use(const String &p_path) {
4156 Node *es = get_edited_scene();
4157 if (es) {
4158 return _find_scene_in_use(es, p_path);
4159 }
4160 return false;
4161}
4162
4163OS::ProcessID EditorNode::has_child_process(OS::ProcessID p_pid) const {
4164 return project_run_bar->has_child_process(p_pid);
4165}
4166
4167void EditorNode::stop_child_process(OS::ProcessID p_pid) {
4168 project_run_bar->stop_child_process(p_pid);
4169}
4170
4171Ref<Script> EditorNode::get_object_custom_type_base(const Object *p_object) const {
4172 ERR_FAIL_NULL_V(p_object, nullptr);
4173
4174 Ref<Script> scr = p_object->get_script();
4175
4176 if (scr.is_valid()) {
4177 // Uncommenting would break things! Consider adding a parameter if you need it.
4178 // StringName name = EditorNode::get_editor_data().script_class_get_name(base_script->get_path());
4179 // if (name != StringName()) {
4180 // return name;
4181 // }
4182
4183 // TODO: Should probably be deprecated in 4.x
4184 StringName base = scr->get_instance_base_type();
4185 if (base != StringName() && EditorNode::get_editor_data().get_custom_types().has(base)) {
4186 const Vector<EditorData::CustomType> &types = EditorNode::get_editor_data().get_custom_types()[base];
4187
4188 Ref<Script> base_scr = scr;
4189 while (base_scr.is_valid()) {
4190 for (int i = 0; i < types.size(); ++i) {
4191 if (types[i].script == base_scr) {
4192 return types[i].script;
4193 }
4194 }
4195 base_scr = base_scr->get_base_script();
4196 }
4197 }
4198 }
4199
4200 return nullptr;
4201}
4202
4203StringName EditorNode::get_object_custom_type_name(const Object *p_object) const {
4204 ERR_FAIL_NULL_V(p_object, StringName());
4205
4206 Ref<Script> scr = p_object->get_script();
4207 if (scr.is_null() && Object::cast_to<Script>(p_object)) {
4208 scr = p_object;
4209 }
4210
4211 if (scr.is_valid()) {
4212 Ref<Script> base_scr = scr;
4213 while (base_scr.is_valid()) {
4214 StringName name = EditorNode::get_editor_data().script_class_get_name(base_scr->get_path());
4215 if (name != StringName()) {
4216 return name;
4217 }
4218
4219 // TODO: Should probably be deprecated in 4.x.
4220 StringName base = base_scr->get_instance_base_type();
4221 if (base != StringName() && EditorNode::get_editor_data().get_custom_types().has(base)) {
4222 const Vector<EditorData::CustomType> &types = EditorNode::get_editor_data().get_custom_types()[base];
4223 for (int i = 0; i < types.size(); ++i) {
4224 if (types[i].script == base_scr) {
4225 return types[i].name;
4226 }
4227 }
4228 }
4229 base_scr = base_scr->get_base_script();
4230 }
4231 }
4232
4233 return StringName();
4234}
4235
4236void EditorNode::_pick_main_scene_custom_action(const String &p_custom_action_name) {
4237 if (p_custom_action_name == "select_current") {
4238 Node *scene = editor_data.get_edited_scene_root();
4239
4240 if (!scene) {
4241 show_accept(TTR("There is no defined scene to run."), TTR("OK"));
4242 return;
4243 }
4244
4245 pick_main_scene->hide();
4246
4247 if (!FileAccess::exists(scene->get_scene_file_path())) {
4248 current_menu_option = FILE_SAVE_AND_RUN_MAIN_SCENE;
4249 _menu_option_confirm(FILE_SAVE_AS_SCENE, true);
4250 file->set_title(TTR("Save scene before running..."));
4251 } else {
4252 current_menu_option = SETTINGS_PICK_MAIN_SCENE;
4253 _dialog_action(scene->get_scene_file_path());
4254 }
4255 }
4256}
4257
4258Ref<Texture2D> EditorNode::_get_class_or_script_icon(const String &p_class, const Ref<Script> &p_script, const String &p_fallback, bool p_fallback_script_to_theme) {
4259 ERR_FAIL_COND_V_MSG(p_class.is_empty(), nullptr, "Class name cannot be empty.");
4260 EditorData &ed = EditorNode::get_editor_data();
4261
4262 // Check for a script icon first.
4263 if (p_script.is_valid()) {
4264 Ref<Texture2D> script_icon = ed.get_script_icon(p_script);
4265 if (script_icon.is_valid()) {
4266 return script_icon;
4267 }
4268
4269 if (p_fallback_script_to_theme) {
4270 // Look for the native base type in the editor theme. This is relevant for
4271 // scripts extending other scripts and for built-in classes.
4272 String script_class_name = p_script->get_language()->get_global_class_name(p_script->get_path());
4273 String base_type = ScriptServer::get_global_class_native_base(script_class_name);
4274 if (theme.is_valid() && theme->has_icon(base_type, EditorStringName(EditorIcons))) {
4275 return theme->get_icon(base_type, EditorStringName(EditorIcons));
4276 }
4277 }
4278 }
4279
4280 // Script was not valid or didn't yield any useful values, try the class name
4281 // directly.
4282
4283 // Check if the class name is an extension-defined type.
4284 Ref<Texture2D> ext_icon = ed.extension_class_get_icon(p_class);
4285 if (ext_icon.is_valid()) {
4286 return ext_icon;
4287 }
4288
4289 // Check if the class name is a custom type.
4290 // TODO: Should probably be deprecated in 4.x
4291 const EditorData::CustomType *ctype = ed.get_custom_type_by_name(p_class);
4292 if (ctype && ctype->icon.is_valid()) {
4293 return ctype->icon;
4294 }
4295
4296 // Look up the class name or the fallback name in the editor theme.
4297 // This is only relevant for built-in classes.
4298 if (theme.is_valid()) {
4299 if (theme->has_icon(p_class, EditorStringName(EditorIcons))) {
4300 return theme->get_icon(p_class, EditorStringName(EditorIcons));
4301 }
4302
4303 if (!p_fallback.is_empty() && theme->has_icon(p_fallback, EditorStringName(EditorIcons))) {
4304 return theme->get_icon(p_fallback, EditorStringName(EditorIcons));
4305 }
4306
4307 // If the fallback is empty or wasn't found, use the default fallback.
4308 if (ClassDB::class_exists(p_class)) {
4309 bool instantiable = !ClassDB::is_virtual(p_class) && ClassDB::can_instantiate(p_class);
4310 if (ClassDB::is_parent_class(p_class, SNAME("Node"))) {
4311 return theme->get_icon(instantiable ? "Node" : "NodeDisabled", EditorStringName(EditorIcons));
4312 } else {
4313 return theme->get_icon(instantiable ? "Object" : "ObjectDisabled", EditorStringName(EditorIcons));
4314 }
4315 }
4316 }
4317
4318 return nullptr;
4319}
4320
4321Ref<Texture2D> EditorNode::get_object_icon(const Object *p_object, const String &p_fallback) {
4322 ERR_FAIL_NULL_V_MSG(p_object, nullptr, "Object cannot be null.");
4323
4324 Ref<Script> scr = p_object->get_script();
4325 if (scr.is_null() && p_object->is_class("Script")) {
4326 scr = p_object;
4327 }
4328
4329 return _get_class_or_script_icon(p_object->get_class(), scr, p_fallback);
4330}
4331
4332Ref<Texture2D> EditorNode::get_class_icon(const String &p_class, const String &p_fallback) {
4333 ERR_FAIL_COND_V_MSG(p_class.is_empty(), nullptr, "Class name cannot be empty.");
4334
4335 Ref<Script> scr;
4336 if (ScriptServer::is_global_class(p_class)) {
4337 scr = EditorNode::get_editor_data().script_class_load_script(p_class);
4338 }
4339
4340 return _get_class_or_script_icon(p_class, scr, p_fallback, true);
4341}
4342
4343bool EditorNode::is_object_of_custom_type(const Object *p_object, const StringName &p_class) {
4344 ERR_FAIL_NULL_V(p_object, false);
4345
4346 Ref<Script> scr = p_object->get_script();
4347 if (scr.is_null() && Object::cast_to<Script>(p_object)) {
4348 scr = p_object;
4349 }
4350
4351 if (scr.is_valid()) {
4352 Ref<Script> base_script = scr;
4353 while (base_script.is_valid()) {
4354 StringName name = EditorNode::get_editor_data().script_class_get_name(base_script->get_path());
4355 if (name == p_class) {
4356 return true;
4357 }
4358 base_script = base_script->get_base_script();
4359 }
4360 }
4361 return false;
4362}
4363
4364void EditorNode::progress_add_task(const String &p_task, const String &p_label, int p_steps, bool p_can_cancel) {
4365 if (singleton->cmdline_export_mode) {
4366 print_line(p_task + ": begin: " + p_label + " steps: " + itos(p_steps));
4367 } else if (singleton->progress_dialog) {
4368 singleton->progress_dialog->add_task(p_task, p_label, p_steps, p_can_cancel);
4369 }
4370}
4371
4372bool EditorNode::progress_task_step(const String &p_task, const String &p_state, int p_step, bool p_force_refresh) {
4373 if (singleton->cmdline_export_mode) {
4374 print_line("\t" + p_task + ": step " + itos(p_step) + ": " + p_state);
4375 return false;
4376 } else if (singleton->progress_dialog) {
4377 return singleton->progress_dialog->task_step(p_task, p_state, p_step, p_force_refresh);
4378 } else {
4379 return false;
4380 }
4381}
4382
4383void EditorNode::progress_end_task(const String &p_task) {
4384 if (singleton->cmdline_export_mode) {
4385 print_line(p_task + ": end");
4386 } else if (singleton->progress_dialog) {
4387 singleton->progress_dialog->end_task(p_task);
4388 }
4389}
4390
4391void EditorNode::progress_add_task_bg(const String &p_task, const String &p_label, int p_steps) {
4392 singleton->progress_hb->add_task(p_task, p_label, p_steps);
4393}
4394
4395void EditorNode::progress_task_step_bg(const String &p_task, int p_step) {
4396 singleton->progress_hb->task_step(p_task, p_step);
4397}
4398
4399void EditorNode::progress_end_task_bg(const String &p_task) {
4400 singleton->progress_hb->end_task(p_task);
4401}
4402
4403String EditorNode::_get_system_info() const {
4404 String distribution_name = OS::get_singleton()->get_distribution_name();
4405 if (distribution_name.is_empty()) {
4406 distribution_name = OS::get_singleton()->get_name();
4407 }
4408 if (distribution_name.is_empty()) {
4409 distribution_name = "Other";
4410 }
4411 const String distribution_version = OS::get_singleton()->get_version();
4412
4413 String godot_version = "Godot v" + String(VERSION_FULL_CONFIG);
4414 if (String(VERSION_BUILD) != "official") {
4415 String hash = String(VERSION_HASH);
4416 hash = hash.is_empty() ? String("unknown") : vformat("(%s)", hash.left(9));
4417 godot_version += " " + hash;
4418 }
4419
4420#ifdef LINUXBSD_ENABLED
4421 const String display_server = OS::get_singleton()->get_environment("XDG_SESSION_TYPE").capitalize().replace(" ", ""); // `replace` is necessary, because `capitalize` introduces a whitespace between "x" and "11".
4422#endif // LINUXBSD_ENABLED
4423 String driver_name = GLOBAL_GET("rendering/rendering_device/driver");
4424 String rendering_method = GLOBAL_GET("rendering/renderer/rendering_method");
4425
4426 const String rendering_device_name = RenderingServer::get_singleton()->get_video_adapter_name();
4427
4428 RenderingDevice::DeviceType device_type = RenderingServer::get_singleton()->get_video_adapter_type();
4429 String device_type_string;
4430 switch (device_type) {
4431 case RenderingDevice::DeviceType::DEVICE_TYPE_INTEGRATED_GPU:
4432 device_type_string = "integrated";
4433 break;
4434 case RenderingDevice::DeviceType::DEVICE_TYPE_DISCRETE_GPU:
4435 device_type_string = "dedicated";
4436 break;
4437 case RenderingDevice::DeviceType::DEVICE_TYPE_VIRTUAL_GPU:
4438 device_type_string = "virtual";
4439 break;
4440 case RenderingDevice::DeviceType::DEVICE_TYPE_CPU:
4441 device_type_string = "(software emulation on CPU)";
4442 break;
4443 case RenderingDevice::DeviceType::DEVICE_TYPE_OTHER:
4444 case RenderingDevice::DeviceType::DEVICE_TYPE_MAX:
4445 break; // Can't happen, but silences warning for DEVICE_TYPE_MAX
4446 }
4447
4448 const Vector<String> video_adapter_driver_info = OS::get_singleton()->get_video_adapter_driver_info();
4449
4450 const String processor_name = OS::get_singleton()->get_processor_name();
4451 const int processor_count = OS::get_singleton()->get_processor_count();
4452
4453 // Prettify
4454 if (rendering_method == "forward_plus") {
4455 rendering_method = "Forward+";
4456 } else if (rendering_method == "mobile") {
4457 rendering_method = "Mobile";
4458 } else if (rendering_method == "gl_compatibility") {
4459 rendering_method = "Compatibility";
4460 driver_name = GLOBAL_GET("rendering/gl_compatibility/driver");
4461 }
4462 if (driver_name == "vulkan") {
4463 driver_name = "Vulkan";
4464 } else if (driver_name == "opengl3") {
4465 driver_name = "GLES3";
4466 }
4467
4468 // Join info.
4469 Vector<String> info;
4470 info.push_back(godot_version);
4471 if (!distribution_version.is_empty()) {
4472 info.push_back(distribution_name + " " + distribution_version);
4473 } else {
4474 info.push_back(distribution_name);
4475 }
4476#ifdef LINUXBSD_ENABLED
4477 if (!display_server.is_empty()) {
4478 info.push_back(display_server);
4479 }
4480#endif // LINUXBSD_ENABLED
4481 info.push_back(vformat("%s (%s)", driver_name, rendering_method));
4482
4483 String graphics;
4484 if (!device_type_string.is_empty()) {
4485 graphics = device_type_string + " ";
4486 }
4487 graphics += rendering_device_name;
4488 if (video_adapter_driver_info.size() == 2) { // This vector is always either of length 0 or 2.
4489 String vad_name = video_adapter_driver_info[0];
4490 String vad_version = video_adapter_driver_info[1]; // Version could be potentially empty on Linux/BSD.
4491 if (!vad_version.is_empty()) {
4492 graphics += vformat(" (%s; %s)", vad_name, vad_version);
4493 } else {
4494 graphics += vformat(" (%s)", vad_name);
4495 }
4496 }
4497 info.push_back(graphics);
4498
4499 info.push_back(vformat("%s (%d Threads)", processor_name, processor_count));
4500
4501 return String(" - ").join(info);
4502}
4503
4504Ref<Texture2D> EditorNode::_file_dialog_get_icon(const String &p_path) {
4505 EditorFileSystemDirectory *efsd = EditorFileSystem::get_singleton()->get_filesystem_path(p_path.get_base_dir());
4506 if (efsd) {
4507 String file = p_path.get_file();
4508 for (int i = 0; i < efsd->get_file_count(); i++) {
4509 if (efsd->get_file(i) == file) {
4510 String type = efsd->get_file_type(i);
4511
4512 if (singleton->icon_type_cache.has(type)) {
4513 return singleton->icon_type_cache[type];
4514 } else {
4515 return singleton->icon_type_cache["Object"];
4516 }
4517 }
4518 }
4519 }
4520
4521 return singleton->icon_type_cache["Object"];
4522}
4523
4524void EditorNode::_build_icon_type_cache() {
4525 List<StringName> tl;
4526 theme->get_icon_list(EditorStringName(EditorIcons), &tl);
4527 for (const StringName &E : tl) {
4528 if (!ClassDB::class_exists(E)) {
4529 continue;
4530 }
4531 icon_type_cache[E] = theme->get_icon(E, EditorStringName(EditorIcons));
4532 }
4533}
4534
4535void EditorNode::_enable_pending_addons() {
4536 for (uint32_t i = 0; i < pending_addons.size(); i++) {
4537 set_addon_plugin_enabled(pending_addons[i], true);
4538 }
4539 pending_addons.clear();
4540}
4541
4542void EditorNode::_file_dialog_register(FileDialog *p_dialog) {
4543 singleton->file_dialogs.insert(p_dialog);
4544}
4545
4546void EditorNode::_file_dialog_unregister(FileDialog *p_dialog) {
4547 singleton->file_dialogs.erase(p_dialog);
4548}
4549
4550void EditorNode::_editor_file_dialog_register(EditorFileDialog *p_dialog) {
4551 singleton->editor_file_dialogs.insert(p_dialog);
4552}
4553
4554void EditorNode::_editor_file_dialog_unregister(EditorFileDialog *p_dialog) {
4555 singleton->editor_file_dialogs.erase(p_dialog);
4556}
4557
4558Vector<EditorNodeInitCallback> EditorNode::_init_callbacks;
4559
4560void EditorNode::_begin_first_scan() {
4561 OS::get_singleton()->benchmark_begin_measure("editor_scan_and_import");
4562 EditorFileSystem::get_singleton()->scan();
4563}
4564
4565Error EditorNode::export_preset(const String &p_preset, const String &p_path, bool p_debug, bool p_pack_only) {
4566 export_defer.preset = p_preset;
4567 export_defer.path = p_path;
4568 export_defer.debug = p_debug;
4569 export_defer.pack_only = p_pack_only;
4570 cmdline_export_mode = true;
4571 return OK;
4572}
4573
4574void EditorNode::show_accept(const String &p_text, const String &p_title) {
4575 current_menu_option = -1;
4576 if (accept) {
4577 accept->set_ok_button_text(p_title);
4578 accept->set_text(p_text);
4579 EditorInterface::get_singleton()->popup_dialog_centered(accept);
4580 }
4581}
4582
4583void EditorNode::show_save_accept(const String &p_text, const String &p_title) {
4584 current_menu_option = -1;
4585 if (save_accept) {
4586 save_accept->set_ok_button_text(p_title);
4587 save_accept->set_text(p_text);
4588 EditorInterface::get_singleton()->popup_dialog_centered(save_accept);
4589 }
4590}
4591
4592void EditorNode::show_warning(const String &p_text, const String &p_title) {
4593 if (warning) {
4594 warning->set_text(p_text);
4595 warning->set_title(p_title);
4596 EditorInterface::get_singleton()->popup_dialog_centered(warning);
4597 } else {
4598 WARN_PRINT(p_title + " " + p_text);
4599 }
4600}
4601
4602void EditorNode::_copy_warning(const String &p_str) {
4603 DisplayServer::get_singleton()->clipboard_set(warning->get_text());
4604}
4605
4606void EditorNode::_dock_floating_close_request(WindowWrapper *p_wrapper) {
4607 int dock_slot_num = p_wrapper->get_meta("dock_slot");
4608 int dock_slot_index = p_wrapper->get_meta("dock_index");
4609
4610 // Give back the dock to the original owner.
4611 Control *dock = p_wrapper->release_wrapped_control();
4612
4613 dock_slot[dock_slot_num]->add_child(dock);
4614 dock_slot[dock_slot_num]->move_child(dock, MIN(dock_slot_index, dock_slot[dock_slot_num]->get_tab_count()));
4615 dock_slot[dock_slot_num]->set_current_tab(dock_slot_index);
4616
4617 floating_docks.erase(p_wrapper);
4618 p_wrapper->queue_free();
4619
4620 _update_dock_containers();
4621
4622 _edit_current();
4623}
4624
4625void EditorNode::_dock_make_selected_float() {
4626 Control *dock = dock_slot[dock_popup_selected_idx]->get_current_tab_control();
4627 _dock_make_float(dock, dock_popup_selected_idx);
4628
4629 dock_select_popup->hide();
4630 _edit_current();
4631}
4632
4633void EditorNode::_dock_make_float(Control *p_dock, int p_slot_index, bool p_show_window) {
4634 ERR_FAIL_NULL(p_dock);
4635
4636 Size2 borders = Size2(4, 4) * EDSCALE;
4637 // Remember size and position before removing it from the main window.
4638 Size2 dock_size = p_dock->get_size() + borders * 2;
4639 Point2 dock_screen_pos = p_dock->get_screen_position();
4640
4641 int dock_index = p_dock->get_index() - 1;
4642 dock_slot[p_slot_index]->remove_child(p_dock);
4643
4644 WindowWrapper *wrapper = memnew(WindowWrapper);
4645 wrapper->set_window_title(vformat(TTR("%s - Godot Engine"), p_dock->get_name()));
4646 wrapper->set_margins_enabled(true);
4647
4648 gui_base->add_child(wrapper);
4649
4650 wrapper->set_wrapped_control(p_dock);
4651 wrapper->set_meta("dock_slot", p_slot_index);
4652 wrapper->set_meta("dock_index", dock_index);
4653 wrapper->set_meta("dock_name", p_dock->get_name().operator String());
4654
4655 wrapper->connect("window_close_requested", callable_mp(this, &EditorNode::_dock_floating_close_request).bind(wrapper));
4656
4657 dock_select_popup->hide();
4658
4659 if (p_show_window) {
4660 wrapper->restore_window(Rect2i(dock_screen_pos, dock_size), get_window()->get_current_screen());
4661 }
4662
4663 _update_dock_containers();
4664
4665 floating_docks.push_back(wrapper);
4666
4667 _edit_current();
4668}
4669
4670void EditorNode::_update_dock_containers() {
4671 for (int i = 0; i < DOCK_SLOT_MAX; i++) {
4672 if (dock_slot[i]->get_tab_count() == 0 && dock_slot[i]->is_visible()) {
4673 dock_slot[i]->hide();
4674 }
4675 if (dock_slot[i]->get_tab_count() > 0 && !dock_slot[i]->is_visible()) {
4676 dock_slot[i]->show();
4677 }
4678 }
4679 for (int i = 0; i < vsplits.size(); i++) {
4680 bool in_use = dock_slot[i * 2 + 0]->get_tab_count() || dock_slot[i * 2 + 1]->get_tab_count();
4681 if (in_use) {
4682 vsplits[i]->show();
4683 } else {
4684 vsplits[i]->hide();
4685 }
4686 }
4687
4688 if (right_l_vsplit->is_visible() || right_r_vsplit->is_visible()) {
4689 right_hsplit->show();
4690 } else {
4691 right_hsplit->hide();
4692 }
4693}
4694
4695void EditorNode::_dock_select_input(const Ref<InputEvent> &p_input) {
4696 Ref<InputEventMouse> me = p_input;
4697
4698 if (me.is_valid()) {
4699 Vector2 point = me->get_position();
4700
4701 int nrect = -1;
4702 for (int i = 0; i < DOCK_SLOT_MAX; i++) {
4703 if (dock_select_rect[i].has_point(point)) {
4704 nrect = i;
4705 break;
4706 }
4707 }
4708
4709 if (nrect != dock_select_rect_over_idx) {
4710 dock_select->queue_redraw();
4711 dock_select_rect_over_idx = nrect;
4712 }
4713
4714 if (nrect == -1) {
4715 return;
4716 }
4717
4718 Ref<InputEventMouseButton> mb = me;
4719
4720 if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed() && dock_popup_selected_idx != nrect) {
4721 Control *dock = dock_slot[dock_popup_selected_idx]->get_current_tab_control();
4722 if (dock) {
4723 dock_slot[dock_popup_selected_idx]->remove_child(dock);
4724 }
4725 if (dock_slot[dock_popup_selected_idx]->get_tab_count() == 0) {
4726 dock_slot[dock_popup_selected_idx]->hide();
4727
4728 } else {
4729 dock_slot[dock_popup_selected_idx]->set_current_tab(0);
4730 }
4731
4732 dock_slot[nrect]->add_child(dock);
4733 dock_popup_selected_idx = nrect;
4734 dock_slot[nrect]->set_current_tab(dock_slot[nrect]->get_tab_count() - 1);
4735 dock_slot[nrect]->set_tab_title(dock_slot[nrect]->get_tab_count() - 1, TTRGET(dock->get_name()));
4736 dock_slot[nrect]->show();
4737 dock_select->queue_redraw();
4738
4739 _update_dock_containers();
4740
4741 _edit_current();
4742 _save_editor_layout();
4743 }
4744 }
4745}
4746
4747void EditorNode::_dock_popup_exit() {
4748 dock_select_rect_over_idx = -1;
4749 dock_select->queue_redraw();
4750}
4751
4752void EditorNode::_dock_pre_popup(int p_which) {
4753 dock_popup_selected_idx = p_which;
4754}
4755
4756void EditorNode::_dock_move_left() {
4757 if (dock_popup_selected_idx < 0 || dock_popup_selected_idx >= DOCK_SLOT_MAX) {
4758 return;
4759 }
4760 Control *current_ctl = dock_slot[dock_popup_selected_idx]->get_tab_control(dock_slot[dock_popup_selected_idx]->get_current_tab());
4761 Control *prev_ctl = dock_slot[dock_popup_selected_idx]->get_tab_control(dock_slot[dock_popup_selected_idx]->get_current_tab() - 1);
4762 if (!current_ctl || !prev_ctl) {
4763 return;
4764 }
4765 dock_slot[dock_popup_selected_idx]->move_child(current_ctl, prev_ctl->get_index(false));
4766 dock_select->queue_redraw();
4767 _edit_current();
4768 _save_editor_layout();
4769}
4770
4771void EditorNode::_dock_move_right() {
4772 Control *current_ctl = dock_slot[dock_popup_selected_idx]->get_tab_control(dock_slot[dock_popup_selected_idx]->get_current_tab());
4773 Control *next_ctl = dock_slot[dock_popup_selected_idx]->get_tab_control(dock_slot[dock_popup_selected_idx]->get_current_tab() + 1);
4774 if (!current_ctl || !next_ctl) {
4775 return;
4776 }
4777 dock_slot[dock_popup_selected_idx]->move_child(next_ctl, current_ctl->get_index(false));
4778 dock_select->queue_redraw();
4779 _edit_current();
4780 _save_editor_layout();
4781}
4782
4783void EditorNode::_dock_select_draw() {
4784 Size2 s = dock_select->get_size();
4785 s.y /= 2.0;
4786 s.x /= 6.0;
4787
4788 Color used = Color(0.6, 0.6, 0.6, 0.8);
4789 Color used_selected = Color(0.8, 0.8, 0.8, 0.8);
4790 Color tab_selected = theme->get_color(SNAME("mono_color"), EditorStringName(Editor));
4791 Color unused = used;
4792 unused.a = 0.4;
4793 Color unusable = unused;
4794 unusable.a = 0.1;
4795
4796 Rect2 unr(s.x * 2, 0, s.x * 2, s.y * 2);
4797 unr.position += Vector2(2, 5);
4798 unr.size -= Vector2(4, 7);
4799
4800 dock_select->draw_rect(unr, unusable);
4801
4802 dock_tab_move_left->set_disabled(true);
4803 dock_tab_move_right->set_disabled(true);
4804
4805 if (dock_popup_selected_idx != -1 && dock_slot[dock_popup_selected_idx]->get_tab_count()) {
4806 dock_tab_move_left->set_disabled(dock_slot[dock_popup_selected_idx]->get_current_tab() == 0);
4807 dock_tab_move_right->set_disabled(dock_slot[dock_popup_selected_idx]->get_current_tab() >= dock_slot[dock_popup_selected_idx]->get_tab_count() - 1);
4808 }
4809
4810 for (int i = 0; i < DOCK_SLOT_MAX; i++) {
4811 Vector2 ofs;
4812
4813 switch (i) {
4814 case DOCK_SLOT_LEFT_UL: {
4815 } break;
4816 case DOCK_SLOT_LEFT_BL: {
4817 ofs.y += s.y;
4818 } break;
4819 case DOCK_SLOT_LEFT_UR: {
4820 ofs.x += s.x;
4821 } break;
4822 case DOCK_SLOT_LEFT_BR: {
4823 ofs += s;
4824 } break;
4825 case DOCK_SLOT_RIGHT_UL: {
4826 ofs.x += s.x * 4;
4827 } break;
4828 case DOCK_SLOT_RIGHT_BL: {
4829 ofs.x += s.x * 4;
4830 ofs.y += s.y;
4831
4832 } break;
4833 case DOCK_SLOT_RIGHT_UR: {
4834 ofs.x += s.x * 4;
4835 ofs.x += s.x;
4836
4837 } break;
4838 case DOCK_SLOT_RIGHT_BR: {
4839 ofs.x += s.x * 4;
4840 ofs += s;
4841
4842 } break;
4843 }
4844
4845 Rect2 r(ofs, s);
4846 dock_select_rect[i] = r;
4847 r.position += Vector2(2, 5);
4848 r.size -= Vector2(4, 7);
4849
4850 if (i == dock_select_rect_over_idx) {
4851 dock_select->draw_rect(r, used_selected);
4852 } else if (dock_slot[i]->get_tab_count() == 0) {
4853 dock_select->draw_rect(r, unused);
4854 } else {
4855 dock_select->draw_rect(r, used);
4856 }
4857
4858 for (int j = 0; j < MIN(3, dock_slot[i]->get_tab_count()); j++) {
4859 int xofs = (r.size.width / 3) * j;
4860 Color c = used;
4861 if (i == dock_popup_selected_idx && (dock_slot[i]->get_current_tab() > 3 || dock_slot[i]->get_current_tab() == j)) {
4862 c = tab_selected;
4863 }
4864 dock_select->draw_rect(Rect2(2 + ofs.x + xofs, ofs.y, r.size.width / 3 - 1, 3), c);
4865 }
4866 }
4867}
4868
4869void EditorNode::_save_editor_layout() {
4870 if (waiting_for_first_scan) {
4871 return; // Scanning, do not touch docks.
4872 }
4873 Ref<ConfigFile> config;
4874 config.instantiate();
4875 // Load and amend existing config if it exists.
4876 config->load(EditorPaths::get_singleton()->get_project_settings_dir().path_join("editor_layout.cfg"));
4877
4878 _save_docks_to_config(config, "docks");
4879 _save_open_scenes_to_config(config);
4880 _save_central_editor_layout_to_config(config);
4881 editor_data.get_plugin_window_layout(config);
4882
4883 config->save(EditorPaths::get_singleton()->get_project_settings_dir().path_join("editor_layout.cfg"));
4884}
4885
4886void EditorNode::_save_docks_to_config(Ref<ConfigFile> p_layout, const String &p_section) {
4887 for (int i = 0; i < DOCK_SLOT_MAX; i++) {
4888 String names;
4889 for (int j = 0; j < dock_slot[i]->get_tab_count(); j++) {
4890 String name = dock_slot[i]->get_tab_control(j)->get_name();
4891 if (!names.is_empty()) {
4892 names += ",";
4893 }
4894 names += name;
4895 }
4896
4897 String config_key = "dock_" + itos(i + 1);
4898
4899 if (p_layout->has_section_key(p_section, config_key)) {
4900 p_layout->erase_section_key(p_section, config_key);
4901 }
4902
4903 if (!names.is_empty()) {
4904 p_layout->set_value(p_section, config_key, names);
4905 }
4906
4907 int selected_tab_idx = dock_slot[i]->get_current_tab();
4908 if (selected_tab_idx >= 0) {
4909 p_layout->set_value(p_section, "dock_" + itos(i + 1) + "_selected_tab_idx", selected_tab_idx);
4910 }
4911 }
4912
4913 Dictionary floating_docks_dump;
4914
4915 for (WindowWrapper *wrapper : floating_docks) {
4916 Control *dock = wrapper->get_wrapped_control();
4917
4918 Dictionary dock_dump;
4919 dock_dump["window_rect"] = wrapper->get_window_rect();
4920
4921 int screen = wrapper->get_window_screen();
4922 dock_dump["window_screen"] = wrapper->get_window_screen();
4923 dock_dump["window_screen_rect"] = DisplayServer::get_singleton()->screen_get_usable_rect(screen);
4924
4925 String name = dock->get_name();
4926 floating_docks_dump[name] = dock_dump;
4927
4928 int dock_slot_id = wrapper->get_meta("dock_slot");
4929 String config_key = "dock_" + itos(dock_slot_id + 1);
4930
4931 String names = p_layout->get_value(p_section, config_key, "");
4932 if (names.is_empty()) {
4933 names = name;
4934 } else {
4935 names += "," + name;
4936 }
4937 p_layout->set_value(p_section, config_key, names);
4938 }
4939
4940 p_layout->set_value(p_section, "dock_floating", floating_docks_dump);
4941
4942 for (int i = 0; i < vsplits.size(); i++) {
4943 if (vsplits[i]->is_visible_in_tree()) {
4944 p_layout->set_value(p_section, "dock_split_" + itos(i + 1), vsplits[i]->get_split_offset());
4945 }
4946 }
4947
4948 for (int i = 0; i < hsplits.size(); i++) {
4949 p_layout->set_value(p_section, "dock_hsplit_" + itos(i + 1), hsplits[i]->get_split_offset());
4950 }
4951
4952 // Save FileSystemDock state.
4953
4954 p_layout->set_value(p_section, "dock_filesystem_split", FileSystemDock::get_singleton()->get_split_offset());
4955 p_layout->set_value(p_section, "dock_filesystem_display_mode", FileSystemDock::get_singleton()->get_display_mode());
4956 p_layout->set_value(p_section, "dock_filesystem_file_sort", FileSystemDock::get_singleton()->get_file_sort());
4957 p_layout->set_value(p_section, "dock_filesystem_file_list_display_mode", FileSystemDock::get_singleton()->get_file_list_display_mode());
4958 PackedStringArray selected_files = FileSystemDock::get_singleton()->get_selected_paths();
4959 p_layout->set_value(p_section, "dock_filesystem_selected_paths", selected_files);
4960 Vector<String> uncollapsed_paths = FileSystemDock::get_singleton()->get_uncollapsed_paths();
4961 p_layout->set_value(p_section, "dock_filesystem_uncollapsed_paths", uncollapsed_paths);
4962}
4963
4964void EditorNode::_save_open_scenes_to_config(Ref<ConfigFile> p_layout) {
4965 PackedStringArray scenes;
4966 for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
4967 String path = editor_data.get_scene_path(i);
4968 if (path.is_empty()) {
4969 continue;
4970 }
4971 scenes.push_back(path);
4972 }
4973 p_layout->set_value(EDITOR_NODE_CONFIG_SECTION, "open_scenes", scenes);
4974
4975 String currently_edited_scene_path = editor_data.get_scene_path(editor_data.get_edited_scene());
4976 p_layout->set_value(EDITOR_NODE_CONFIG_SECTION, "current_scene", currently_edited_scene_path);
4977}
4978
4979void EditorNode::save_editor_layout_delayed() {
4980 editor_layout_save_delay_timer->start();
4981}
4982
4983void EditorNode::_dock_split_dragged(int ofs) {
4984 editor_layout_save_delay_timer->start();
4985}
4986
4987void EditorNode::_load_editor_layout() {
4988 Ref<ConfigFile> config;
4989 config.instantiate();
4990 Error err = config->load(EditorPaths::get_singleton()->get_project_settings_dir().path_join("editor_layout.cfg"));
4991 if (err != OK) { // No config.
4992 // If config is not found, expand the res:// folder and favorites by default.
4993 TreeItem *root = FileSystemDock::get_singleton()->get_tree_control()->get_item_with_metadata("res://", 0);
4994 if (root) {
4995 root->set_collapsed(false);
4996 }
4997
4998 TreeItem *favorites = FileSystemDock::get_singleton()->get_tree_control()->get_item_with_metadata("Favorites", 0);
4999 if (favorites) {
5000 favorites->set_collapsed(false);
5001 }
5002
5003 if (overridden_default_layout >= 0) {
5004 _layout_menu_option(overridden_default_layout);
5005 }
5006 return;
5007 }
5008
5009 _load_docks_from_config(config, "docks");
5010 _load_open_scenes_from_config(config);
5011 _load_central_editor_layout_from_config(config);
5012
5013 editor_data.set_plugin_window_layout(config);
5014}
5015
5016void EditorNode::_update_dock_slots_visibility(bool p_keep_selected_tabs) {
5017 if (!docks_visible) {
5018 for (int i = 0; i < DOCK_SLOT_MAX; i++) {
5019 dock_slot[i]->hide();
5020 }
5021
5022 for (int i = 0; i < vsplits.size(); i++) {
5023 vsplits[i]->hide();
5024 }
5025
5026 right_hsplit->hide();
5027 } else {
5028 for (int i = 0; i < DOCK_SLOT_MAX; i++) {
5029 int tabs_visible = 0;
5030 for (int j = 0; j < dock_slot[i]->get_tab_count(); j++) {
5031 if (!dock_slot[i]->is_tab_hidden(j)) {
5032 tabs_visible++;
5033 }
5034 }
5035 if (tabs_visible) {
5036 dock_slot[i]->show();
5037 } else {
5038 dock_slot[i]->hide();
5039 }
5040 }
5041
5042 for (int i = 0; i < vsplits.size(); i++) {
5043 bool in_use = dock_slot[i * 2 + 0]->get_tab_count() || dock_slot[i * 2 + 1]->get_tab_count();
5044 if (in_use) {
5045 vsplits[i]->show();
5046 } else {
5047 vsplits[i]->hide();
5048 }
5049 }
5050
5051 if (!p_keep_selected_tabs) {
5052 for (int i = 0; i < DOCK_SLOT_MAX; i++) {
5053 if (dock_slot[i]->is_visible() && dock_slot[i]->get_tab_count()) {
5054 dock_slot[i]->set_current_tab(0);
5055 }
5056 }
5057 }
5058
5059 if (right_l_vsplit->is_visible() || right_r_vsplit->is_visible()) {
5060 right_hsplit->show();
5061 } else {
5062 right_hsplit->hide();
5063 }
5064 }
5065}
5066
5067void EditorNode::_dock_tab_changed(int p_tab) {
5068 // Update visibility but don't set current tab.
5069
5070 if (!docks_visible) {
5071 for (int i = 0; i < DOCK_SLOT_MAX; i++) {
5072 dock_slot[i]->hide();
5073 }
5074
5075 for (int i = 0; i < vsplits.size(); i++) {
5076 vsplits[i]->hide();
5077 }
5078
5079 right_hsplit->hide();
5080 bottom_panel->hide();
5081 } else {
5082 for (int i = 0; i < DOCK_SLOT_MAX; i++) {
5083 if (dock_slot[i]->get_tab_count()) {
5084 dock_slot[i]->show();
5085 } else {
5086 dock_slot[i]->hide();
5087 }
5088 }
5089
5090 for (int i = 0; i < vsplits.size(); i++) {
5091 bool in_use = dock_slot[i * 2 + 0]->get_tab_count() || dock_slot[i * 2 + 1]->get_tab_count();
5092 if (in_use) {
5093 vsplits[i]->show();
5094 } else {
5095 vsplits[i]->hide();
5096 }
5097 }
5098 bottom_panel->show();
5099
5100 if (right_l_vsplit->is_visible() || right_r_vsplit->is_visible()) {
5101 right_hsplit->show();
5102 } else {
5103 right_hsplit->hide();
5104 }
5105 }
5106}
5107
5108void EditorNode::_restore_floating_dock(const Dictionary &p_dock_dump, Control *p_dock, int p_slot_index) {
5109 WindowWrapper *wrapper = Object::cast_to<WindowWrapper>(p_dock);
5110 if (!wrapper) {
5111 _dock_make_float(p_dock, p_slot_index, false);
5112 wrapper = floating_docks[floating_docks.size() - 1];
5113 }
5114
5115 wrapper->restore_window_from_saved_position(
5116 p_dock_dump.get("window_rect", Rect2i()),
5117 p_dock_dump.get("window_screen", -1),
5118 p_dock_dump.get("window_screen_rect", Rect2i()));
5119}
5120
5121void EditorNode::_load_docks_from_config(Ref<ConfigFile> p_layout, const String &p_section) {
5122 Dictionary floating_docks_dump = p_layout->get_value(p_section, "dock_floating", Dictionary());
5123
5124 bool restore_window_on_load = EDITOR_GET("interface/multi_window/restore_windows_on_load");
5125
5126 for (int i = 0; i < DOCK_SLOT_MAX; i++) {
5127 if (!p_layout->has_section_key(p_section, "dock_" + itos(i + 1))) {
5128 continue;
5129 }
5130
5131 Vector<String> names = String(p_layout->get_value(p_section, "dock_" + itos(i + 1))).split(",");
5132
5133 for (int j = names.size() - 1; j >= 0; j--) {
5134 String name = names[j];
5135
5136 // FIXME: Find it, in a horribly inefficient way.
5137 int atidx = -1;
5138 Control *node = nullptr;
5139 for (int k = 0; k < DOCK_SLOT_MAX; k++) {
5140 if (!dock_slot[k]->has_node(name)) {
5141 continue;
5142 }
5143 node = Object::cast_to<Control>(dock_slot[k]->get_node(name));
5144 if (!node) {
5145 continue;
5146 }
5147 atidx = k;
5148 break;
5149 }
5150
5151 if (atidx == -1) {
5152 // Try floating docks.
5153 for (WindowWrapper *wrapper : floating_docks) {
5154 if (wrapper->get_meta("dock_name") == name) {
5155 if (restore_window_on_load && floating_docks_dump.has(name)) {
5156 _restore_floating_dock(floating_docks_dump[name], wrapper, i);
5157 return;
5158 } else {
5159 _dock_floating_close_request(wrapper);
5160 atidx = wrapper->get_meta("dock_index");
5161 }
5162 }
5163 }
5164
5165 // Well, it's not anywhere.
5166 continue;
5167 }
5168
5169 if (atidx == i) {
5170 dock_slot[i]->move_child(node, 0);
5171 } else if (atidx != -1) {
5172 dock_slot[atidx]->remove_child(node);
5173
5174 if (dock_slot[atidx]->get_tab_count() == 0) {
5175 dock_slot[atidx]->hide();
5176 }
5177 dock_slot[i]->add_child(node);
5178 dock_slot[i]->move_child(node, 0);
5179 dock_slot[i]->set_tab_title(0, TTRGET(node->get_name()));
5180 dock_slot[i]->show();
5181 }
5182
5183 WindowWrapper *wrapper = Object::cast_to<WindowWrapper>(node);
5184 if (restore_window_on_load && floating_docks_dump.has(name)) {
5185 _restore_floating_dock(floating_docks_dump[name], node, i);
5186 } else if (wrapper) {
5187 _dock_floating_close_request(wrapper);
5188 }
5189 }
5190
5191 if (!p_layout->has_section_key(p_section, "dock_" + itos(i + 1) + "_selected_tab_idx")) {
5192 continue;
5193 }
5194
5195 int selected_tab_idx = p_layout->get_value(p_section, "dock_" + itos(i + 1) + "_selected_tab_idx");
5196 if (selected_tab_idx >= 0 && selected_tab_idx < dock_slot[i]->get_tab_count()) {
5197 dock_slot[i]->call_deferred("set_current_tab", selected_tab_idx);
5198 }
5199 }
5200
5201 for (int i = 0; i < vsplits.size(); i++) {
5202 if (!p_layout->has_section_key(p_section, "dock_split_" + itos(i + 1))) {
5203 continue;
5204 }
5205
5206 int ofs = p_layout->get_value(p_section, "dock_split_" + itos(i + 1));
5207 vsplits[i]->set_split_offset(ofs);
5208 }
5209
5210 for (int i = 0; i < hsplits.size(); i++) {
5211 if (!p_layout->has_section_key(p_section, "dock_hsplit_" + itos(i + 1))) {
5212 continue;
5213 }
5214 int ofs = p_layout->get_value(p_section, "dock_hsplit_" + itos(i + 1));
5215 hsplits[i]->set_split_offset(ofs);
5216 }
5217
5218 for (int i = 0; i < vsplits.size(); i++) {
5219 bool in_use = dock_slot[i * 2 + 0]->get_tab_count() || dock_slot[i * 2 + 1]->get_tab_count();
5220 if (in_use) {
5221 vsplits[i]->show();
5222 } else {
5223 vsplits[i]->hide();
5224 }
5225 }
5226
5227 if (right_l_vsplit->is_visible() || right_r_vsplit->is_visible()) {
5228 right_hsplit->show();
5229 } else {
5230 right_hsplit->hide();
5231 }
5232
5233 for (int i = 0; i < DOCK_SLOT_MAX; i++) {
5234 if (dock_slot[i]->is_visible() && dock_slot[i]->get_tab_count()) {
5235 dock_slot[i]->set_current_tab(0);
5236 }
5237 }
5238
5239 // FileSystemDock.
5240
5241 if (p_layout->has_section_key(p_section, "dock_filesystem_split")) {
5242 int fs_split_ofs = p_layout->get_value(p_section, "dock_filesystem_split");
5243 FileSystemDock::get_singleton()->set_split_offset(fs_split_ofs);
5244 }
5245
5246 if (p_layout->has_section_key(p_section, "dock_filesystem_display_mode")) {
5247 FileSystemDock::DisplayMode dock_filesystem_display_mode = FileSystemDock::DisplayMode(int(p_layout->get_value(p_section, "dock_filesystem_display_mode")));
5248 FileSystemDock::get_singleton()->set_display_mode(dock_filesystem_display_mode);
5249 }
5250
5251 if (p_layout->has_section_key(p_section, "dock_filesystem_file_sort")) {
5252 FileSystemDock::FileSortOption dock_filesystem_file_sort = FileSystemDock::FileSortOption(int(p_layout->get_value(p_section, "dock_filesystem_file_sort")));
5253 FileSystemDock::get_singleton()->set_file_sort(dock_filesystem_file_sort);
5254 }
5255
5256 if (p_layout->has_section_key(p_section, "dock_filesystem_file_list_display_mode")) {
5257 FileSystemDock::FileListDisplayMode dock_filesystem_file_list_display_mode = FileSystemDock::FileListDisplayMode(int(p_layout->get_value(p_section, "dock_filesystem_file_list_display_mode")));
5258 FileSystemDock::get_singleton()->set_file_list_display_mode(dock_filesystem_file_list_display_mode);
5259 }
5260
5261 if (p_layout->has_section_key(p_section, "dock_filesystem_selected_paths")) {
5262 PackedStringArray dock_filesystem_selected_paths = p_layout->get_value(p_section, "dock_filesystem_selected_paths");
5263 for (int i = 0; i < dock_filesystem_selected_paths.size(); i++) {
5264 FileSystemDock::get_singleton()->select_file(dock_filesystem_selected_paths[i]);
5265 }
5266 }
5267
5268 // Restore collapsed state of FileSystemDock.
5269 PackedStringArray uncollapsed_tis;
5270 if (p_layout->has_section_key(p_section, "dock_filesystem_uncollapsed_paths")) {
5271 uncollapsed_tis = p_layout->get_value(p_section, "dock_filesystem_uncollapsed_paths");
5272 } else {
5273 uncollapsed_tis = { "res://" };
5274 }
5275
5276 if (!uncollapsed_tis.is_empty()) {
5277 for (int i = 0; i < uncollapsed_tis.size(); i++) {
5278 TreeItem *uncollapsed_ti = FileSystemDock::get_singleton()->get_tree_control()->get_item_with_metadata(uncollapsed_tis[i], 0);
5279 if (uncollapsed_ti) {
5280 uncollapsed_ti->set_collapsed(false);
5281 }
5282 }
5283 FileSystemDock::get_singleton()->get_tree_control()->queue_redraw();
5284 }
5285}
5286
5287void EditorNode::_save_central_editor_layout_to_config(Ref<ConfigFile> p_config_file) {
5288 // Bottom panel.
5289
5290 int center_split_offset = center_split->get_split_offset();
5291 p_config_file->set_value(EDITOR_NODE_CONFIG_SECTION, "center_split_offset", center_split_offset);
5292
5293 int selected_bottom_panel_item_idx = -1;
5294 for (int i = 0; i < bottom_panel_items.size(); i++) {
5295 if (bottom_panel_items[i].button->is_pressed()) {
5296 selected_bottom_panel_item_idx = i;
5297 break;
5298 }
5299 }
5300 if (selected_bottom_panel_item_idx != -1) {
5301 p_config_file->set_value(EDITOR_NODE_CONFIG_SECTION, "selected_bottom_panel_item", selected_bottom_panel_item_idx);
5302 } else {
5303 p_config_file->set_value(EDITOR_NODE_CONFIG_SECTION, "selected_bottom_panel_item", Variant());
5304 }
5305
5306 // Debugger tab.
5307
5308 int selected_default_debugger_tab_idx = EditorDebuggerNode::get_singleton()->get_default_debugger()->get_current_debugger_tab();
5309 p_config_file->set_value(EDITOR_NODE_CONFIG_SECTION, "selected_default_debugger_tab_idx", selected_default_debugger_tab_idx);
5310
5311 // Main editor (plugin).
5312
5313 int selected_main_editor_idx = -1;
5314 for (int i = 0; i < main_editor_buttons.size(); i++) {
5315 if (main_editor_buttons[i]->is_pressed()) {
5316 selected_main_editor_idx = i;
5317 break;
5318 }
5319 }
5320 if (selected_main_editor_idx != -1) {
5321 p_config_file->set_value(EDITOR_NODE_CONFIG_SECTION, "selected_main_editor_idx", selected_main_editor_idx);
5322 } else {
5323 p_config_file->set_value(EDITOR_NODE_CONFIG_SECTION, "selected_main_editor_idx", Variant());
5324 }
5325}
5326
5327void EditorNode::_load_central_editor_layout_from_config(Ref<ConfigFile> p_config_file) {
5328 // Bottom panel.
5329
5330 bool has_active_tab = false;
5331 if (p_config_file->has_section_key(EDITOR_NODE_CONFIG_SECTION, "selected_bottom_panel_item")) {
5332 int selected_bottom_panel_item_idx = p_config_file->get_value(EDITOR_NODE_CONFIG_SECTION, "selected_bottom_panel_item");
5333 if (selected_bottom_panel_item_idx >= 0 && selected_bottom_panel_item_idx < bottom_panel_items.size()) {
5334 // Make sure we don't try to open contextual editors which are not enabled in the current context.
5335 if (bottom_panel_items[selected_bottom_panel_item_idx].button->is_visible()) {
5336 _bottom_panel_switch(true, selected_bottom_panel_item_idx);
5337 has_active_tab = true;
5338 }
5339 }
5340 }
5341
5342 if (p_config_file->has_section_key(EDITOR_NODE_CONFIG_SECTION, "center_split_offset")) {
5343 int center_split_offset = p_config_file->get_value(EDITOR_NODE_CONFIG_SECTION, "center_split_offset");
5344 center_split->set_split_offset(center_split_offset);
5345
5346 // If there is no active tab we need to collapse the panel.
5347 if (!has_active_tab) {
5348 bottom_panel_items[0].control->show(); // _bottom_panel_switch() can collapse only visible tabs.
5349 _bottom_panel_switch(false, 0);
5350 }
5351 }
5352
5353 // Debugger tab.
5354
5355 if (p_config_file->has_section_key(EDITOR_NODE_CONFIG_SECTION, "selected_default_debugger_tab_idx")) {
5356 int selected_default_debugger_tab_idx = p_config_file->get_value(EDITOR_NODE_CONFIG_SECTION, "selected_default_debugger_tab_idx");
5357 EditorDebuggerNode::get_singleton()->get_default_debugger()->switch_to_debugger(selected_default_debugger_tab_idx);
5358 }
5359
5360 // Main editor (plugin).
5361
5362 if (p_config_file->has_section_key(EDITOR_NODE_CONFIG_SECTION, "selected_main_editor_idx")) {
5363 int selected_main_editor_idx = p_config_file->get_value(EDITOR_NODE_CONFIG_SECTION, "selected_main_editor_idx");
5364 if (selected_main_editor_idx >= 0 && selected_main_editor_idx < main_editor_buttons.size()) {
5365 callable_mp(this, &EditorNode::editor_select).call_deferred(selected_main_editor_idx);
5366 }
5367 }
5368}
5369
5370void EditorNode::_load_open_scenes_from_config(Ref<ConfigFile> p_layout) {
5371 if (!bool(EDITOR_GET("interface/scene_tabs/restore_scenes_on_load"))) {
5372 return;
5373 }
5374
5375 if (!p_layout->has_section(EDITOR_NODE_CONFIG_SECTION) ||
5376 !p_layout->has_section_key(EDITOR_NODE_CONFIG_SECTION, "open_scenes")) {
5377 return;
5378 }
5379
5380 restoring_scenes = true;
5381
5382 PackedStringArray scenes = p_layout->get_value(EDITOR_NODE_CONFIG_SECTION, "open_scenes");
5383 for (int i = 0; i < scenes.size(); i++) {
5384 load_scene(scenes[i]);
5385 }
5386
5387 if (p_layout->has_section_key(EDITOR_NODE_CONFIG_SECTION, "current_scene")) {
5388 String current_scene = p_layout->get_value(EDITOR_NODE_CONFIG_SECTION, "current_scene");
5389 int current_scene_idx = scenes.find(current_scene);
5390 if (current_scene_idx >= 0) {
5391 _set_current_scene(current_scene_idx);
5392 }
5393 }
5394
5395 save_editor_layout_delayed();
5396
5397 restoring_scenes = false;
5398}
5399
5400bool EditorNode::has_scenes_in_session() {
5401 if (!bool(EDITOR_GET("interface/scene_tabs/restore_scenes_on_load"))) {
5402 return false;
5403 }
5404 Ref<ConfigFile> config;
5405 config.instantiate();
5406 Error err = config->load(EditorPaths::get_singleton()->get_project_settings_dir().path_join("editor_layout.cfg"));
5407 if (err != OK) {
5408 return false;
5409 }
5410 if (!config->has_section(EDITOR_NODE_CONFIG_SECTION) || !config->has_section_key(EDITOR_NODE_CONFIG_SECTION, "open_scenes")) {
5411 return false;
5412 }
5413 Array scenes = config->get_value(EDITOR_NODE_CONFIG_SECTION, "open_scenes");
5414 return !scenes.is_empty();
5415}
5416
5417bool EditorNode::ensure_main_scene(bool p_from_native) {
5418 pick_main_scene->set_meta("from_native", p_from_native); // Whether from play button or native run.
5419 String main_scene = GLOBAL_GET("application/run/main_scene");
5420
5421 if (main_scene.is_empty()) {
5422 current_menu_option = -1;
5423 pick_main_scene->set_text(TTR("No main scene has ever been defined, select one?\nYou can change it later in \"Project Settings\" under the 'application' category."));
5424 pick_main_scene->popup_centered();
5425
5426 if (editor_data.get_edited_scene_root()) {
5427 select_current_scene_button->set_disabled(false);
5428 select_current_scene_button->grab_focus();
5429 } else {
5430 select_current_scene_button->set_disabled(true);
5431 }
5432
5433 return false;
5434 }
5435
5436 if (!FileAccess::exists(main_scene)) {
5437 current_menu_option = -1;
5438 pick_main_scene->set_text(vformat(TTR("Selected scene '%s' does not exist, select a valid one?\nYou can change it later in \"Project Settings\" under the 'application' category."), main_scene));
5439 pick_main_scene->popup_centered();
5440 return false;
5441 }
5442
5443 if (ResourceLoader::get_resource_type(main_scene) != "PackedScene") {
5444 current_menu_option = -1;
5445 pick_main_scene->set_text(vformat(TTR("Selected scene '%s' is not a scene file, select a valid one?\nYou can change it later in \"Project Settings\" under the 'application' category."), main_scene));
5446 pick_main_scene->popup_centered();
5447 return false;
5448 }
5449
5450 return true;
5451}
5452
5453void EditorNode::_immediate_dialog_confirmed() {
5454 immediate_dialog_confirmed = true;
5455}
5456bool EditorNode::immediate_confirmation_dialog(const String &p_text, const String &p_ok_text, const String &p_cancel_text) {
5457 ConfirmationDialog *cd = memnew(ConfirmationDialog);
5458 cd->set_text(p_text);
5459 cd->set_ok_button_text(p_ok_text);
5460 cd->set_cancel_button_text(p_cancel_text);
5461 cd->connect("confirmed", callable_mp(singleton, &EditorNode::_immediate_dialog_confirmed));
5462 singleton->gui_base->add_child(cd);
5463
5464 cd->popup_centered();
5465
5466 while (true) {
5467 OS::get_singleton()->delay_usec(1);
5468 DisplayServer::get_singleton()->process_events();
5469 Main::iteration();
5470 if (singleton->immediate_dialog_confirmed || !cd->is_visible()) {
5471 break;
5472 }
5473 }
5474
5475 memdelete(cd);
5476 return singleton->immediate_dialog_confirmed;
5477}
5478
5479void EditorNode::cleanup() {
5480 _init_callbacks.clear();
5481}
5482
5483void EditorNode::_update_layouts_menu() {
5484 editor_layouts->clear();
5485 overridden_default_layout = -1;
5486
5487 editor_layouts->reset_size();
5488 editor_layouts->add_shortcut(ED_SHORTCUT("layout/save", TTR("Save Layout")), SETTINGS_LAYOUT_SAVE);
5489 editor_layouts->add_shortcut(ED_SHORTCUT("layout/delete", TTR("Delete Layout")), SETTINGS_LAYOUT_DELETE);
5490 editor_layouts->add_separator();
5491 editor_layouts->add_shortcut(ED_SHORTCUT("layout/default", TTR("Default")), SETTINGS_LAYOUT_DEFAULT);
5492
5493 Ref<ConfigFile> config;
5494 config.instantiate();
5495 Error err = config->load(EditorSettings::get_singleton()->get_editor_layouts_config());
5496 if (err != OK) {
5497 return; // No config.
5498 }
5499
5500 List<String> layouts;
5501 config.ptr()->get_sections(&layouts);
5502
5503 for (const String &layout : layouts) {
5504 if (layout == TTR("Default")) {
5505 editor_layouts->remove_item(editor_layouts->get_item_index(SETTINGS_LAYOUT_DEFAULT));
5506 overridden_default_layout = editor_layouts->get_item_count();
5507 }
5508
5509 editor_layouts->add_item(layout);
5510 }
5511}
5512
5513void EditorNode::_layout_menu_option(int p_id) {
5514 switch (p_id) {
5515 case SETTINGS_LAYOUT_SAVE: {
5516 current_menu_option = p_id;
5517 layout_dialog->set_title(TTR("Save Layout"));
5518 layout_dialog->set_ok_button_text(TTR("Save"));
5519 layout_dialog->set_name_line_enabled(true);
5520 layout_dialog->popup_centered();
5521 } break;
5522 case SETTINGS_LAYOUT_DELETE: {
5523 current_menu_option = p_id;
5524 layout_dialog->set_title(TTR("Delete Layout"));
5525 layout_dialog->set_ok_button_text(TTR("Delete"));
5526 layout_dialog->set_name_line_enabled(false);
5527 layout_dialog->popup_centered();
5528 } break;
5529 case SETTINGS_LAYOUT_DEFAULT: {
5530 _load_docks_from_config(default_layout, "docks");
5531 _save_editor_layout();
5532 } break;
5533 default: {
5534 Ref<ConfigFile> config;
5535 config.instantiate();
5536 Error err = config->load(EditorSettings::get_singleton()->get_editor_layouts_config());
5537 if (err != OK) {
5538 return; // No config.
5539 }
5540
5541 _load_docks_from_config(config, editor_layouts->get_item_text(p_id));
5542 _save_editor_layout();
5543 }
5544 }
5545}
5546
5547void EditorNode::_proceed_closing_scene_tabs() {
5548 List<String>::Element *E = tabs_to_close.front();
5549 if (!E) {
5550 if (_is_closing_editor()) {
5551 current_menu_option = tab_closing_menu_option;
5552 _menu_option_confirm(tab_closing_menu_option, true);
5553 } else {
5554 current_menu_option = -1;
5555 save_confirmation->hide();
5556 }
5557 return;
5558 }
5559 String scene_to_close = E->get();
5560 tabs_to_close.pop_front();
5561
5562 int tab_idx = -1;
5563 for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
5564 if (editor_data.get_scene_path(i) == scene_to_close) {
5565 tab_idx = i;
5566 break;
5567 }
5568 }
5569 ERR_FAIL_COND(tab_idx < 0);
5570
5571 _scene_tab_closed(tab_idx);
5572}
5573
5574bool EditorNode::_is_closing_editor() const {
5575 return tab_closing_menu_option == FILE_QUIT || tab_closing_menu_option == RUN_PROJECT_MANAGER || tab_closing_menu_option == RELOAD_CURRENT_PROJECT;
5576}
5577
5578void EditorNode::_scene_tab_closed(int p_tab) {
5579 current_menu_option = SCENE_TAB_CLOSE;
5580 tab_closing_idx = p_tab;
5581 Node *scene = editor_data.get_edited_scene_root(p_tab);
5582 if (!scene) {
5583 _discard_changes();
5584 return;
5585 }
5586
5587 String scene_filename = scene->get_scene_file_path();
5588 String unsaved_message;
5589
5590 if (EditorUndoRedoManager::get_singleton()->is_history_unsaved(editor_data.get_scene_history_id(p_tab))) {
5591 if (scene_filename.is_empty()) {
5592 unsaved_message = TTR("This scene was never saved.");
5593 } else {
5594 // Consider editor startup to be a point of saving, so that when you
5595 // close and reopen the editor, you don't get an excessively long
5596 // "modified X hours ago".
5597 const uint64_t last_modified_seconds = Time::get_singleton()->get_unix_time_from_system() - MAX(started_timestamp, FileAccess::get_modified_time(scene->get_scene_file_path()));
5598 String last_modified_string;
5599 if (last_modified_seconds < 120) {
5600 last_modified_string = vformat(TTRN("%d second ago", "%d seconds ago", last_modified_seconds), last_modified_seconds);
5601 } else if (last_modified_seconds < 7200) {
5602 last_modified_string = vformat(TTRN("%d minute ago", "%d minutes ago", last_modified_seconds / 60), last_modified_seconds / 60);
5603 } else {
5604 last_modified_string = vformat(TTRN("%d hour ago", "%d hours ago", last_modified_seconds / 3600), last_modified_seconds / 3600);
5605 }
5606 unsaved_message = vformat(TTR("Scene \"%s\" has unsaved changes.\nLast saved: %s."), scene_filename, last_modified_string);
5607 }
5608 } else {
5609 // Check if any plugin has unsaved changes in that scene.
5610 for (int i = 0; i < editor_data.get_editor_plugin_count(); i++) {
5611 unsaved_message = editor_data.get_editor_plugin(i)->get_unsaved_status(scene_filename);
5612 if (!unsaved_message.is_empty()) {
5613 break;
5614 }
5615 }
5616 }
5617
5618 if (!unsaved_message.is_empty()) {
5619 if (scene_tabs->get_current_tab() != p_tab) {
5620 _set_current_scene(p_tab);
5621 }
5622
5623 save_confirmation->set_ok_button_text(TTR("Save & Close"));
5624 save_confirmation->set_text(unsaved_message + "\n\n" + TTR("Save before closing?"));
5625 save_confirmation->reset_size();
5626 save_confirmation->popup_centered();
5627 } else {
5628 _discard_changes();
5629 }
5630
5631 save_editor_layout_delayed();
5632 scene_tabs->update_scene_tabs();
5633}
5634
5635Button *EditorNode::add_bottom_panel_item(String p_text, Control *p_item) {
5636 Button *tb = memnew(Button);
5637 tb->set_flat(true);
5638 tb->connect("toggled", callable_mp(this, &EditorNode::_bottom_panel_switch).bind(bottom_panel_items.size()));
5639 tb->set_text(p_text);
5640 tb->set_toggle_mode(true);
5641 tb->set_focus_mode(Control::FOCUS_NONE);
5642 bottom_panel_vb->add_child(p_item);
5643 bottom_panel_hb->move_to_front();
5644 bottom_panel_hb_editors->add_child(tb);
5645 p_item->set_v_size_flags(Control::SIZE_EXPAND_FILL);
5646 p_item->hide();
5647 BottomPanelItem bpi;
5648 bpi.button = tb;
5649 bpi.control = p_item;
5650 bpi.name = p_text;
5651 bottom_panel_items.push_back(bpi);
5652
5653 return tb;
5654}
5655
5656void EditorNode::hide_bottom_panel() {
5657 for (int i = 0; i < bottom_panel_items.size(); i++) {
5658 if (bottom_panel_items[i].control->is_visible()) {
5659 _bottom_panel_switch(false, i);
5660 break;
5661 }
5662 }
5663}
5664
5665void EditorNode::make_bottom_panel_item_visible(Control *p_item) {
5666 for (int i = 0; i < bottom_panel_items.size(); i++) {
5667 if (bottom_panel_items[i].control == p_item) {
5668 _bottom_panel_switch(true, i);
5669 break;
5670 }
5671 }
5672}
5673
5674void EditorNode::raise_bottom_panel_item(Control *p_item) {
5675 for (int i = 0; i < bottom_panel_items.size(); i++) {
5676 if (bottom_panel_items[i].control == p_item) {
5677 bottom_panel_items[i].button->move_to_front();
5678 SWAP(bottom_panel_items.write[i], bottom_panel_items.write[bottom_panel_items.size() - 1]);
5679 break;
5680 }
5681 }
5682
5683 for (int i = 0; i < bottom_panel_items.size(); i++) {
5684 bottom_panel_items[i].button->disconnect("toggled", callable_mp(this, &EditorNode::_bottom_panel_switch));
5685 bottom_panel_items[i].button->connect("toggled", callable_mp(this, &EditorNode::_bottom_panel_switch).bind(i));
5686 }
5687}
5688
5689void EditorNode::remove_bottom_panel_item(Control *p_item) {
5690 for (int i = 0; i < bottom_panel_items.size(); i++) {
5691 if (bottom_panel_items[i].control == p_item) {
5692 if (p_item->is_visible_in_tree()) {
5693 _bottom_panel_switch(false, i);
5694 }
5695 bottom_panel_vb->remove_child(bottom_panel_items[i].control);
5696 bottom_panel_hb_editors->remove_child(bottom_panel_items[i].button);
5697 memdelete(bottom_panel_items[i].button);
5698 bottom_panel_items.remove_at(i);
5699 break;
5700 }
5701 }
5702
5703 for (int i = 0; i < bottom_panel_items.size(); i++) {
5704 bottom_panel_items[i].button->disconnect("toggled", callable_mp(this, &EditorNode::_bottom_panel_switch));
5705 bottom_panel_items[i].button->connect("toggled", callable_mp(this, &EditorNode::_bottom_panel_switch).bind(i));
5706 }
5707}
5708
5709void EditorNode::_bottom_panel_switch(bool p_enable, int p_idx) {
5710 if (bottom_panel_updating) {
5711 return;
5712 }
5713 ERR_FAIL_INDEX(p_idx, bottom_panel_items.size());
5714
5715 if (bottom_panel_items[p_idx].control->is_visible() == p_enable) {
5716 return;
5717 }
5718
5719 if (p_enable) {
5720 bottom_panel_updating = true;
5721
5722 for (int i = 0; i < bottom_panel_items.size(); i++) {
5723 bottom_panel_items[i].button->set_pressed(i == p_idx);
5724 bottom_panel_items[i].control->set_visible(i == p_idx);
5725 }
5726 if (EditorDebuggerNode::get_singleton() == bottom_panel_items[p_idx].control) {
5727 // This is the debug panel which uses tabs, so the top section should be smaller.
5728 bottom_panel->add_theme_style_override("panel", theme->get_stylebox(SNAME("BottomPanelDebuggerOverride"), EditorStringName(EditorStyles)));
5729 } else {
5730 bottom_panel->add_theme_style_override("panel", theme->get_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)));
5731 }
5732 center_split->set_dragger_visibility(SplitContainer::DRAGGER_VISIBLE);
5733 center_split->set_collapsed(false);
5734 if (bottom_panel_raise->is_pressed()) {
5735 top_split->hide();
5736 }
5737 bottom_panel_raise->show();
5738 } else {
5739 bottom_panel->add_theme_style_override("panel", theme->get_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)));
5740 bottom_panel_items[p_idx].button->set_pressed(false);
5741 bottom_panel_items[p_idx].control->set_visible(false);
5742 center_split->set_dragger_visibility(SplitContainer::DRAGGER_HIDDEN);
5743 center_split->set_collapsed(true);
5744 bottom_panel_raise->hide();
5745 if (bottom_panel_raise->is_pressed()) {
5746 top_split->show();
5747 }
5748 }
5749}
5750
5751void EditorNode::set_docks_visible(bool p_show) {
5752 docks_visible = p_show;
5753 _update_dock_slots_visibility(true);
5754}
5755
5756bool EditorNode::get_docks_visible() const {
5757 return docks_visible;
5758}
5759
5760void EditorNode::_toggle_distraction_free_mode() {
5761 if (EDITOR_GET("interface/editor/separate_distraction_mode")) {
5762 int screen = -1;
5763 for (int i = 0; i < editor_table.size(); i++) {
5764 if (editor_plugin_screen == editor_table[i]) {
5765 screen = i;
5766 break;
5767 }
5768 }
5769
5770 if (screen == EDITOR_SCRIPT) {
5771 script_distraction_free = !script_distraction_free;
5772 set_distraction_free_mode(script_distraction_free);
5773 } else {
5774 scene_distraction_free = !scene_distraction_free;
5775 set_distraction_free_mode(scene_distraction_free);
5776 }
5777 } else {
5778 set_distraction_free_mode(distraction_free->is_pressed());
5779 }
5780}
5781
5782void EditorNode::set_distraction_free_mode(bool p_enter) {
5783 distraction_free->set_pressed(p_enter);
5784
5785 if (p_enter) {
5786 if (docks_visible) {
5787 set_docks_visible(false);
5788 }
5789 } else {
5790 set_docks_visible(true);
5791 }
5792}
5793
5794bool EditorNode::is_distraction_free_mode_enabled() const {
5795 return distraction_free->is_pressed();
5796}
5797
5798void EditorNode::add_control_to_dock(DockSlot p_slot, Control *p_control) {
5799 ERR_FAIL_INDEX(p_slot, DOCK_SLOT_MAX);
5800 dock_slot[p_slot]->add_child(p_control);
5801 _update_dock_slots_visibility();
5802}
5803
5804void EditorNode::remove_control_from_dock(Control *p_control) {
5805 Control *dock = nullptr;
5806 for (int i = 0; i < DOCK_SLOT_MAX; i++) {
5807 if (p_control->get_parent() == dock_slot[i]) {
5808 dock = dock_slot[i];
5809 break;
5810 }
5811 }
5812
5813 ERR_FAIL_NULL_MSG(dock, "Control was not in dock.");
5814
5815 dock->remove_child(p_control);
5816 _update_dock_slots_visibility();
5817}
5818
5819Variant EditorNode::drag_resource(const Ref<Resource> &p_res, Control *p_from) {
5820 Control *drag_control = memnew(Control);
5821 TextureRect *drag_preview = memnew(TextureRect);
5822 Label *label = memnew(Label);
5823
5824 Ref<Texture2D> preview;
5825
5826 {
5827 // TODO: make proper previews
5828 Ref<ImageTexture> texture = theme->get_icon(SNAME("FileBigThumb"), EditorStringName(EditorIcons));
5829 Ref<Image> img = texture->get_image();
5830 img = img->duplicate();
5831 img->resize(48, 48); // meh
5832 preview = ImageTexture::create_from_image(img);
5833 }
5834
5835 drag_preview->set_texture(preview);
5836 drag_control->add_child(drag_preview);
5837 if (p_res->get_path().is_resource_file()) {
5838 label->set_text(p_res->get_path().get_file());
5839 } else if (!p_res->get_name().is_empty()) {
5840 label->set_text(p_res->get_name());
5841 } else {
5842 label->set_text(p_res->get_class());
5843 }
5844
5845 drag_control->add_child(label);
5846
5847 p_from->set_drag_preview(drag_control); // Wait until it enters scene.
5848
5849 label->set_position(Point2((preview->get_width() - label->get_minimum_size().width) / 2, preview->get_height()));
5850
5851 Dictionary drag_data;
5852 drag_data["type"] = "resource";
5853 drag_data["resource"] = p_res;
5854 drag_data["from"] = p_from;
5855
5856 return drag_data;
5857}
5858
5859Variant EditorNode::drag_files_and_dirs(const Vector<String> &p_paths, Control *p_from) {
5860 bool has_folder = false;
5861 bool has_file = false;
5862 for (int i = 0; i < p_paths.size(); i++) {
5863 bool is_folder = p_paths[i].ends_with("/");
5864 has_folder |= is_folder;
5865 has_file |= !is_folder;
5866 }
5867
5868 int max_rows = 6;
5869 int num_rows = p_paths.size() > max_rows ? max_rows - 1 : p_paths.size(); // Don't waste a row to say "1 more file" - list it instead.
5870 VBoxContainer *vbox = memnew(VBoxContainer);
5871 for (int i = 0; i < num_rows; i++) {
5872 HBoxContainer *hbox = memnew(HBoxContainer);
5873 TextureRect *icon = memnew(TextureRect);
5874 Label *label = memnew(Label);
5875
5876 if (p_paths[i].ends_with("/")) {
5877 label->set_text(p_paths[i].substr(0, p_paths[i].length() - 1).get_file());
5878 icon->set_texture(theme->get_icon(SNAME("Folder"), EditorStringName(EditorIcons)));
5879 } else {
5880 label->set_text(p_paths[i].get_file());
5881 icon->set_texture(theme->get_icon(SNAME("File"), EditorStringName(EditorIcons)));
5882 }
5883 icon->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
5884 icon->set_size(Size2(16, 16));
5885 hbox->add_child(icon);
5886 hbox->add_child(label);
5887 vbox->add_child(hbox);
5888 }
5889
5890 if (p_paths.size() > num_rows) {
5891 Label *label = memnew(Label);
5892 if (has_file && has_folder) {
5893 label->set_text(vformat(TTR("%d more files or folders"), p_paths.size() - num_rows));
5894 } else if (has_folder) {
5895 label->set_text(vformat(TTR("%d more folders"), p_paths.size() - num_rows));
5896 } else {
5897 label->set_text(vformat(TTR("%d more files"), p_paths.size() - num_rows));
5898 }
5899 vbox->add_child(label);
5900 }
5901 p_from->set_drag_preview(vbox); // Wait until it enters scene.
5902
5903 Dictionary drag_data;
5904 drag_data["type"] = has_folder ? "files_and_dirs" : "files";
5905 drag_data["files"] = p_paths;
5906 drag_data["from"] = p_from;
5907 return drag_data;
5908}
5909
5910void EditorNode::add_tool_menu_item(const String &p_name, const Callable &p_callback) {
5911 int idx = tool_menu->get_item_count();
5912 tool_menu->add_item(p_name, TOOLS_CUSTOM);
5913 tool_menu->set_item_metadata(idx, p_callback);
5914}
5915
5916void EditorNode::add_tool_submenu_item(const String &p_name, PopupMenu *p_submenu) {
5917 ERR_FAIL_NULL(p_submenu);
5918 ERR_FAIL_COND(p_submenu->get_parent() != nullptr);
5919
5920 tool_menu->add_child(p_submenu);
5921 tool_menu->add_submenu_item(p_name, p_submenu->get_name(), TOOLS_CUSTOM);
5922}
5923
5924void EditorNode::remove_tool_menu_item(const String &p_name) {
5925 for (int i = 0; i < tool_menu->get_item_count(); i++) {
5926 if (tool_menu->get_item_id(i) != TOOLS_CUSTOM) {
5927 continue;
5928 }
5929
5930 if (tool_menu->get_item_text(i) == p_name) {
5931 if (tool_menu->get_item_submenu(i) != "") {
5932 Node *n = tool_menu->get_node(tool_menu->get_item_submenu(i));
5933 tool_menu->remove_child(n);
5934 memdelete(n);
5935 }
5936 tool_menu->remove_item(i);
5937 tool_menu->reset_size();
5938 return;
5939 }
5940 }
5941}
5942
5943PopupMenu *EditorNode::get_export_as_menu() {
5944 return export_as_menu;
5945}
5946
5947void EditorNode::_dropped_files(const Vector<String> &p_files) {
5948 String to_path = ProjectSettings::get_singleton()->globalize_path(FileSystemDock::get_singleton()->get_current_directory());
5949
5950 _add_dropped_files_recursive(p_files, to_path);
5951
5952 EditorFileSystem::get_singleton()->scan_changes();
5953}
5954
5955void EditorNode::_add_dropped_files_recursive(const Vector<String> &p_files, String to_path) {
5956 Ref<DirAccess> dir = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
5957
5958 for (int i = 0; i < p_files.size(); i++) {
5959 String from = p_files[i];
5960 String to = to_path.path_join(from.get_file());
5961
5962 if (dir->dir_exists(from)) {
5963 Vector<String> sub_files;
5964
5965 Ref<DirAccess> sub_dir = DirAccess::open(from);
5966 sub_dir->list_dir_begin();
5967
5968 String next_file = sub_dir->get_next();
5969 while (!next_file.is_empty()) {
5970 if (next_file == "." || next_file == "..") {
5971 next_file = sub_dir->get_next();
5972 continue;
5973 }
5974
5975 sub_files.push_back(from.path_join(next_file));
5976 next_file = sub_dir->get_next();
5977 }
5978
5979 if (!sub_files.is_empty()) {
5980 dir->make_dir(to);
5981 _add_dropped_files_recursive(sub_files, to);
5982 }
5983
5984 continue;
5985 }
5986
5987 dir->copy(from, to);
5988 }
5989}
5990
5991void EditorNode::_file_access_close_error_notify(const String &p_str) {
5992 callable_mp_static(&EditorNode::_file_access_close_error_notify_impl).bind(p_str).call_deferred();
5993}
5994
5995void EditorNode::_file_access_close_error_notify_impl(const String &p_str) {
5996 add_io_error(vformat(TTR("Unable to write to file '%s', file in use, locked or lacking permissions."), p_str));
5997}
5998
5999void EditorNode::reload_scene(const String &p_path) {
6000 int scene_idx = -1;
6001 for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
6002 if (editor_data.get_scene_path(i) == p_path) {
6003 scene_idx = i;
6004 break;
6005 }
6006 }
6007
6008 int current_tab = editor_data.get_edited_scene();
6009
6010 if (scene_idx == -1) {
6011 if (get_edited_scene()) {
6012 // Scene is not open, so at it might be instantiated. We'll refresh the whole scene later.
6013 EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_current_edited_scene_history_id());
6014 }
6015 return;
6016 }
6017
6018 if (current_tab == scene_idx) {
6019 editor_data.apply_changes_in_editors();
6020 _save_editor_states(p_path);
6021 }
6022
6023 // Reload scene.
6024 _remove_scene(scene_idx, false);
6025 load_scene(p_path, true, false, true, true);
6026
6027 // Adjust index so tab is back a the previous position.
6028 editor_data.move_edited_scene_to_index(scene_idx);
6029 EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_scene_history_id(scene_idx));
6030
6031 // Recover the tab.
6032 scene_tabs->set_current_tab(current_tab);
6033}
6034
6035void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, List<Node *> &p_instance_list) {
6036 String scene_file_path = p_node->get_scene_file_path();
6037
6038 // This is going to get messy...
6039 if (p_node->get_scene_file_path() == p_instance_path) {
6040 p_instance_list.push_back(p_node);
6041 } else {
6042 Node *current_node = p_node;
6043
6044 Ref<SceneState> inherited_state = current_node->get_scene_inherited_state();
6045 while (inherited_state.is_valid()) {
6046 String inherited_path = inherited_state->get_path();
6047 if (inherited_path == p_instance_path) {
6048 p_instance_list.push_back(p_node);
6049 break;
6050 }
6051
6052 inherited_state = inherited_state->get_base_scene_state();
6053 }
6054 }
6055
6056 for (int i = 0; i < p_node->get_child_count(); i++) {
6057 Node *child = p_node->get_child(i);
6058 find_all_instances_inheriting_path_in_node(p_root, child, p_instance_path, p_instance_list);
6059 }
6060}
6061
6062void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_instance_path) {
6063 int original_edited_scene_idx = editor_data.get_edited_scene();
6064 HashMap<int, List<Node *>> edited_scene_map;
6065
6066 // Walk through each opened scene to get a global list of all instances which match
6067 // the current reimported scenes.
6068 for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
6069 if (editor_data.get_scene_path(i) != p_instance_path) {
6070 Node *edited_scene_root = editor_data.get_edited_scene_root(i);
6071
6072 if (edited_scene_root) {
6073 List<Node *> valid_nodes;
6074 find_all_instances_inheriting_path_in_node(edited_scene_root, edited_scene_root, p_instance_path, valid_nodes);
6075 if (valid_nodes.size() > 0) {
6076 edited_scene_map[i] = valid_nodes;
6077 }
6078 }
6079 }
6080 }
6081
6082 if (edited_scene_map.size() > 0) {
6083 // Reload the new instance.
6084 Error err;
6085 Ref<PackedScene> instance_scene_packed_scene = ResourceLoader::load(p_instance_path, "", ResourceFormatLoader::CACHE_MODE_IGNORE, &err);
6086 instance_scene_packed_scene->set_path(p_instance_path, true);
6087
6088 ERR_FAIL_COND(err != OK);
6089 ERR_FAIL_COND(instance_scene_packed_scene.is_null());
6090
6091 HashMap<String, Ref<PackedScene>> local_scene_cache;
6092 local_scene_cache[p_instance_path] = instance_scene_packed_scene;
6093
6094 for (const KeyValue<int, List<Node *>> &edited_scene_map_elem : edited_scene_map) {
6095 // Set the current scene.
6096 int current_scene_idx = edited_scene_map_elem.key;
6097 editor_data.set_edited_scene(current_scene_idx);
6098 Node *current_edited_scene = editor_data.get_edited_scene_root(current_scene_idx);
6099
6100 // Clear the history for this tab (should we allow history to be retained?).
6101 EditorUndoRedoManager::get_singleton()->clear_history();
6102
6103 // Update the version
6104 editor_data.is_scene_changed(current_scene_idx);
6105
6106 for (Node *original_node : edited_scene_map_elem.value) {
6107 // Walk the tree for the current node and extract relevant diff data, storing it in the modification table.
6108 // For additional nodes which are part of the current scene, they get added to the addition table.
6109 HashMap<NodePath, ModificationNodeEntry> modification_table;
6110 List<AdditiveNodeEntry> addition_list;
6111 update_diff_data_for_node(current_edited_scene, original_node, original_node, modification_table, addition_list);
6112
6113 // Disconnect all relevant connections, all connections from and persistent connections to.
6114 for (const KeyValue<NodePath, ModificationNodeEntry> &modification_table_entry : modification_table) {
6115 for (Connection conn : modification_table_entry.value.connections_from) {
6116 conn.signal.get_object()->disconnect(conn.signal.get_name(), conn.callable);
6117 }
6118 for (ConnectionWithNodePath cwnp : modification_table_entry.value.connections_to) {
6119 Connection conn = cwnp.connection;
6120 if (conn.flags & CONNECT_PERSIST) {
6121 conn.signal.get_object()->disconnect(conn.signal.get_name(), conn.callable);
6122 }
6123 }
6124 }
6125
6126 // Store all the paths for any selected nodes which are ancestors of the node we're replacing.
6127 List<NodePath> selected_node_paths;
6128 for (Node *selected_node : editor_selection->get_selected_node_list()) {
6129 if (selected_node == original_node || original_node->is_ancestor_of(selected_node)) {
6130 selected_node_paths.push_back(original_node->get_path_to(selected_node));
6131 editor_selection->remove_node(selected_node);
6132 }
6133 }
6134
6135 // Remove all nodes which were added as additional elements (they will be restored later).
6136 for (AdditiveNodeEntry additive_node_entry : addition_list) {
6137 Node *addition_node = additive_node_entry.node;
6138 addition_node->get_parent()->remove_child(addition_node);
6139 }
6140
6141 // Clear ownership of the nodes (kind of hack to workaround an issue with
6142 // replace_by when called on nodes in other tabs).
6143 List<Node *> nodes_owned_by_original_node;
6144 original_node->get_owned_by(original_node, &nodes_owned_by_original_node);
6145 for (Node *owned_node : nodes_owned_by_original_node) {
6146 owned_node->set_owner(nullptr);
6147 }
6148
6149 // Delete all the remaining node children.
6150 while (original_node->get_child_count()) {
6151 Node *child = original_node->get_child(0);
6152
6153 original_node->remove_child(child);
6154 child->queue_free();
6155 }
6156
6157 // Reset the editable instance state.
6158 bool is_editable = true;
6159 Node *owner = original_node->get_owner();
6160 if (owner) {
6161 is_editable = owner->is_editable_instance(original_node);
6162 }
6163
6164 // Load a replacement scene for the node.
6165 Ref<PackedScene> current_packed_scene;
6166 if (original_node->get_scene_file_path() == p_instance_path) {
6167 // If the node file name directly matches the scene we're replacing,
6168 // just load it since we already cached it.
6169 current_packed_scene = instance_scene_packed_scene;
6170 } else {
6171 // Otherwise, check the inheritance chain, reloading and caching any scenes
6172 // we require along the way.
6173 List<String> required_load_paths;
6174 String scene_path = original_node->get_scene_file_path();
6175 // Do we need to check if the paths are empty?
6176 if (!scene_path.is_empty()) {
6177 required_load_paths.push_front(scene_path);
6178 }
6179 Ref<SceneState> inherited_state = original_node->get_scene_inherited_state();
6180 while (inherited_state.is_valid()) {
6181 String inherited_path = inherited_state->get_path();
6182 // Do we need to check if the paths are empty?
6183 if (!inherited_path.is_empty()) {
6184 required_load_paths.push_front(inherited_path);
6185 }
6186 inherited_state = inherited_state->get_base_scene_state();
6187 }
6188
6189 // Ensure the inheritance chain is loaded in the correct order so that cache can
6190 // be properly updated.
6191 for (String path : required_load_paths) {
6192 if (!local_scene_cache.find(path)) {
6193 current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_IGNORE, &err);
6194 current_packed_scene->set_path(path, true);
6195 local_scene_cache[path] = current_packed_scene;
6196 } else {
6197 current_packed_scene = local_scene_cache[path];
6198 }
6199 }
6200 }
6201
6202 ERR_FAIL_COND(current_packed_scene.is_null());
6203
6204 // Instantiate the node.
6205 Node *instantiated_node = nullptr;
6206 if (current_packed_scene.is_valid()) {
6207 instantiated_node = current_packed_scene->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
6208 }
6209
6210 ERR_FAIL_NULL(instantiated_node);
6211
6212 bool original_node_is_displayed_folded = original_node->is_displayed_folded();
6213 bool original_node_scene_instance_load_placeholder = original_node->get_scene_instance_load_placeholder();
6214
6215 // Update the name to match
6216 instantiated_node->set_name(original_node->get_name());
6217
6218 // Is this replacing the edited root node?
6219 String original_node_file_path = original_node->get_scene_file_path();
6220
6221 if (current_edited_scene == original_node) {
6222 instantiated_node->set_scene_instance_state(original_node->get_scene_instance_state());
6223 // Fix unsaved inherited scene
6224 if (original_node_file_path.is_empty()) {
6225 Ref<SceneState> state = current_packed_scene->get_state();
6226 state->set_path(current_packed_scene->get_path());
6227 instantiated_node->set_scene_inherited_state(state);
6228 instantiated_node->set_scene_file_path(String());
6229 }
6230 editor_data.set_edited_scene_root(instantiated_node);
6231 current_edited_scene = instantiated_node;
6232
6233 if (original_node->is_inside_tree()) {
6234 SceneTreeDock::get_singleton()->set_edited_scene(current_edited_scene);
6235 original_node->get_tree()->set_edited_scene_root(instantiated_node);
6236 }
6237 }
6238
6239 // Replace the original node with the instantiated version.
6240 original_node->replace_by(instantiated_node, false);
6241
6242 // Mark the old node for deletion.
6243 original_node->queue_free();
6244
6245 // Restore the folded and placeholder state from the original node.
6246 instantiated_node->set_display_folded(original_node_is_displayed_folded);
6247 instantiated_node->set_scene_instance_load_placeholder(original_node_scene_instance_load_placeholder);
6248
6249 if (owner) {
6250 Ref<SceneState> ss_inst = owner->get_scene_instance_state();
6251 if (ss_inst.is_valid()) {
6252 ss_inst->update_instance_resource(p_instance_path, current_packed_scene);
6253 }
6254
6255 owner->set_editable_instance(instantiated_node, is_editable);
6256 }
6257
6258 // Attempt to re-add all the additional nodes.
6259 for (AdditiveNodeEntry additive_node_entry : addition_list) {
6260 Node *parent_node = instantiated_node->get_node_or_null(additive_node_entry.parent);
6261
6262 if (!parent_node) {
6263 parent_node = current_edited_scene;
6264 }
6265
6266 parent_node->add_child(additive_node_entry.node);
6267 parent_node->move_child(additive_node_entry.node, additive_node_entry.index);
6268 // If the additive node's owner was the node which got replaced, update it.
6269 if (additive_node_entry.owner == original_node) {
6270 additive_node_entry.owner = instantiated_node;
6271 }
6272
6273 additive_node_entry.node->set_owner(additive_node_entry.owner);
6274
6275 // If the parent node was lost, attempt to restore the original global transform.
6276 {
6277 Node2D *node_2d = Object::cast_to<Node2D>(additive_node_entry.node);
6278 if (node_2d) {
6279 node_2d->set_transform(additive_node_entry.transform_2d);
6280 }
6281
6282 Node3D *node_3d = Object::cast_to<Node3D>(additive_node_entry.node);
6283 if (node_3d) {
6284 node_3d->set_transform(additive_node_entry.transform_3d);
6285 }
6286 }
6287
6288 // Restore the ownership of its ancestors
6289 for (KeyValue<Node *, Node *> &E : additive_node_entry.ownership_table) {
6290 Node *current_ancestor = E.key;
6291 Node *ancestor_owner = E.value;
6292
6293 if (ancestor_owner == original_node) {
6294 ancestor_owner = instantiated_node;
6295 }
6296
6297 current_ancestor->set_owner(ancestor_owner);
6298 }
6299 }
6300
6301 // Restore the selection.
6302 if (selected_node_paths.size()) {
6303 for (NodePath selected_node_path : selected_node_paths) {
6304 Node *selected_node = instantiated_node->get_node_or_null(selected_node_path);
6305 if (selected_node) {
6306 editor_selection->add_node(selected_node);
6307 }
6308 }
6309 editor_selection->update();
6310 }
6311
6312 // Attempt to restore the modified properties and signals for the instantitated node and all its owned children.
6313 for (KeyValue<NodePath, ModificationNodeEntry> &E : modification_table) {
6314 NodePath new_current_path = E.key;
6315 Node *modifiable_node = instantiated_node->get_node_or_null(new_current_path);
6316
6317 if (modifiable_node) {
6318 // Get properties for this node.
6319 List<PropertyInfo> pinfo;
6320 modifiable_node->get_property_list(&pinfo);
6321
6322 // Get names of all valid property names (TODO: make this more efficient).
6323 List<String> property_names;
6324 for (const PropertyInfo &E2 : pinfo) {
6325 if (E2.usage & PROPERTY_USAGE_STORAGE) {
6326 property_names.push_back(E2.name);
6327 }
6328 }
6329
6330 // Restore the modified properties for this node.
6331 for (const KeyValue<StringName, Variant> &E2 : E.value.property_table) {
6332 if (property_names.find(E2.key)) {
6333 modifiable_node->set(E2.key, E2.value);
6334 }
6335 }
6336 // Restore the connections to other nodes.
6337 for (const ConnectionWithNodePath &E2 : E.value.connections_to) {
6338 Connection conn = E2.connection;
6339
6340 // Get the node the callable is targeting.
6341 Node *target_node = cast_to<Node>(conn.callable.get_object());
6342
6343 // If the callable object no longer exists or is marked for deletion,
6344 // attempt to reaccquire the closest match by using the node path
6345 // we saved earlier.
6346 if (!target_node || !target_node->is_queued_for_deletion()) {
6347 target_node = modifiable_node->get_node_or_null(E2.node_path);
6348 }
6349
6350 if (target_node) {
6351 // Reconstruct the callable.
6352 Callable new_callable = Callable(target_node, conn.callable.get_method());
6353
6354 if (!modifiable_node->is_connected(conn.signal.get_name(), new_callable)) {
6355 ERR_FAIL_COND(modifiable_node->connect(conn.signal.get_name(), new_callable, conn.flags) != OK);
6356 }
6357 }
6358 }
6359
6360 // Restore the connections from other nodes.
6361 for (const Connection &E2 : E.value.connections_from) {
6362 Connection conn = E2;
6363
6364 bool valid = modifiable_node->has_method(conn.callable.get_method()) || Ref<Script>(modifiable_node->get_script()).is_null() || Ref<Script>(modifiable_node->get_script())->has_method(conn.callable.get_method());
6365 ERR_CONTINUE_MSG(!valid, vformat("Attempt to connect signal '%s.%s' to nonexistent method '%s.%s'.", conn.signal.get_object()->get_class(), conn.signal.get_name(), conn.callable.get_object()->get_class(), conn.callable.get_method()));
6366
6367 // Get the object which the signal is connected from.
6368 Object *source_object = conn.signal.get_object();
6369
6370 if (source_object) {
6371 ERR_FAIL_COND(source_object->connect(conn.signal.get_name(), Callable(modifiable_node, conn.callable.get_method()), conn.flags) != OK);
6372 }
6373 }
6374
6375 // Re-add the groups.
6376 for (const Node::GroupInfo &E2 : E.value.groups) {
6377 modifiable_node->add_to_group(E2.name, E2.persistent);
6378 }
6379 }
6380 }
6381 }
6382 // Cleanup the history of the changes.
6383 editor_history.cleanup_history();
6384
6385 current_edited_scene->propagate_notification(NOTIFICATION_NODE_RECACHE_REQUESTED);
6386 }
6387 edited_scene_map.clear();
6388 }
6389 editor_data.set_edited_scene(original_edited_scene_idx);
6390
6391 _edit_current();
6392}
6393
6394int EditorNode::plugin_init_callback_count = 0;
6395
6396void EditorNode::add_plugin_init_callback(EditorPluginInitializeCallback p_callback) {
6397 ERR_FAIL_COND(plugin_init_callback_count == MAX_INIT_CALLBACKS);
6398
6399 plugin_init_callbacks[plugin_init_callback_count++] = p_callback;
6400}
6401
6402EditorPluginInitializeCallback EditorNode::plugin_init_callbacks[EditorNode::MAX_INIT_CALLBACKS];
6403
6404int EditorNode::build_callback_count = 0;
6405
6406void EditorNode::add_build_callback(EditorBuildCallback p_callback) {
6407 ERR_FAIL_COND(build_callback_count == MAX_INIT_CALLBACKS);
6408
6409 build_callbacks[build_callback_count++] = p_callback;
6410}
6411
6412EditorBuildCallback EditorNode::build_callbacks[EditorNode::MAX_BUILD_CALLBACKS];
6413
6414bool EditorNode::call_build() {
6415 bool builds_successful = true;
6416
6417 for (int i = 0; i < build_callback_count && builds_successful; i++) {
6418 if (!build_callbacks[i]()) {
6419 ERR_PRINT("A Godot Engine build callback failed.");
6420 builds_successful = false;
6421 }
6422 }
6423
6424 if (builds_successful && !editor_data.call_build()) {
6425 ERR_PRINT("An EditorPlugin build callback failed.");
6426 builds_successful = false;
6427 }
6428
6429 return builds_successful;
6430}
6431
6432void EditorNode::_inherit_imported(const String &p_action) {
6433 open_imported->hide();
6434 load_scene(open_import_request, true, true);
6435}
6436
6437void EditorNode::_open_imported() {
6438 load_scene(open_import_request, true, false, true, true);
6439}
6440
6441void EditorNode::dim_editor(bool p_dimming) {
6442 dimmed = p_dimming;
6443 gui_base->set_modulate(p_dimming ? Color(0.5, 0.5, 0.5) : Color(1, 1, 1));
6444}
6445
6446bool EditorNode::is_editor_dimmed() const {
6447 return dimmed;
6448}
6449
6450void EditorNode::open_export_template_manager() {
6451 export_template_manager->popup_manager();
6452}
6453
6454void EditorNode::add_resource_conversion_plugin(const Ref<EditorResourceConversionPlugin> &p_plugin) {
6455 resource_conversion_plugins.push_back(p_plugin);
6456}
6457
6458void EditorNode::remove_resource_conversion_plugin(const Ref<EditorResourceConversionPlugin> &p_plugin) {
6459 resource_conversion_plugins.erase(p_plugin);
6460}
6461
6462Vector<Ref<EditorResourceConversionPlugin>> EditorNode::find_resource_conversion_plugin(const Ref<Resource> &p_for_resource) {
6463 Vector<Ref<EditorResourceConversionPlugin>> ret;
6464
6465 for (int i = 0; i < resource_conversion_plugins.size(); i++) {
6466 if (resource_conversion_plugins[i].is_valid() && resource_conversion_plugins[i]->handles(p_for_resource)) {
6467 ret.push_back(resource_conversion_plugins[i]);
6468 }
6469 }
6470
6471 return ret;
6472}
6473
6474void EditorNode::_bottom_panel_raise_toggled(bool p_pressed) {
6475 top_split->set_visible(!p_pressed);
6476}
6477
6478void EditorNode::_update_renderer_color() {
6479 String rendering_method = renderer->get_selected_metadata();
6480
6481 if (rendering_method == "forward_plus") {
6482 renderer->add_theme_color_override("font_color", Color::hex(0x5d8c3fff));
6483 }
6484 if (rendering_method == "mobile") {
6485 renderer->add_theme_color_override("font_color", Color::hex(0xa5557dff));
6486 }
6487 if (rendering_method == "gl_compatibility") {
6488 renderer->add_theme_color_override("font_color", Color::hex(0x5586a4ff));
6489 }
6490}
6491
6492void EditorNode::_renderer_selected(int p_which) {
6493 String rendering_method = renderer->get_item_metadata(p_which);
6494
6495 String current_renderer = GLOBAL_GET("rendering/renderer/rendering_method");
6496
6497 if (rendering_method == current_renderer) {
6498 return;
6499 }
6500
6501 renderer_request = rendering_method;
6502 video_restart_dialog->popup_centered();
6503 renderer->select(renderer_current);
6504 _update_renderer_color();
6505}
6506
6507void EditorNode::_resource_saved(Ref<Resource> p_resource, const String &p_path) {
6508 if (singleton->saving_resources_in_path.has(p_resource)) {
6509 // This is going to be handled by save_resource_in_path when the time is right.
6510 return;
6511 }
6512
6513 if (EditorFileSystem::get_singleton()) {
6514 EditorFileSystem::get_singleton()->update_file(p_path);
6515 }
6516
6517 singleton->editor_folding.save_resource_folding(p_resource, p_path);
6518}
6519
6520void EditorNode::_resource_loaded(Ref<Resource> p_resource, const String &p_path) {
6521 singleton->editor_folding.load_resource_folding(p_resource, p_path);
6522}
6523
6524void EditorNode::_feature_profile_changed() {
6525 Ref<EditorFeatureProfile> profile = feature_profile_manager->get_current_profile();
6526 TabContainer *import_tabs = cast_to<TabContainer>(ImportDock::get_singleton()->get_parent());
6527 TabContainer *node_tabs = cast_to<TabContainer>(NodeDock::get_singleton()->get_parent());
6528 TabContainer *fs_tabs = cast_to<TabContainer>(FileSystemDock::get_singleton()->get_parent());
6529 TabContainer *history_tabs = cast_to<TabContainer>(history_dock->get_parent());
6530 if (profile.is_valid()) {
6531 node_tabs->set_tab_hidden(node_tabs->get_tab_idx_from_control(NodeDock::get_singleton()), profile->is_feature_disabled(EditorFeatureProfile::FEATURE_NODE_DOCK));
6532 // The Import dock is useless without the FileSystem dock. Ensure the configuration is valid.
6533 bool fs_dock_disabled = profile->is_feature_disabled(EditorFeatureProfile::FEATURE_FILESYSTEM_DOCK);
6534 fs_tabs->set_tab_hidden(fs_tabs->get_tab_idx_from_control(FileSystemDock::get_singleton()), fs_dock_disabled);
6535 import_tabs->set_tab_hidden(import_tabs->get_tab_idx_from_control(ImportDock::get_singleton()), fs_dock_disabled || profile->is_feature_disabled(EditorFeatureProfile::FEATURE_IMPORT_DOCK));
6536 history_tabs->set_tab_hidden(history_tabs->get_tab_idx_from_control(history_dock), profile->is_feature_disabled(EditorFeatureProfile::FEATURE_HISTORY_DOCK));
6537
6538 main_editor_buttons[EDITOR_3D]->set_visible(!profile->is_feature_disabled(EditorFeatureProfile::FEATURE_3D));
6539 main_editor_buttons[EDITOR_SCRIPT]->set_visible(!profile->is_feature_disabled(EditorFeatureProfile::FEATURE_SCRIPT));
6540 if (AssetLibraryEditorPlugin::is_available()) {
6541 main_editor_buttons[EDITOR_ASSETLIB]->set_visible(!profile->is_feature_disabled(EditorFeatureProfile::FEATURE_ASSET_LIB));
6542 }
6543 if ((profile->is_feature_disabled(EditorFeatureProfile::FEATURE_3D) && singleton->main_editor_buttons[EDITOR_3D]->is_pressed()) ||
6544 (profile->is_feature_disabled(EditorFeatureProfile::FEATURE_SCRIPT) && singleton->main_editor_buttons[EDITOR_SCRIPT]->is_pressed()) ||
6545 (AssetLibraryEditorPlugin::is_available() && profile->is_feature_disabled(EditorFeatureProfile::FEATURE_ASSET_LIB) && singleton->main_editor_buttons[EDITOR_ASSETLIB]->is_pressed())) {
6546 editor_select(EDITOR_2D);
6547 }
6548 } else {
6549 import_tabs->set_tab_hidden(import_tabs->get_tab_idx_from_control(ImportDock::get_singleton()), false);
6550 node_tabs->set_tab_hidden(node_tabs->get_tab_idx_from_control(NodeDock::get_singleton()), false);
6551 fs_tabs->set_tab_hidden(fs_tabs->get_tab_idx_from_control(FileSystemDock::get_singleton()), false);
6552 history_tabs->set_tab_hidden(history_tabs->get_tab_idx_from_control(history_dock), false);
6553 main_editor_buttons[EDITOR_3D]->set_visible(true);
6554 main_editor_buttons[EDITOR_SCRIPT]->set_visible(true);
6555 if (AssetLibraryEditorPlugin::is_available()) {
6556 main_editor_buttons[EDITOR_ASSETLIB]->set_visible(true);
6557 }
6558 }
6559
6560 _update_dock_slots_visibility();
6561}
6562
6563void EditorNode::_bind_methods() {
6564 ClassDB::bind_method(D_METHOD("push_item", "object", "property", "inspector_only"), &EditorNode::push_item, DEFVAL(""), DEFVAL(false));
6565
6566 ClassDB::bind_method("set_edited_scene", &EditorNode::set_edited_scene);
6567 ClassDB::bind_method("open_request", &EditorNode::open_request);
6568 ClassDB::bind_method("edit_foreign_resource", &EditorNode::edit_foreign_resource);
6569 ClassDB::bind_method("is_resource_read_only", &EditorNode::is_resource_read_only);
6570
6571 ClassDB::bind_method("stop_child_process", &EditorNode::stop_child_process);
6572
6573 ClassDB::bind_method("_set_main_scene_state", &EditorNode::_set_main_scene_state);
6574 ClassDB::bind_method("_update_recent_scenes", &EditorNode::_update_recent_scenes);
6575
6576 ADD_SIGNAL(MethodInfo("request_help_search"));
6577 ADD_SIGNAL(MethodInfo("script_add_function_request", PropertyInfo(Variant::OBJECT, "obj"), PropertyInfo(Variant::STRING, "function"), PropertyInfo(Variant::PACKED_STRING_ARRAY, "args")));
6578 ADD_SIGNAL(MethodInfo("resource_saved", PropertyInfo(Variant::OBJECT, "obj")));
6579 ADD_SIGNAL(MethodInfo("scene_saved", PropertyInfo(Variant::STRING, "path")));
6580 ADD_SIGNAL(MethodInfo("scene_changed"));
6581 ADD_SIGNAL(MethodInfo("scene_closed", PropertyInfo(Variant::STRING, "path")));
6582}
6583
6584static Node *_resource_get_edited_scene() {
6585 return EditorNode::get_singleton()->get_edited_scene();
6586}
6587
6588void EditorNode::_print_handler(void *p_this, const String &p_string, bool p_error, bool p_rich) {
6589 callable_mp_static(&EditorNode::_print_handler_impl).bind(p_string, p_error, p_rich).call_deferred();
6590}
6591
6592void EditorNode::_print_handler_impl(const String &p_string, bool p_error, bool p_rich) {
6593 if (!singleton) {
6594 return;
6595 }
6596 if (p_error) {
6597 singleton->log->add_message(p_string, EditorLog::MSG_TYPE_ERROR);
6598 } else if (p_rich) {
6599 singleton->log->add_message(p_string, EditorLog::MSG_TYPE_STD_RICH);
6600 } else {
6601 singleton->log->add_message(p_string, EditorLog::MSG_TYPE_STD);
6602 }
6603}
6604
6605static void _execute_thread(void *p_ud) {
6606 EditorNode::ExecuteThreadArgs *eta = (EditorNode::ExecuteThreadArgs *)p_ud;
6607 Error err = OS::get_singleton()->execute(eta->path, eta->args, &eta->output, &eta->exitcode, true, &eta->execute_output_mutex);
6608 print_verbose("Thread exit status: " + itos(eta->exitcode));
6609 if (err != OK) {
6610 eta->exitcode = err;
6611 }
6612
6613 eta->done.set();
6614}
6615
6616int EditorNode::execute_and_show_output(const String &p_title, const String &p_path, const List<String> &p_arguments, bool p_close_on_ok, bool p_close_on_errors) {
6617 if (execute_output_dialog) {
6618 execute_output_dialog->set_title(p_title);
6619 execute_output_dialog->get_ok_button()->set_disabled(true);
6620 execute_outputs->clear();
6621 execute_outputs->set_scroll_follow(true);
6622 EditorInterface::get_singleton()->popup_dialog_centered_ratio(execute_output_dialog);
6623 }
6624
6625 ExecuteThreadArgs eta;
6626 eta.path = p_path;
6627 eta.args = p_arguments;
6628 eta.exitcode = 255;
6629
6630 int prev_len = 0;
6631
6632 eta.execute_output_thread.start(_execute_thread, &eta);
6633
6634 while (!eta.done.is_set()) {
6635 {
6636 MutexLock lock(eta.execute_output_mutex);
6637 if (prev_len != eta.output.length()) {
6638 String to_add = eta.output.substr(prev_len, eta.output.length());
6639 prev_len = eta.output.length();
6640 execute_outputs->add_text(to_add);
6641 DisplayServer::get_singleton()->process_events(); // Get rid of pending events.
6642 Main::iteration();
6643 }
6644 }
6645 OS::get_singleton()->delay_usec(1000);
6646 }
6647
6648 eta.execute_output_thread.wait_to_finish();
6649 execute_outputs->add_text("\nExit Code: " + itos(eta.exitcode));
6650
6651 if (execute_output_dialog) {
6652 if (p_close_on_errors && eta.exitcode != 0) {
6653 execute_output_dialog->hide();
6654 }
6655 if (p_close_on_ok && eta.exitcode == 0) {
6656 execute_output_dialog->hide();
6657 }
6658
6659 execute_output_dialog->get_ok_button()->set_disabled(false);
6660 }
6661
6662 return eta.exitcode;
6663}
6664
6665EditorNode::EditorNode() {
6666 EditorPropertyNameProcessor *epnp = memnew(EditorPropertyNameProcessor);
6667 add_child(epnp);
6668
6669 PortableCompressedTexture2D::set_keep_all_compressed_buffers(true);
6670 Input::get_singleton()->set_use_accumulated_input(true);
6671 Resource::_get_local_scene_func = _resource_get_edited_scene;
6672
6673 RenderingServer::get_singleton()->set_debug_generate_wireframes(true);
6674
6675 AudioServer::get_singleton()->set_enable_tagging_used_audio_streams(true);
6676
6677 // No navigation server by default if in editor.
6678 if (NavigationServer3D::get_singleton()->get_debug_enabled()) {
6679 NavigationServer3D::get_singleton()->set_active(true);
6680 } else {
6681 NavigationServer3D::get_singleton()->set_active(false);
6682 }
6683
6684 // No physics by default if in editor.
6685 PhysicsServer3D::get_singleton()->set_active(false);
6686 PhysicsServer2D::get_singleton()->set_active(false);
6687
6688 // No scripting by default if in editor.
6689 ScriptServer::set_scripting_enabled(false);
6690
6691 EditorHelp::generate_doc();
6692 SceneState::set_disable_placeholders(true);
6693 ResourceLoader::clear_translation_remaps(); // Using no remaps if in editor.
6694 ResourceLoader::clear_path_remaps();
6695 ResourceLoader::set_create_missing_resources_if_class_unavailable(true);
6696
6697 Input *id = Input::get_singleton();
6698
6699 if (id) {
6700 if (!DisplayServer::get_singleton()->is_touchscreen_available() && Input::get_singleton()) {
6701 // Only if no touchscreen ui hint, disable emulation just in case.
6702 id->set_emulate_touch_from_mouse(false);
6703 }
6704 DisplayServer::get_singleton()->cursor_set_custom_image(Ref<Resource>());
6705 }
6706
6707 DEV_ASSERT(!singleton);
6708 singleton = this;
6709
6710 EditorUndoRedoManager::get_singleton()->connect("version_changed", callable_mp(this, &EditorNode::_update_undo_redo_allowed));
6711 EditorUndoRedoManager::get_singleton()->connect("history_changed", callable_mp(this, &EditorNode::_update_undo_redo_allowed));
6712 ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &EditorNode::_update_from_settings));
6713
6714 TranslationServer::get_singleton()->set_enabled(false);
6715 // Load settings.
6716 if (!EditorSettings::get_singleton()) {
6717 EditorSettings::create();
6718 }
6719
6720 FileAccess::set_backup_save(EDITOR_GET("filesystem/on_save/safe_save_on_backup_then_rename"));
6721
6722 {
6723 int display_scale = EDITOR_GET("interface/editor/display_scale");
6724
6725 switch (display_scale) {
6726 case 0:
6727 // Try applying a suitable display scale automatically.
6728 EditorScale::set_scale(EditorSettings::get_singleton()->get_auto_display_scale());
6729 break;
6730 case 1:
6731 EditorScale::set_scale(0.75);
6732 break;
6733 case 2:
6734 EditorScale::set_scale(1.0);
6735 break;
6736 case 3:
6737 EditorScale::set_scale(1.25);
6738 break;
6739 case 4:
6740 EditorScale::set_scale(1.5);
6741 break;
6742 case 5:
6743 EditorScale::set_scale(1.75);
6744 break;
6745 case 6:
6746 EditorScale::set_scale(2.0);
6747 break;
6748 default:
6749 EditorScale::set_scale(EDITOR_GET("interface/editor/custom_display_scale"));
6750 break;
6751 }
6752 }
6753
6754 // Define a minimum window size to prevent UI elements from overlapping or being cut off.
6755 Window *w = Object::cast_to<Window>(SceneTree::get_singleton()->get_root());
6756 if (w) {
6757 w->set_min_size(Size2(1024, 600) * EDSCALE);
6758 }
6759
6760 EditorFileDialog::set_default_show_hidden_files(EDITOR_GET("filesystem/file_dialog/show_hidden_files"));
6761 EditorFileDialog::set_default_display_mode((EditorFileDialog::DisplayMode)EDITOR_GET("filesystem/file_dialog/display_mode").operator int());
6762
6763 int swap_cancel_ok = EDITOR_GET("interface/editor/accept_dialog_cancel_ok_buttons");
6764 if (swap_cancel_ok != 0) { // 0 is auto, set in register_scene based on DisplayServer.
6765 // Swap on means OK first.
6766 AcceptDialog::set_swap_cancel_ok(swap_cancel_ok == 2);
6767 }
6768
6769 ResourceLoader::set_abort_on_missing_resources(false);
6770 ResourceLoader::set_error_notify_func(&EditorNode::add_io_error);
6771 ResourceLoader::set_dependency_error_notify_func(&EditorNode::_dependency_error_report);
6772
6773 SceneState::set_instantiation_warning_notify_func([](const String &p_warning) {
6774 add_io_warning(p_warning);
6775 callable_mp(EditorInterface::get_singleton(), &EditorInterface::mark_scene_as_unsaved).call_deferred();
6776 });
6777
6778 {
6779 // Register importers at the beginning, so dialogs are created with the right extensions.
6780 Ref<ResourceImporterTexture> import_texture = memnew(ResourceImporterTexture(true));
6781 ResourceFormatImporter::get_singleton()->add_importer(import_texture);
6782
6783 Ref<ResourceImporterLayeredTexture> import_cubemap;
6784 import_cubemap.instantiate();
6785 import_cubemap->set_mode(ResourceImporterLayeredTexture::MODE_CUBEMAP);
6786 ResourceFormatImporter::get_singleton()->add_importer(import_cubemap);
6787
6788 Ref<ResourceImporterLayeredTexture> import_array;
6789 import_array.instantiate();
6790 import_array->set_mode(ResourceImporterLayeredTexture::MODE_2D_ARRAY);
6791 ResourceFormatImporter::get_singleton()->add_importer(import_array);
6792
6793 Ref<ResourceImporterLayeredTexture> import_cubemap_array;
6794 import_cubemap_array.instantiate();
6795 import_cubemap_array->set_mode(ResourceImporterLayeredTexture::MODE_CUBEMAP_ARRAY);
6796 ResourceFormatImporter::get_singleton()->add_importer(import_cubemap_array);
6797
6798 Ref<ResourceImporterLayeredTexture> import_3d = memnew(ResourceImporterLayeredTexture(true));
6799 import_3d->set_mode(ResourceImporterLayeredTexture::MODE_3D);
6800 ResourceFormatImporter::get_singleton()->add_importer(import_3d);
6801
6802 Ref<ResourceImporterImage> import_image;
6803 import_image.instantiate();
6804 ResourceFormatImporter::get_singleton()->add_importer(import_image);
6805
6806 Ref<ResourceImporterTextureAtlas> import_texture_atlas;
6807 import_texture_atlas.instantiate();
6808 ResourceFormatImporter::get_singleton()->add_importer(import_texture_atlas);
6809
6810 Ref<ResourceImporterDynamicFont> import_font_data_dynamic;
6811 import_font_data_dynamic.instantiate();
6812 ResourceFormatImporter::get_singleton()->add_importer(import_font_data_dynamic);
6813
6814 Ref<ResourceImporterBMFont> import_font_data_bmfont;
6815 import_font_data_bmfont.instantiate();
6816 ResourceFormatImporter::get_singleton()->add_importer(import_font_data_bmfont);
6817
6818 Ref<ResourceImporterImageFont> import_font_data_image;
6819 import_font_data_image.instantiate();
6820 ResourceFormatImporter::get_singleton()->add_importer(import_font_data_image);
6821
6822 Ref<ResourceImporterCSVTranslation> import_csv_translation;
6823 import_csv_translation.instantiate();
6824 ResourceFormatImporter::get_singleton()->add_importer(import_csv_translation);
6825
6826 Ref<ResourceImporterWAV> import_wav;
6827 import_wav.instantiate();
6828 ResourceFormatImporter::get_singleton()->add_importer(import_wav);
6829
6830 Ref<ResourceImporterOBJ> import_obj;
6831 import_obj.instantiate();
6832 ResourceFormatImporter::get_singleton()->add_importer(import_obj);
6833
6834 Ref<ResourceImporterShaderFile> import_shader_file;
6835 import_shader_file.instantiate();
6836 ResourceFormatImporter::get_singleton()->add_importer(import_shader_file);
6837
6838 Ref<ResourceImporterScene> import_scene = memnew(ResourceImporterScene(false, true));
6839 ResourceFormatImporter::get_singleton()->add_importer(import_scene);
6840
6841 Ref<ResourceImporterScene> import_animation = memnew(ResourceImporterScene(true, true));
6842 ResourceFormatImporter::get_singleton()->add_importer(import_animation);
6843
6844 {
6845 Ref<EditorSceneFormatImporterCollada> import_collada;
6846 import_collada.instantiate();
6847 ResourceImporterScene::add_importer(import_collada);
6848
6849 Ref<EditorOBJImporter> import_obj2;
6850 import_obj2.instantiate();
6851 ResourceImporterScene::add_importer(import_obj2);
6852
6853 Ref<EditorSceneFormatImporterESCN> import_escn;
6854 import_escn.instantiate();
6855 ResourceImporterScene::add_importer(import_escn);
6856 }
6857
6858 Ref<ResourceImporterBitMap> import_bitmap;
6859 import_bitmap.instantiate();
6860 ResourceFormatImporter::get_singleton()->add_importer(import_bitmap);
6861 }
6862
6863 {
6864 Ref<EditorInspectorDefaultPlugin> eidp;
6865 eidp.instantiate();
6866 EditorInspector::add_inspector_plugin(eidp);
6867
6868 Ref<EditorInspectorRootMotionPlugin> rmp;
6869 rmp.instantiate();
6870 EditorInspector::add_inspector_plugin(rmp);
6871
6872 Ref<EditorInspectorVisualShaderModePlugin> smp;
6873 smp.instantiate();
6874 EditorInspector::add_inspector_plugin(smp);
6875 }
6876
6877 editor_selection = memnew(EditorSelection);
6878
6879 EditorFileSystem *efs = memnew(EditorFileSystem);
6880 add_child(efs);
6881
6882 // Used for previews.
6883 FileDialog::get_icon_func = _file_dialog_get_icon;
6884 FileDialog::register_func = _file_dialog_register;
6885 FileDialog::unregister_func = _file_dialog_unregister;
6886
6887 EditorFileDialog::get_icon_func = _file_dialog_get_icon;
6888 EditorFileDialog::register_func = _editor_file_dialog_register;
6889 EditorFileDialog::unregister_func = _editor_file_dialog_unregister;
6890
6891 editor_export = memnew(EditorExport);
6892 add_child(editor_export);
6893
6894 // Exporters might need the theme.
6895 EditorColorMap::create();
6896 theme = create_custom_theme();
6897 DisplayServer::set_early_window_clear_color_override(true, theme->get_color(SNAME("background"), EditorStringName(Editor)));
6898
6899 register_exporters();
6900
6901 EDITOR_DEF("interface/editor/save_on_focus_loss", false);
6902 EDITOR_DEF("interface/editor/show_update_spinner", false);
6903 EDITOR_DEF("interface/editor/update_continuously", false);
6904 EDITOR_DEF("interface/editor/localize_settings", true);
6905 EDITOR_DEF_RST("interface/scene_tabs/restore_scenes_on_load", true);
6906 EDITOR_DEF_RST("interface/inspector/default_property_name_style", EditorPropertyNameProcessor::STYLE_CAPITALIZED);
6907 EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "interface/inspector/default_property_name_style", PROPERTY_HINT_ENUM, "Raw,Capitalized,Localized"));
6908 EDITOR_DEF_RST("interface/inspector/default_float_step", 0.001);
6909 // The lowest value is equal to the minimum float step for 32-bit floats.
6910 // The step must be set manually, as changing this setting should not change the step here.
6911 EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::FLOAT, "interface/inspector/default_float_step", PROPERTY_HINT_RANGE, "0.0000001,1,0.0000001"));
6912 EDITOR_DEF_RST("interface/inspector/disable_folding", false);
6913 EDITOR_DEF_RST("interface/inspector/auto_unfold_foreign_scenes", true);
6914 EDITOR_DEF("interface/inspector/horizontal_vector2_editing", false);
6915 EDITOR_DEF("interface/inspector/horizontal_vector_types_editing", true);
6916 EDITOR_DEF("interface/inspector/open_resources_in_current_inspector", true);
6917
6918 PackedStringArray open_in_new_inspector_defaults;
6919 // Required for the script editor to work.
6920 open_in_new_inspector_defaults.push_back("Script");
6921 // Required for the GridMap editor to work.
6922 open_in_new_inspector_defaults.push_back("MeshLibrary");
6923 EDITOR_DEF("interface/inspector/resources_to_open_in_new_inspector", open_in_new_inspector_defaults);
6924
6925 EDITOR_DEF("interface/inspector/default_color_picker_mode", 0);
6926 EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "interface/inspector/default_color_picker_mode", PROPERTY_HINT_ENUM, "RGB,HSV,RAW,OKHSL", PROPERTY_USAGE_DEFAULT));
6927 EDITOR_DEF("interface/inspector/default_color_picker_shape", (int32_t)ColorPicker::SHAPE_OKHSL_CIRCLE);
6928 EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "interface/inspector/default_color_picker_shape", PROPERTY_HINT_ENUM, "HSV Rectangle,HSV Rectangle Wheel,VHS Circle,OKHSL Circle", PROPERTY_USAGE_DEFAULT));
6929
6930 ED_SHORTCUT("canvas_item_editor/pan_view", TTR("Pan View"), Key::SPACE);
6931
6932 const Vector<String> textfile_ext = ((String)(EDITOR_GET("docks/filesystem/textfile_extensions"))).split(",", false);
6933 for (const String &E : textfile_ext) {
6934 textfile_extensions.insert(E);
6935 }
6936
6937 resource_preview = memnew(EditorResourcePreview);
6938 add_child(resource_preview);
6939 progress_dialog = memnew(ProgressDialog);
6940 progress_dialog->set_unparent_when_invisible(true);
6941
6942 gui_base = memnew(Panel);
6943 add_child(gui_base);
6944
6945 // Take up all screen.
6946 gui_base->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
6947 gui_base->set_anchor(SIDE_RIGHT, Control::ANCHOR_END);
6948 gui_base->set_anchor(SIDE_BOTTOM, Control::ANCHOR_END);
6949 gui_base->set_end(Point2(0, 0));
6950
6951 main_vbox = memnew(VBoxContainer);
6952 gui_base->add_child(main_vbox);
6953
6954 title_bar = memnew(EditorTitleBar);
6955 main_vbox->add_child(title_bar);
6956
6957 left_l_hsplit = memnew(HSplitContainer);
6958 main_vbox->add_child(left_l_hsplit);
6959
6960 left_l_hsplit->set_v_size_flags(Control::SIZE_EXPAND_FILL);
6961
6962 left_l_vsplit = memnew(VSplitContainer);
6963 left_l_hsplit->add_child(left_l_vsplit);
6964 dock_slot[DOCK_SLOT_LEFT_UL] = memnew(TabContainer);
6965 left_l_vsplit->add_child(dock_slot[DOCK_SLOT_LEFT_UL]);
6966 dock_slot[DOCK_SLOT_LEFT_BL] = memnew(TabContainer);
6967 left_l_vsplit->add_child(dock_slot[DOCK_SLOT_LEFT_BL]);
6968
6969 left_r_hsplit = memnew(HSplitContainer);
6970 left_l_hsplit->add_child(left_r_hsplit);
6971 left_r_vsplit = memnew(VSplitContainer);
6972 left_r_hsplit->add_child(left_r_vsplit);
6973 dock_slot[DOCK_SLOT_LEFT_UR] = memnew(TabContainer);
6974 left_r_vsplit->add_child(dock_slot[DOCK_SLOT_LEFT_UR]);
6975 dock_slot[DOCK_SLOT_LEFT_BR] = memnew(TabContainer);
6976 left_r_vsplit->add_child(dock_slot[DOCK_SLOT_LEFT_BR]);
6977
6978 main_hsplit = memnew(HSplitContainer);
6979 left_r_hsplit->add_child(main_hsplit);
6980 VBoxContainer *center_vb = memnew(VBoxContainer);
6981 main_hsplit->add_child(center_vb);
6982 center_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
6983
6984 center_split = memnew(VSplitContainer);
6985 center_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);
6986 center_split->set_collapsed(false);
6987 center_vb->add_child(center_split);
6988
6989 right_hsplit = memnew(HSplitContainer);
6990 main_hsplit->add_child(right_hsplit);
6991
6992 right_l_vsplit = memnew(VSplitContainer);
6993 right_hsplit->add_child(right_l_vsplit);
6994 dock_slot[DOCK_SLOT_RIGHT_UL] = memnew(TabContainer);
6995 right_l_vsplit->add_child(dock_slot[DOCK_SLOT_RIGHT_UL]);
6996 dock_slot[DOCK_SLOT_RIGHT_BL] = memnew(TabContainer);
6997 right_l_vsplit->add_child(dock_slot[DOCK_SLOT_RIGHT_BL]);
6998
6999 right_r_vsplit = memnew(VSplitContainer);
7000 right_hsplit->add_child(right_r_vsplit);
7001 dock_slot[DOCK_SLOT_RIGHT_UR] = memnew(TabContainer);
7002 right_r_vsplit->add_child(dock_slot[DOCK_SLOT_RIGHT_UR]);
7003 dock_slot[DOCK_SLOT_RIGHT_BR] = memnew(TabContainer);
7004 right_r_vsplit->add_child(dock_slot[DOCK_SLOT_RIGHT_BR]);
7005
7006 // Store them for easier access.
7007 vsplits.push_back(left_l_vsplit);
7008 vsplits.push_back(left_r_vsplit);
7009 vsplits.push_back(right_l_vsplit);
7010 vsplits.push_back(right_r_vsplit);
7011
7012 hsplits.push_back(left_l_hsplit);
7013 hsplits.push_back(left_r_hsplit);
7014 hsplits.push_back(main_hsplit);
7015 hsplits.push_back(right_hsplit);
7016
7017 for (int i = 0; i < vsplits.size(); i++) {
7018 vsplits[i]->connect("dragged", callable_mp(this, &EditorNode::_dock_split_dragged));
7019 hsplits[i]->connect("dragged", callable_mp(this, &EditorNode::_dock_split_dragged));
7020 }
7021
7022 dock_select_popup = memnew(PopupPanel);
7023 gui_base->add_child(dock_select_popup);
7024 VBoxContainer *dock_vb = memnew(VBoxContainer);
7025 dock_select_popup->add_child(dock_vb);
7026
7027 HBoxContainer *dock_hb = memnew(HBoxContainer);
7028 dock_tab_move_left = memnew(Button);
7029 dock_tab_move_left->set_flat(true);
7030 dock_tab_move_left->set_focus_mode(Control::FOCUS_NONE);
7031 dock_tab_move_left->connect("pressed", callable_mp(this, &EditorNode::_dock_move_left));
7032 dock_hb->add_child(dock_tab_move_left);
7033
7034 Label *dock_label = memnew(Label);
7035 dock_label->set_text(TTR("Dock Position"));
7036 dock_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
7037 dock_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
7038 dock_hb->add_child(dock_label);
7039
7040 dock_tab_move_right = memnew(Button);
7041 dock_tab_move_right->set_flat(true);
7042 dock_tab_move_right->set_focus_mode(Control::FOCUS_NONE);
7043 dock_tab_move_right->connect("pressed", callable_mp(this, &EditorNode::_dock_move_right));
7044
7045 dock_hb->add_child(dock_tab_move_right);
7046 dock_vb->add_child(dock_hb);
7047
7048 dock_select = memnew(Control);
7049 dock_select->set_custom_minimum_size(Size2(128, 64) * EDSCALE);
7050 dock_select->connect("gui_input", callable_mp(this, &EditorNode::_dock_select_input));
7051 dock_select->connect("draw", callable_mp(this, &EditorNode::_dock_select_draw));
7052 dock_select->connect("mouse_exited", callable_mp(this, &EditorNode::_dock_popup_exit));
7053 dock_select->set_v_size_flags(Control::SIZE_EXPAND_FILL);
7054 dock_vb->add_child(dock_select);
7055
7056 if (!SceneTree::get_singleton()->get_root()->is_embedding_subwindows() && !EDITOR_GET("interface/editor/single_window_mode") && EDITOR_GET("interface/multi_window/enable")) {
7057 dock_float = memnew(Button);
7058 dock_float->set_icon(theme->get_icon("MakeFloating", EditorStringName(EditorIcons)));
7059 dock_float->set_text(TTR("Make Floating"));
7060 dock_float->set_focus_mode(Control::FOCUS_NONE);
7061 dock_float->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
7062 dock_float->connect("pressed", callable_mp(this, &EditorNode::_dock_make_selected_float));
7063
7064 dock_vb->add_child(dock_float);
7065 }
7066
7067 dock_select_popup->reset_size();
7068
7069 for (int i = 0; i < DOCK_SLOT_MAX; i++) {
7070 dock_slot[i]->set_custom_minimum_size(Size2(170, 0) * EDSCALE);
7071 dock_slot[i]->set_v_size_flags(Control::SIZE_EXPAND_FILL);
7072 dock_slot[i]->set_popup(dock_select_popup);
7073 dock_slot[i]->connect("pre_popup_pressed", callable_mp(this, &EditorNode::_dock_pre_popup).bind(i));
7074 dock_slot[i]->set_drag_to_rearrange_enabled(true);
7075 dock_slot[i]->set_tabs_rearrange_group(1);
7076 dock_slot[i]->connect("tab_changed", callable_mp(this, &EditorNode::_dock_tab_changed));
7077 dock_slot[i]->set_use_hidden_tabs_for_min_size(true);
7078 }
7079
7080 editor_layout_save_delay_timer = memnew(Timer);
7081 add_child(editor_layout_save_delay_timer);
7082 editor_layout_save_delay_timer->set_wait_time(0.5);
7083 editor_layout_save_delay_timer->set_one_shot(true);
7084 editor_layout_save_delay_timer->connect("timeout", callable_mp(this, &EditorNode::_save_editor_layout));
7085
7086 top_split = memnew(VSplitContainer);
7087 center_split->add_child(top_split);
7088 top_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);
7089 top_split->set_collapsed(true);
7090
7091 VBoxContainer *srt = memnew(VBoxContainer);
7092 srt->set_v_size_flags(Control::SIZE_EXPAND_FILL);
7093 srt->add_theme_constant_override("separation", 0);
7094 top_split->add_child(srt);
7095
7096 scene_tabs = memnew(EditorSceneTabs);
7097 srt->add_child(scene_tabs);
7098 scene_tabs->connect("tab_changed", callable_mp(this, &EditorNode::_set_current_scene));
7099 scene_tabs->connect("tab_closed", callable_mp(this, &EditorNode::_scene_tab_closed));
7100
7101 distraction_free = memnew(Button);
7102 distraction_free->set_flat(true);
7103 ED_SHORTCUT_AND_COMMAND("editor/distraction_free_mode", TTR("Distraction Free Mode"), KeyModifierMask::CTRL | KeyModifierMask::SHIFT | Key::F11);
7104 ED_SHORTCUT_OVERRIDE("editor/distraction_free_mode", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::D);
7105 distraction_free->set_shortcut(ED_GET_SHORTCUT("editor/distraction_free_mode"));
7106 distraction_free->set_tooltip_text(TTR("Toggle distraction-free mode."));
7107 distraction_free->set_toggle_mode(true);
7108 scene_tabs->add_extra_button(distraction_free);
7109 distraction_free->connect("pressed", callable_mp(this, &EditorNode::_toggle_distraction_free_mode));
7110
7111 scene_root_parent = memnew(PanelContainer);
7112 scene_root_parent->set_custom_minimum_size(Size2(0, 80) * EDSCALE);
7113 scene_root_parent->add_theme_style_override("panel", theme->get_stylebox(SNAME("Content"), EditorStringName(EditorStyles)));
7114 scene_root_parent->set_draw_behind_parent(true);
7115 srt->add_child(scene_root_parent);
7116 scene_root_parent->set_v_size_flags(Control::SIZE_EXPAND_FILL);
7117
7118 scene_root = memnew(SubViewport);
7119 scene_root->set_embedding_subwindows(true);
7120 scene_root->set_disable_3d(true);
7121 scene_root->set_disable_input(true);
7122 scene_root->set_as_audio_listener_2d(true);
7123
7124 main_screen_vbox = memnew(VBoxContainer);
7125 main_screen_vbox->set_name("MainScreen");
7126 main_screen_vbox->set_v_size_flags(Control::SIZE_EXPAND_FILL);
7127 main_screen_vbox->add_theme_constant_override("separation", 0);
7128 scene_root_parent->add_child(main_screen_vbox);
7129
7130 bool global_menu = !bool(EDITOR_GET("interface/editor/use_embedded_menu")) && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU);
7131 bool can_expand = bool(EDITOR_GET("interface/editor/expand_to_title")) && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_EXTEND_TO_TITLE);
7132
7133 if (can_expand) {
7134 // Add spacer to avoid other controls under window minimize/maximize/close buttons (left side).
7135 left_menu_spacer = memnew(Control);
7136 left_menu_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);
7137 title_bar->add_child(left_menu_spacer);
7138 }
7139
7140 main_menu = memnew(MenuBar);
7141 title_bar->add_child(main_menu);
7142
7143 main_menu->add_theme_style_override("hover", theme->get_stylebox(SNAME("MenuHover"), EditorStringName(EditorStyles)));
7144 main_menu->set_flat(true);
7145 main_menu->set_start_index(0); // Main menu, add to the start of global menu.
7146 main_menu->set_prefer_global_menu(global_menu);
7147 main_menu->set_switch_on_hover(true);
7148
7149 file_menu = memnew(PopupMenu);
7150 file_menu->set_name(TTR("Scene"));
7151 main_menu->add_child(file_menu);
7152 main_menu->set_menu_tooltip(0, TTR("Operations with scene files."));
7153
7154 accept = memnew(AcceptDialog);
7155 accept->set_unparent_when_invisible(true);
7156
7157 save_accept = memnew(AcceptDialog);
7158 save_accept->set_unparent_when_invisible(true);
7159 save_accept->connect("confirmed", callable_mp(this, &EditorNode::_menu_option).bind((int)MenuOptions::FILE_SAVE_AS_SCENE));
7160
7161 project_export = memnew(ProjectExportDialog);
7162 gui_base->add_child(project_export);
7163
7164 dependency_error = memnew(DependencyErrorDialog);
7165 gui_base->add_child(dependency_error);
7166
7167 dependency_fixer = memnew(DependencyEditor);
7168 gui_base->add_child(dependency_fixer);
7169
7170 editor_settings_dialog = memnew(EditorSettingsDialog);
7171 gui_base->add_child(editor_settings_dialog);
7172
7173 project_settings_editor = memnew(ProjectSettingsEditor(&editor_data));
7174 gui_base->add_child(project_settings_editor);
7175
7176 scene_import_settings = memnew(SceneImportSettings);
7177 gui_base->add_child(scene_import_settings);
7178
7179 audio_stream_import_settings = memnew(AudioStreamImportSettings);
7180 gui_base->add_child(audio_stream_import_settings);
7181
7182 fontdata_import_settings = memnew(DynamicFontImportSettings);
7183 gui_base->add_child(fontdata_import_settings);
7184
7185 export_template_manager = memnew(ExportTemplateManager);
7186 gui_base->add_child(export_template_manager);
7187
7188 feature_profile_manager = memnew(EditorFeatureProfileManager);
7189 gui_base->add_child(feature_profile_manager);
7190
7191 build_profile_manager = memnew(EditorBuildProfileManager);
7192 gui_base->add_child(build_profile_manager);
7193
7194 about = memnew(EditorAbout);
7195 gui_base->add_child(about);
7196 feature_profile_manager->connect("current_feature_profile_changed", callable_mp(this, &EditorNode::_feature_profile_changed));
7197
7198#if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED)
7199 fbx_importer_manager = memnew(FBXImporterManager);
7200 gui_base->add_child(fbx_importer_manager);
7201#endif
7202
7203 warning = memnew(AcceptDialog);
7204 warning->set_unparent_when_invisible(true);
7205 warning->add_button(TTR("Copy Text"), true, "copy");
7206 warning->connect("custom_action", callable_mp(this, &EditorNode::_copy_warning));
7207
7208 ED_SHORTCUT("editor/next_tab", TTR("Next Scene Tab"), KeyModifierMask::CMD_OR_CTRL + Key::TAB);
7209 ED_SHORTCUT("editor/prev_tab", TTR("Previous Scene Tab"), KeyModifierMask::CMD_OR_CTRL + KeyModifierMask::SHIFT + Key::TAB);
7210 ED_SHORTCUT("editor/filter_files", TTR("Focus FileSystem Filter"), KeyModifierMask::CMD_OR_CTRL + KeyModifierMask::ALT + Key::P);
7211
7212 command_palette = EditorCommandPalette::get_singleton();
7213 command_palette->set_title(TTR("Command Palette"));
7214 gui_base->add_child(command_palette);
7215
7216 file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/new_scene", TTR("New Scene"), KeyModifierMask::CMD_OR_CTRL + Key::N), FILE_NEW_SCENE);
7217 file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/new_inherited_scene", TTR("New Inherited Scene..."), KeyModifierMask::CMD_OR_CTRL + KeyModifierMask::SHIFT + Key::N), FILE_NEW_INHERITED_SCENE);
7218 file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/open_scene", TTR("Open Scene..."), KeyModifierMask::CMD_OR_CTRL + Key::O), FILE_OPEN_SCENE);
7219 file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/reopen_closed_scene", TTR("Reopen Closed Scene"), KeyModifierMask::CMD_OR_CTRL + KeyModifierMask::SHIFT + Key::T), FILE_OPEN_PREV);
7220 file_menu->add_submenu_item(TTR("Open Recent"), "RecentScenes", FILE_OPEN_RECENT);
7221
7222 file_menu->add_separator();
7223 file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/save_scene", TTR("Save Scene"), KeyModifierMask::CMD_OR_CTRL + Key::S), FILE_SAVE_SCENE);
7224 file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/save_scene_as", TTR("Save Scene As..."), KeyModifierMask::CMD_OR_CTRL + KeyModifierMask::SHIFT + Key::S), FILE_SAVE_AS_SCENE);
7225 file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/save_all_scenes", TTR("Save All Scenes"), KeyModifierMask::CMD_OR_CTRL + KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::S), FILE_SAVE_ALL_SCENES);
7226
7227 file_menu->add_separator();
7228
7229 file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/quick_open", TTR("Quick Open..."), KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::O), FILE_QUICK_OPEN);
7230 ED_SHORTCUT_OVERRIDE("editor/quick_open", "macos", KeyModifierMask::META + KeyModifierMask::CTRL + Key::O);
7231 file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/quick_open_scene", TTR("Quick Open Scene..."), KeyModifierMask::CMD_OR_CTRL + KeyModifierMask::SHIFT + Key::O), FILE_QUICK_OPEN_SCENE);
7232 file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/quick_open_script", TTR("Quick Open Script..."), KeyModifierMask::CMD_OR_CTRL + KeyModifierMask::ALT + Key::O), FILE_QUICK_OPEN_SCRIPT);
7233
7234 file_menu->add_separator();
7235 export_as_menu = memnew(PopupMenu);
7236 export_as_menu->set_name("Export");
7237 file_menu->add_child(export_as_menu);
7238 file_menu->add_submenu_item(TTR("Export As..."), "Export");
7239 export_as_menu->add_shortcut(ED_SHORTCUT("editor/export_as_mesh_library", TTR("MeshLibrary...")), FILE_EXPORT_MESH_LIBRARY);
7240 export_as_menu->connect("index_pressed", callable_mp(this, &EditorNode::_export_as_menu_option));
7241
7242 file_menu->add_separator();
7243 file_menu->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO, true, true);
7244 file_menu->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO, true, true);
7245
7246 file_menu->add_separator();
7247 file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/reload_saved_scene", TTR("Reload Saved Scene")), EDIT_RELOAD_SAVED_SCENE);
7248 file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/close_scene", TTR("Close Scene"), KeyModifierMask::CMD_OR_CTRL + KeyModifierMask::SHIFT + Key::W), FILE_CLOSE);
7249
7250 recent_scenes = memnew(PopupMenu);
7251 recent_scenes->set_name("RecentScenes");
7252 file_menu->add_child(recent_scenes);
7253 recent_scenes->connect("id_pressed", callable_mp(this, &EditorNode::_open_recent_scene));
7254
7255 if (!global_menu || !OS::get_singleton()->has_feature("macos")) {
7256 // On macOS "Quit" and "About" options are in the "app" menu.
7257 file_menu->add_separator();
7258 file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/file_quit", TTR("Quit"), KeyModifierMask::CMD_OR_CTRL + Key::Q), FILE_QUIT, true);
7259 }
7260
7261 project_menu = memnew(PopupMenu);
7262 project_menu->set_name(TTR("Project"));
7263 main_menu->add_child(project_menu);
7264
7265 project_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/project_settings", TTR("Project Settings..."), Key::NONE, TTR("Project Settings")), RUN_SETTINGS);
7266 project_menu->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
7267
7268 project_menu->add_separator();
7269 project_menu->add_item(TTR("Version Control"), VCS_MENU);
7270
7271 project_menu->add_separator();
7272 project_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/export", TTR("Export..."), Key::NONE, TTR("Export")), FILE_EXPORT_PROJECT);
7273#ifndef ANDROID_ENABLED
7274 project_menu->add_item(TTR("Install Android Build Template..."), FILE_INSTALL_ANDROID_SOURCE);
7275 project_menu->add_item(TTR("Open User Data Folder"), RUN_USER_DATA_FOLDER);
7276#endif
7277
7278 project_menu->add_separator();
7279 project_menu->add_item(TTR("Customize Engine Build Configuration..."), TOOLS_BUILD_PROFILE_MANAGER);
7280 project_menu->add_separator();
7281
7282 plugin_config_dialog = memnew(PluginConfigDialog);
7283 plugin_config_dialog->connect("plugin_ready", callable_mp(this, &EditorNode::_on_plugin_ready));
7284 gui_base->add_child(plugin_config_dialog);
7285
7286 tool_menu = memnew(PopupMenu);
7287 tool_menu->set_name("Tools");
7288 tool_menu->connect("index_pressed", callable_mp(this, &EditorNode::_tool_menu_option));
7289 project_menu->add_child(tool_menu);
7290 project_menu->add_submenu_item(TTR("Tools"), "Tools");
7291 tool_menu->add_item(TTR("Orphan Resource Explorer..."), TOOLS_ORPHAN_RESOURCES);
7292
7293 project_menu->add_separator();
7294 project_menu->add_shortcut(ED_SHORTCUT("editor/reload_current_project", TTR("Reload Current Project")), RELOAD_CURRENT_PROJECT);
7295 ED_SHORTCUT_AND_COMMAND("editor/quit_to_project_list", TTR("Quit to Project List"), KeyModifierMask::CTRL + KeyModifierMask::SHIFT + Key::Q);
7296 ED_SHORTCUT_OVERRIDE("editor/quit_to_project_list", "macos", KeyModifierMask::META + KeyModifierMask::CTRL + KeyModifierMask::ALT + Key::Q);
7297 project_menu->add_shortcut(ED_GET_SHORTCUT("editor/quit_to_project_list"), RUN_PROJECT_MANAGER, true);
7298
7299 // Spacer to center 2D / 3D / Script buttons.
7300 HBoxContainer *left_spacer = memnew(HBoxContainer);
7301 left_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);
7302 left_spacer->set_h_size_flags(Control::SIZE_EXPAND_FILL);
7303 title_bar->add_child(left_spacer);
7304
7305 if (can_expand && global_menu) {
7306 project_title = memnew(Label);
7307 project_title->add_theme_font_override("font", theme->get_font(SNAME("bold"), EditorStringName(EditorFonts)));
7308 project_title->add_theme_font_size_override("font_size", theme->get_font_size(SNAME("bold_size"), EditorStringName(EditorFonts)));
7309 project_title->set_focus_mode(Control::FOCUS_NONE);
7310 project_title->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
7311 project_title->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
7312 project_title->set_h_size_flags(Control::SIZE_EXPAND_FILL);
7313 project_title->set_mouse_filter(Control::MOUSE_FILTER_PASS);
7314 left_spacer->add_child(project_title);
7315 }
7316
7317 main_editor_button_hb = memnew(HBoxContainer);
7318 title_bar->add_child(main_editor_button_hb);
7319
7320 // Options are added and handled by DebuggerEditorPlugin.
7321 debug_menu = memnew(PopupMenu);
7322 debug_menu->set_name(TTR("Debug"));
7323 main_menu->add_child(debug_menu);
7324
7325 settings_menu = memnew(PopupMenu);
7326 settings_menu->set_name(TTR("Editor"));
7327 main_menu->add_child(settings_menu);
7328
7329 ED_SHORTCUT_AND_COMMAND("editor/editor_settings", TTR("Editor Settings..."));
7330 ED_SHORTCUT_OVERRIDE("editor/editor_settings", "macos", KeyModifierMask::META + Key::COMMA);
7331 settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/editor_settings"), SETTINGS_PREFERENCES);
7332 settings_menu->add_shortcut(ED_SHORTCUT("editor/command_palette", TTR("Command Palette..."), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::P), HELP_COMMAND_PALETTE);
7333 settings_menu->add_separator();
7334
7335 editor_layouts = memnew(PopupMenu);
7336 editor_layouts->set_name("Layouts");
7337 editor_layouts->set_auto_translate(false);
7338 settings_menu->add_child(editor_layouts);
7339 editor_layouts->connect("id_pressed", callable_mp(this, &EditorNode::_layout_menu_option));
7340 settings_menu->add_submenu_item(TTR("Editor Layout"), "Layouts");
7341 settings_menu->add_separator();
7342
7343 ED_SHORTCUT_AND_COMMAND("editor/take_screenshot", TTR("Take Screenshot"), KeyModifierMask::CTRL | Key::F12);
7344 ED_SHORTCUT_OVERRIDE("editor/take_screenshot", "macos", KeyModifierMask::META | Key::F12);
7345 settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/take_screenshot"), EDITOR_SCREENSHOT);
7346
7347 settings_menu->set_item_tooltip(-1, TTR("Screenshots are stored in the Editor Data/Settings Folder."));
7348
7349#ifndef ANDROID_ENABLED
7350 ED_SHORTCUT_AND_COMMAND("editor/fullscreen_mode", TTR("Toggle Fullscreen"), KeyModifierMask::SHIFT | Key::F11);
7351 ED_SHORTCUT_OVERRIDE("editor/fullscreen_mode", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::F);
7352 settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/fullscreen_mode"), SETTINGS_TOGGLE_FULLSCREEN);
7353#endif
7354 settings_menu->add_separator();
7355
7356#ifndef ANDROID_ENABLED
7357 if (OS::get_singleton()->get_data_path() == OS::get_singleton()->get_config_path()) {
7358 // Configuration and data folders are located in the same place (Windows/macOS).
7359 settings_menu->add_item(TTR("Open Editor Data/Settings Folder"), SETTINGS_EDITOR_DATA_FOLDER);
7360 } else {
7361 // Separate configuration and data folders (Linux).
7362 settings_menu->add_item(TTR("Open Editor Data Folder"), SETTINGS_EDITOR_DATA_FOLDER);
7363 settings_menu->add_item(TTR("Open Editor Settings Folder"), SETTINGS_EDITOR_CONFIG_FOLDER);
7364 }
7365 settings_menu->add_separator();
7366#endif
7367
7368 settings_menu->add_item(TTR("Manage Editor Features..."), SETTINGS_MANAGE_FEATURE_PROFILES);
7369#ifndef ANDROID_ENABLED
7370 settings_menu->add_item(TTR("Manage Export Templates..."), SETTINGS_MANAGE_EXPORT_TEMPLATES);
7371#endif
7372#if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED)
7373 settings_menu->add_item(TTR("Configure FBX Importer..."), SETTINGS_MANAGE_FBX_IMPORTER);
7374#endif
7375
7376 help_menu = memnew(PopupMenu);
7377 help_menu->set_name(TTR("Help"));
7378 main_menu->add_child(help_menu);
7379
7380 help_menu->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
7381
7382 ED_SHORTCUT_AND_COMMAND("editor/editor_help", TTR("Search Help"), Key::F1);
7383 ED_SHORTCUT_OVERRIDE("editor/editor_help", "macos", KeyModifierMask::ALT | Key::SPACE);
7384 help_menu->add_icon_shortcut(theme->get_icon(SNAME("HelpSearch"), EditorStringName(EditorIcons)), ED_GET_SHORTCUT("editor/editor_help"), HELP_SEARCH);
7385 help_menu->add_separator();
7386 help_menu->add_icon_shortcut(theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)), ED_SHORTCUT_AND_COMMAND("editor/online_docs", TTR("Online Documentation")), HELP_DOCS);
7387 help_menu->add_icon_shortcut(theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)), ED_SHORTCUT_AND_COMMAND("editor/q&a", TTR("Questions & Answers")), HELP_QA);
7388 help_menu->add_icon_shortcut(theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)), ED_SHORTCUT_AND_COMMAND("editor/community", TTR("Community")), HELP_COMMUNITY);
7389 help_menu->add_separator();
7390 help_menu->add_icon_shortcut(theme->get_icon(SNAME("ActionCopy"), EditorStringName(EditorIcons)), ED_SHORTCUT_AND_COMMAND("editor/copy_system_info", TTR("Copy System Info")), HELP_COPY_SYSTEM_INFO);
7391 help_menu->set_item_tooltip(-1, TTR("Copies the system info as a single-line text into the clipboard."));
7392 help_menu->add_icon_shortcut(theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)), ED_SHORTCUT_AND_COMMAND("editor/report_a_bug", TTR("Report a Bug")), HELP_REPORT_A_BUG);
7393 help_menu->add_icon_shortcut(theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)), ED_SHORTCUT_AND_COMMAND("editor/suggest_a_feature", TTR("Suggest a Feature")), HELP_SUGGEST_A_FEATURE);
7394 help_menu->add_icon_shortcut(theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)), ED_SHORTCUT_AND_COMMAND("editor/send_docs_feedback", TTR("Send Docs Feedback")), HELP_SEND_DOCS_FEEDBACK);
7395 help_menu->add_separator();
7396 if (!global_menu || !OS::get_singleton()->has_feature("macos")) {
7397 // On macOS "Quit" and "About" options are in the "app" menu.
7398 help_menu->add_icon_shortcut(theme->get_icon(SNAME("Godot"), EditorStringName(EditorIcons)), ED_SHORTCUT_AND_COMMAND("editor/about", TTR("About Godot")), HELP_ABOUT);
7399 }
7400 help_menu->add_icon_shortcut(theme->get_icon(SNAME("Heart"), EditorStringName(EditorIcons)), ED_SHORTCUT_AND_COMMAND("editor/support_development", TTR("Support Godot Development")), HELP_SUPPORT_GODOT_DEVELOPMENT);
7401
7402 // Spacer to center 2D / 3D / Script buttons.
7403 Control *right_spacer = memnew(Control);
7404 right_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);
7405 right_spacer->set_h_size_flags(Control::SIZE_EXPAND_FILL);
7406 title_bar->add_child(right_spacer);
7407
7408 project_run_bar = memnew(EditorRunBar);
7409 title_bar->add_child(project_run_bar);
7410 project_run_bar->connect("play_pressed", callable_mp(this, &EditorNode::_project_run_started));
7411 project_run_bar->connect("stop_pressed", callable_mp(this, &EditorNode::_project_run_stopped));
7412
7413 HBoxContainer *right_menu_hb = memnew(HBoxContainer);
7414 title_bar->add_child(right_menu_hb);
7415
7416 renderer = memnew(OptionButton);
7417 renderer->set_visible(true);
7418 renderer->set_flat(true);
7419 renderer->set_fit_to_longest_item(false);
7420 renderer->set_focus_mode(Control::FOCUS_NONE);
7421 renderer->connect("item_selected", callable_mp(this, &EditorNode::_renderer_selected));
7422 renderer->add_theme_font_override("font", theme->get_font(SNAME("bold"), EditorStringName(EditorFonts)));
7423 renderer->add_theme_font_size_override("font_size", theme->get_font_size(SNAME("bold_size"), EditorStringName(EditorFonts)));
7424 renderer->set_tooltip_text(TTR("Choose a renderer."));
7425
7426 right_menu_hb->add_child(renderer);
7427
7428 if (can_expand) {
7429 // Add spacer to avoid other controls under the window minimize/maximize/close buttons (right side).
7430 right_menu_spacer = memnew(Control);
7431 right_menu_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);
7432 title_bar->add_child(right_menu_spacer);
7433 }
7434
7435 String current_renderer = GLOBAL_GET("rendering/renderer/rendering_method");
7436
7437 PackedStringArray renderers = ProjectSettings::get_singleton()->get_custom_property_info().get(StringName("rendering/renderer/rendering_method")).hint_string.split(",", false);
7438
7439 // As we are doing string comparisons, keep in standard case to prevent problems with capitals
7440 // "vulkan" in particular uses lowercase "v" in the code, and uppercase in the UI.
7441 current_renderer = current_renderer.to_lower();
7442
7443 for (int i = 0; i < renderers.size(); i++) {
7444 String rendering_method = renderers[i];
7445
7446 // Add the renderers name to the UI.
7447 if (rendering_method == "forward_plus") {
7448 renderer->add_item(TTR("Forward+"));
7449 }
7450 if (rendering_method == "mobile") {
7451 renderer->add_item(TTR("Mobile"));
7452 }
7453 if (rendering_method == "gl_compatibility") {
7454 renderer->add_item(TTR("Compatibility"));
7455 }
7456 renderer->set_item_metadata(i, rendering_method);
7457
7458 // Lowercase for standard comparison.
7459 rendering_method = rendering_method.to_lower();
7460
7461 if (current_renderer == rendering_method) {
7462 renderer->select(i);
7463 renderer_current = i;
7464 }
7465 }
7466 _update_renderer_color();
7467
7468 video_restart_dialog = memnew(ConfirmationDialog);
7469 video_restart_dialog->set_text(TTR("Changing the renderer requires restarting the editor."));
7470 video_restart_dialog->set_ok_button_text(TTR("Save & Restart"));
7471 video_restart_dialog->connect("confirmed", callable_mp(this, &EditorNode::_menu_option).bind(SET_RENDERER_NAME_SAVE_AND_RESTART));
7472 gui_base->add_child(video_restart_dialog);
7473
7474 progress_hb = memnew(BackgroundProgress);
7475
7476 layout_dialog = memnew(EditorLayoutsDialog);
7477 gui_base->add_child(layout_dialog);
7478 layout_dialog->set_hide_on_ok(false);
7479 layout_dialog->set_size(Size2(225, 270) * EDSCALE);
7480 layout_dialog->connect("name_confirmed", callable_mp(this, &EditorNode::_dialog_action));
7481
7482 update_spinner = memnew(MenuButton);
7483 right_menu_hb->add_child(update_spinner);
7484 update_spinner->set_icon(theme->get_icon(SNAME("Progress1"), EditorStringName(EditorIcons)));
7485 update_spinner->get_popup()->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
7486 PopupMenu *p = update_spinner->get_popup();
7487 p->add_radio_check_item(TTR("Update Continuously"), SETTINGS_UPDATE_CONTINUOUSLY);
7488 p->add_radio_check_item(TTR("Update When Changed"), SETTINGS_UPDATE_WHEN_CHANGED);
7489 p->add_separator();
7490 p->add_item(TTR("Hide Update Spinner"), SETTINGS_UPDATE_SPINNER_HIDE);
7491 _update_update_spinner();
7492
7493 // Instantiate and place editor docks.
7494
7495 memnew(SceneTreeDock(scene_root, editor_selection, editor_data));
7496 memnew(InspectorDock(editor_data));
7497 memnew(ImportDock);
7498 memnew(NodeDock);
7499
7500 FileSystemDock *filesystem_dock = memnew(FileSystemDock);
7501 filesystem_dock->connect("inherit", callable_mp(this, &EditorNode::_inherit_request));
7502 filesystem_dock->connect("instantiate", callable_mp(this, &EditorNode::_instantiate_request));
7503 filesystem_dock->connect("display_mode_changed", callable_mp(this, &EditorNode::_save_editor_layout));
7504 get_project_settings()->connect_filesystem_dock_signals(filesystem_dock);
7505
7506 history_dock = memnew(HistoryDock);
7507
7508 // Scene: Top left.
7509 dock_slot[DOCK_SLOT_LEFT_UR]->add_child(SceneTreeDock::get_singleton());
7510 dock_slot[DOCK_SLOT_LEFT_UR]->set_tab_title(dock_slot[DOCK_SLOT_LEFT_UR]->get_tab_idx_from_control(SceneTreeDock::get_singleton()), TTR("Scene"));
7511
7512 // Import: Top left, behind Scene.
7513 dock_slot[DOCK_SLOT_LEFT_UR]->add_child(ImportDock::get_singleton());
7514 dock_slot[DOCK_SLOT_LEFT_UR]->set_tab_title(dock_slot[DOCK_SLOT_LEFT_UR]->get_tab_idx_from_control(ImportDock::get_singleton()), TTR("Import"));
7515
7516 // FileSystem: Bottom left.
7517 dock_slot[DOCK_SLOT_LEFT_BR]->add_child(FileSystemDock::get_singleton());
7518 dock_slot[DOCK_SLOT_LEFT_BR]->set_tab_title(dock_slot[DOCK_SLOT_LEFT_BR]->get_tab_idx_from_control(FileSystemDock::get_singleton()), TTR("FileSystem"));
7519
7520 // Inspector: Full height right.
7521 dock_slot[DOCK_SLOT_RIGHT_UL]->add_child(InspectorDock::get_singleton());
7522 dock_slot[DOCK_SLOT_RIGHT_UL]->set_tab_title(dock_slot[DOCK_SLOT_RIGHT_UL]->get_tab_idx_from_control(InspectorDock::get_singleton()), TTR("Inspector"));
7523
7524 // Node: Full height right, behind Inspector.
7525 dock_slot[DOCK_SLOT_RIGHT_UL]->add_child(NodeDock::get_singleton());
7526 dock_slot[DOCK_SLOT_RIGHT_UL]->set_tab_title(dock_slot[DOCK_SLOT_RIGHT_UL]->get_tab_idx_from_control(NodeDock::get_singleton()), TTR("Node"));
7527
7528 // History: Full height right, behind Node.
7529 dock_slot[DOCK_SLOT_RIGHT_UL]->add_child(history_dock);
7530 dock_slot[DOCK_SLOT_RIGHT_UL]->set_tab_title(dock_slot[DOCK_SLOT_RIGHT_UL]->get_tab_idx_from_control(history_dock), TTR("History"));
7531
7532 // Hide unused dock slots and vsplits.
7533 dock_slot[DOCK_SLOT_LEFT_UL]->hide();
7534 dock_slot[DOCK_SLOT_LEFT_BL]->hide();
7535 dock_slot[DOCK_SLOT_RIGHT_BL]->hide();
7536 dock_slot[DOCK_SLOT_RIGHT_UR]->hide();
7537 dock_slot[DOCK_SLOT_RIGHT_BR]->hide();
7538 left_l_vsplit->hide();
7539 right_r_vsplit->hide();
7540
7541 // Add some offsets to left_r and main hsplits to make LEFT_R and RIGHT_L docks wider than minsize.
7542 left_r_hsplit->set_split_offset(270 * EDSCALE);
7543 main_hsplit->set_split_offset(-270 * EDSCALE);
7544
7545 // Define corresponding default layout.
7546
7547 const String docks_section = "docks";
7548 default_layout.instantiate();
7549 // Dock numbers are based on DockSlot enum value + 1.
7550 default_layout->set_value(docks_section, "dock_3", "Scene,Import");
7551 default_layout->set_value(docks_section, "dock_4", "FileSystem");
7552 default_layout->set_value(docks_section, "dock_5", "Inspector,Node,History");
7553
7554 for (int i = 0; i < vsplits.size(); i++) {
7555 default_layout->set_value(docks_section, "dock_split_" + itos(i + 1), 0);
7556 }
7557 default_layout->set_value(docks_section, "dock_hsplit_1", 0);
7558 default_layout->set_value(docks_section, "dock_hsplit_2", 270 * EDSCALE);
7559 default_layout->set_value(docks_section, "dock_hsplit_3", -270 * EDSCALE);
7560 default_layout->set_value(docks_section, "dock_hsplit_4", 0);
7561
7562 _update_layouts_menu();
7563
7564 // Bottom panels.
7565
7566 bottom_panel = memnew(PanelContainer);
7567 bottom_panel->add_theme_style_override("panel", theme->get_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)));
7568 center_split->add_child(bottom_panel);
7569 center_split->set_dragger_visibility(SplitContainer::DRAGGER_HIDDEN);
7570
7571 bottom_panel_vb = memnew(VBoxContainer);
7572 bottom_panel->add_child(bottom_panel_vb);
7573
7574 bottom_panel_hb = memnew(HBoxContainer);
7575 bottom_panel_hb->set_custom_minimum_size(Size2(0, 24 * EDSCALE)); // Adjust for the height of the "Expand Bottom Dock" icon.
7576 bottom_panel_vb->add_child(bottom_panel_hb);
7577
7578 bottom_panel_hb_editors = memnew(HBoxContainer);
7579 bottom_panel_hb_editors->set_h_size_flags(Control::SIZE_EXPAND_FILL);
7580 bottom_panel_hb->add_child(bottom_panel_hb_editors);
7581
7582 editor_toaster = memnew(EditorToaster);
7583 bottom_panel_hb->add_child(editor_toaster);
7584
7585 VBoxContainer *version_info_vbc = memnew(VBoxContainer);
7586 bottom_panel_hb->add_child(version_info_vbc);
7587
7588 // Add a dummy control node for vertical spacing.
7589 Control *v_spacer = memnew(Control);
7590 version_info_vbc->add_child(v_spacer);
7591
7592 version_btn = memnew(LinkButton);
7593 version_btn->set_text(VERSION_FULL_CONFIG);
7594 String hash = String(VERSION_HASH);
7595 if (hash.length() != 0) {
7596 hash = " " + vformat("[%s]", hash.left(9));
7597 }
7598 // Set the text to copy in metadata as it slightly differs from the button's text.
7599 version_btn->set_meta(META_TEXT_TO_COPY, "v" VERSION_FULL_BUILD + hash);
7600 // Fade out the version label to be less prominent, but still readable.
7601 version_btn->set_self_modulate(Color(1, 1, 1, 0.65));
7602 version_btn->set_underline_mode(LinkButton::UNDERLINE_MODE_ON_HOVER);
7603 version_btn->set_tooltip_text(TTR("Click to copy."));
7604 version_btn->connect("pressed", callable_mp(this, &EditorNode::_version_button_pressed));
7605 version_info_vbc->add_child(version_btn);
7606
7607 // Add a dummy control node for horizontal spacing.
7608 Control *h_spacer = memnew(Control);
7609 bottom_panel_hb->add_child(h_spacer);
7610
7611 bottom_panel_raise = memnew(Button);
7612 bottom_panel_hb->add_child(bottom_panel_raise);
7613 bottom_panel_raise->hide();
7614 bottom_panel_raise->set_flat(true);
7615 bottom_panel_raise->set_toggle_mode(true);
7616 bottom_panel_raise->set_shortcut(ED_SHORTCUT_AND_COMMAND("editor/bottom_panel_expand", TTR("Expand Bottom Panel"), KeyModifierMask::SHIFT | Key::F12));
7617 bottom_panel_raise->connect("toggled", callable_mp(this, &EditorNode::_bottom_panel_raise_toggled));
7618
7619 log = memnew(EditorLog);
7620 Button *output_button = add_bottom_panel_item(TTR("Output"), log);
7621 log->set_tool_button(output_button);
7622
7623 center_split->connect("resized", callable_mp(this, &EditorNode::_vp_resized));
7624
7625 native_shader_source_visualizer = memnew(EditorNativeShaderSourceVisualizer);
7626 gui_base->add_child(native_shader_source_visualizer);
7627
7628 orphan_resources = memnew(OrphanResourcesDialog);
7629 gui_base->add_child(orphan_resources);
7630
7631 confirmation = memnew(ConfirmationDialog);
7632 gui_base->add_child(confirmation);
7633 confirmation->connect("confirmed", callable_mp(this, &EditorNode::_menu_confirm_current));
7634
7635 save_confirmation = memnew(ConfirmationDialog);
7636 save_confirmation->add_button(TTR("Don't Save"), DisplayServer::get_singleton()->get_swap_cancel_ok(), "discard");
7637 gui_base->add_child(save_confirmation);
7638 save_confirmation->set_min_size(Vector2(450.0 * EDSCALE, 0));
7639 save_confirmation->connect("confirmed", callable_mp(this, &EditorNode::_menu_confirm_current));
7640 save_confirmation->connect("custom_action", callable_mp(this, &EditorNode::_discard_changes));
7641
7642 gradle_build_manage_templates = memnew(ConfirmationDialog);
7643 gradle_build_manage_templates->set_text(TTR("Android build template is missing, please install relevant templates."));
7644 gradle_build_manage_templates->set_ok_button_text(TTR("Manage Templates"));
7645 gradle_build_manage_templates->add_button(TTR("Install from file"))->connect("pressed", callable_mp(this, &EditorNode::_menu_option).bind(SETTINGS_INSTALL_ANDROID_BUILD_TEMPLATE));
7646 gradle_build_manage_templates->connect("confirmed", callable_mp(this, &EditorNode::_menu_option).bind(SETTINGS_MANAGE_EXPORT_TEMPLATES));
7647 gui_base->add_child(gradle_build_manage_templates);
7648
7649 file_android_build_source = memnew(EditorFileDialog);
7650 file_android_build_source->set_title(TTR("Select Android sources file"));
7651 file_android_build_source->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
7652 file_android_build_source->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
7653 file_android_build_source->add_filter("*.zip");
7654 file_android_build_source->connect("file_selected", callable_mp(this, &EditorNode::_android_build_source_selected));
7655 gui_base->add_child(file_android_build_source);
7656
7657 install_android_build_template = memnew(ConfirmationDialog);
7658 install_android_build_template->set_text(TTR("This will set up your project for gradle Android builds by installing the source template to \"res://android/build\".\nYou can then apply modifications and build your own custom APK on export (adding modules, changing the AndroidManifest.xml, etc.).\nNote that in order to make gradle builds instead of using pre-built APKs, the \"Use Gradle Build\" option should be enabled in the Android export preset."));
7659 install_android_build_template->set_ok_button_text(TTR("Install"));
7660 install_android_build_template->connect("confirmed", callable_mp(this, &EditorNode::_menu_confirm_current));
7661 gui_base->add_child(install_android_build_template);
7662
7663 remove_android_build_template = memnew(ConfirmationDialog);
7664 remove_android_build_template->set_text(TTR("The Android build template is already installed in this project and it won't be overwritten.\nRemove the \"res://android/build\" directory manually before attempting this operation again."));
7665 remove_android_build_template->set_ok_button_text(TTR("Show in File Manager"));
7666 remove_android_build_template->connect("confirmed", callable_mp(this, &EditorNode::_menu_option).bind(FILE_EXPLORE_ANDROID_BUILD_TEMPLATES));
7667 gui_base->add_child(remove_android_build_template);
7668
7669 file_templates = memnew(EditorFileDialog);
7670 file_templates->set_title(TTR("Import Templates From ZIP File"));
7671
7672 gui_base->add_child(file_templates);
7673 file_templates->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
7674 file_templates->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
7675 file_templates->clear_filters();
7676 file_templates->add_filter("*.tpz", TTR("Template Package"));
7677
7678 file = memnew(EditorFileDialog);
7679 gui_base->add_child(file);
7680 file->set_current_dir("res://");
7681
7682 file_export_lib = memnew(EditorFileDialog);
7683 file_export_lib->set_title(TTR("Export Library"));
7684 file_export_lib->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
7685 file_export_lib->connect("file_selected", callable_mp(this, &EditorNode::_dialog_action));
7686 file_export_lib_merge = memnew(CheckBox);
7687 file_export_lib_merge->set_text(TTR("Merge With Existing"));
7688 file_export_lib_merge->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
7689 file_export_lib_merge->set_pressed(true);
7690 file_export_lib->get_vbox()->add_child(file_export_lib_merge);
7691 file_export_lib_apply_xforms = memnew(CheckBox);
7692 file_export_lib_apply_xforms->set_text(TTR("Apply MeshInstance Transforms"));
7693 file_export_lib_apply_xforms->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
7694 file_export_lib_apply_xforms->set_pressed(false);
7695 file_export_lib->get_vbox()->add_child(file_export_lib_apply_xforms);
7696 gui_base->add_child(file_export_lib);
7697
7698 file_script = memnew(EditorFileDialog);
7699 file_script->set_title(TTR("Open & Run a Script"));
7700 file_script->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
7701 file_script->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
7702 List<String> sexts;
7703 ResourceLoader::get_recognized_extensions_for_type("Script", &sexts);
7704 for (const String &E : sexts) {
7705 file_script->add_filter("*." + E);
7706 }
7707 gui_base->add_child(file_script);
7708 file_script->connect("file_selected", callable_mp(this, &EditorNode::_dialog_action));
7709
7710 file_menu->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
7711 file_menu->connect("about_to_popup", callable_mp(this, &EditorNode::_update_file_menu_opened));
7712 file_menu->connect("popup_hide", callable_mp(this, &EditorNode::_update_file_menu_closed));
7713
7714 settings_menu->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
7715
7716 file->connect("file_selected", callable_mp(this, &EditorNode::_dialog_action));
7717 file_templates->connect("file_selected", callable_mp(this, &EditorNode::_dialog_action));
7718
7719 audio_preview_gen = memnew(AudioStreamPreviewGenerator);
7720 add_child(audio_preview_gen);
7721
7722 add_editor_plugin(memnew(DebuggerEditorPlugin(debug_menu)));
7723
7724 disk_changed = memnew(ConfirmationDialog);
7725 {
7726 VBoxContainer *vbc = memnew(VBoxContainer);
7727 disk_changed->add_child(vbc);
7728
7729 Label *dl = memnew(Label);
7730 dl->set_text(TTR("The following files are newer on disk.\nWhat action should be taken?"));
7731 vbc->add_child(dl);
7732
7733 disk_changed_list = memnew(Tree);
7734 vbc->add_child(disk_changed_list);
7735 disk_changed_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
7736
7737 disk_changed->connect("confirmed", callable_mp(this, &EditorNode::_reload_modified_scenes));
7738 disk_changed->connect("confirmed", callable_mp(this, &EditorNode::_reload_project_settings));
7739 disk_changed->set_ok_button_text(TTR("Reload"));
7740
7741 disk_changed->add_button(TTR("Resave"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave");
7742 disk_changed->connect("custom_action", callable_mp(this, &EditorNode::_resave_scenes));
7743 }
7744
7745 gui_base->add_child(disk_changed);
7746
7747 add_editor_plugin(memnew(AnimationPlayerEditorPlugin));
7748 add_editor_plugin(memnew(AnimationTrackKeyEditEditorPlugin));
7749 add_editor_plugin(memnew(CanvasItemEditorPlugin));
7750 add_editor_plugin(memnew(Node3DEditorPlugin));
7751 add_editor_plugin(memnew(ScriptEditorPlugin));
7752
7753 EditorAudioBuses *audio_bus_editor = EditorAudioBuses::register_editor();
7754
7755 ScriptTextEditor::register_editor(); // Register one for text scripts.
7756 TextEditor::register_editor();
7757
7758 if (AssetLibraryEditorPlugin::is_available()) {
7759 add_editor_plugin(memnew(AssetLibraryEditorPlugin));
7760 } else {
7761 print_verbose("Asset Library not available (due to using Web editor, or SSL support disabled).");
7762 }
7763
7764 // More visually meaningful to have this later.
7765 raise_bottom_panel_item(AnimationPlayerEditor::get_singleton());
7766
7767 add_editor_plugin(VersionControlEditorPlugin::get_singleton());
7768
7769 vcs_actions_menu = VersionControlEditorPlugin::get_singleton()->get_version_control_actions_panel();
7770 vcs_actions_menu->set_name("Version Control");
7771 vcs_actions_menu->connect("index_pressed", callable_mp(this, &EditorNode::_version_control_menu_option));
7772 vcs_actions_menu->add_item(TTR("Create Version Control Metadata..."), RUN_VCS_METADATA);
7773 vcs_actions_menu->add_item(TTR("Version Control Settings..."), RUN_VCS_SETTINGS);
7774 project_menu->add_child(vcs_actions_menu);
7775 project_menu->set_item_submenu(project_menu->get_item_index(VCS_MENU), "Version Control");
7776
7777 add_editor_plugin(memnew(AudioBusesEditorPlugin(audio_bus_editor)));
7778
7779 for (int i = 0; i < EditorPlugins::get_plugin_count(); i++) {
7780 add_editor_plugin(EditorPlugins::create(i));
7781 }
7782
7783 for (const StringName &extension_class_name : GDExtensionEditorPlugins::get_extension_classes()) {
7784 add_extension_editor_plugin(extension_class_name);
7785 }
7786 GDExtensionEditorPlugins::editor_node_add_plugin = &EditorNode::add_extension_editor_plugin;
7787 GDExtensionEditorPlugins::editor_node_remove_plugin = &EditorNode::remove_extension_editor_plugin;
7788
7789 for (int i = 0; i < plugin_init_callback_count; i++) {
7790 plugin_init_callbacks[i]();
7791 }
7792
7793 resource_preview->add_preview_generator(Ref<EditorTexturePreviewPlugin>(memnew(EditorTexturePreviewPlugin)));
7794 resource_preview->add_preview_generator(Ref<EditorImagePreviewPlugin>(memnew(EditorImagePreviewPlugin)));
7795 resource_preview->add_preview_generator(Ref<EditorPackedScenePreviewPlugin>(memnew(EditorPackedScenePreviewPlugin)));
7796 resource_preview->add_preview_generator(Ref<EditorMaterialPreviewPlugin>(memnew(EditorMaterialPreviewPlugin)));
7797 resource_preview->add_preview_generator(Ref<EditorScriptPreviewPlugin>(memnew(EditorScriptPreviewPlugin)));
7798 resource_preview->add_preview_generator(Ref<EditorAudioStreamPreviewPlugin>(memnew(EditorAudioStreamPreviewPlugin)));
7799 resource_preview->add_preview_generator(Ref<EditorMeshPreviewPlugin>(memnew(EditorMeshPreviewPlugin)));
7800 resource_preview->add_preview_generator(Ref<EditorBitmapPreviewPlugin>(memnew(EditorBitmapPreviewPlugin)));
7801 resource_preview->add_preview_generator(Ref<EditorFontPreviewPlugin>(memnew(EditorFontPreviewPlugin)));
7802 resource_preview->add_preview_generator(Ref<EditorGradientPreviewPlugin>(memnew(EditorGradientPreviewPlugin)));
7803
7804 {
7805 Ref<StandardMaterial3DConversionPlugin> spatial_mat_convert;
7806 spatial_mat_convert.instantiate();
7807 resource_conversion_plugins.push_back(spatial_mat_convert);
7808
7809 Ref<ORMMaterial3DConversionPlugin> orm_mat_convert;
7810 orm_mat_convert.instantiate();
7811 resource_conversion_plugins.push_back(orm_mat_convert);
7812
7813 Ref<CanvasItemMaterialConversionPlugin> canvas_item_mat_convert;
7814 canvas_item_mat_convert.instantiate();
7815 resource_conversion_plugins.push_back(canvas_item_mat_convert);
7816
7817 Ref<ParticleProcessMaterialConversionPlugin> particles_mat_convert;
7818 particles_mat_convert.instantiate();
7819 resource_conversion_plugins.push_back(particles_mat_convert);
7820
7821 Ref<ProceduralSkyMaterialConversionPlugin> procedural_sky_mat_convert;
7822 procedural_sky_mat_convert.instantiate();
7823 resource_conversion_plugins.push_back(procedural_sky_mat_convert);
7824
7825 Ref<PanoramaSkyMaterialConversionPlugin> panorama_sky_mat_convert;
7826 panorama_sky_mat_convert.instantiate();
7827 resource_conversion_plugins.push_back(panorama_sky_mat_convert);
7828
7829 Ref<PhysicalSkyMaterialConversionPlugin> physical_sky_mat_convert;
7830 physical_sky_mat_convert.instantiate();
7831 resource_conversion_plugins.push_back(physical_sky_mat_convert);
7832
7833 Ref<FogMaterialConversionPlugin> fog_mat_convert;
7834 fog_mat_convert.instantiate();
7835 resource_conversion_plugins.push_back(fog_mat_convert);
7836
7837 Ref<VisualShaderConversionPlugin> vshader_convert;
7838 vshader_convert.instantiate();
7839 resource_conversion_plugins.push_back(vshader_convert);
7840 }
7841
7842 update_spinner_step_msec = OS::get_singleton()->get_ticks_msec();
7843 update_spinner_step_frame = Engine::get_singleton()->get_frames_drawn();
7844
7845 editor_plugin_screen = nullptr;
7846 editor_plugins_over = memnew(EditorPluginList);
7847 editor_plugins_force_over = memnew(EditorPluginList);
7848 editor_plugins_force_input_forwarding = memnew(EditorPluginList);
7849
7850 Ref<GDExtensionExportPlugin> gdextension_export_plugin;
7851 gdextension_export_plugin.instantiate();
7852
7853 EditorExport::get_singleton()->add_export_plugin(gdextension_export_plugin);
7854
7855 Ref<DedicatedServerExportPlugin> dedicated_server_export_plugin;
7856 dedicated_server_export_plugin.instantiate();
7857
7858 EditorExport::get_singleton()->add_export_plugin(dedicated_server_export_plugin);
7859
7860 Ref<PackedSceneEditorTranslationParserPlugin> packed_scene_translation_parser_plugin;
7861 packed_scene_translation_parser_plugin.instantiate();
7862 EditorTranslationParser::get_singleton()->add_parser(packed_scene_translation_parser_plugin, EditorTranslationParser::STANDARD);
7863
7864 _edit_current();
7865 current = nullptr;
7866 saving_resource = Ref<Resource>();
7867
7868 set_process(true);
7869
7870 open_imported = memnew(ConfirmationDialog);
7871 open_imported->set_ok_button_text(TTR("Open Anyway"));
7872 new_inherited_button = open_imported->add_button(TTR("New Inherited"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "inherit");
7873 open_imported->connect("confirmed", callable_mp(this, &EditorNode::_open_imported));
7874 open_imported->connect("custom_action", callable_mp(this, &EditorNode::_inherit_imported));
7875 gui_base->add_child(open_imported);
7876
7877 quick_open = memnew(EditorQuickOpen);
7878 gui_base->add_child(quick_open);
7879 quick_open->connect("quick_open", callable_mp(this, &EditorNode::_quick_opened));
7880
7881 _update_recent_scenes();
7882
7883 set_process_shortcut_input(true);
7884
7885 load_errors = memnew(RichTextLabel);
7886 load_error_dialog = memnew(AcceptDialog);
7887 load_error_dialog->set_unparent_when_invisible(true);
7888 load_error_dialog->add_child(load_errors);
7889 load_error_dialog->set_title(TTR("Load Errors"));
7890
7891 execute_outputs = memnew(RichTextLabel);
7892 execute_outputs->set_selection_enabled(true);
7893 execute_outputs->set_context_menu_enabled(true);
7894 execute_output_dialog = memnew(AcceptDialog);
7895 execute_output_dialog->set_unparent_when_invisible(true);
7896 execute_output_dialog->add_child(execute_outputs);
7897 execute_output_dialog->set_title("");
7898
7899 EditorFileSystem::get_singleton()->connect("sources_changed", callable_mp(this, &EditorNode::_sources_changed));
7900 EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &EditorNode::_fs_changed));
7901 EditorFileSystem::get_singleton()->connect("resources_reimported", callable_mp(this, &EditorNode::_resources_reimported));
7902 EditorFileSystem::get_singleton()->connect("resources_reload", callable_mp(this, &EditorNode::_resources_changed));
7903
7904 _build_icon_type_cache();
7905
7906 pick_main_scene = memnew(ConfirmationDialog);
7907 gui_base->add_child(pick_main_scene);
7908 pick_main_scene->set_ok_button_text(TTR("Select"));
7909 pick_main_scene->connect("confirmed", callable_mp(this, &EditorNode::_menu_option).bind(SETTINGS_PICK_MAIN_SCENE));
7910 select_current_scene_button = pick_main_scene->add_button(TTR("Select Current"), true, "select_current");
7911 pick_main_scene->connect("custom_action", callable_mp(this, &EditorNode::_pick_main_scene_custom_action));
7912
7913 for (int i = 0; i < _init_callbacks.size(); i++) {
7914 _init_callbacks[i]();
7915 }
7916
7917 editor_data.add_edited_scene(-1);
7918 editor_data.set_edited_scene(0);
7919 scene_tabs->update_scene_tabs();
7920
7921 ImportDock::get_singleton()->initialize_import_options();
7922
7923 FileAccess::set_file_close_fail_notify_callback(_file_access_close_error_notify);
7924
7925 print_handler.printfunc = _print_handler;
7926 print_handler.userdata = this;
7927 add_print_handler(&print_handler);
7928
7929 ResourceSaver::set_save_callback(_resource_saved);
7930 ResourceLoader::set_load_callback(_resource_loaded);
7931
7932 // Use the Ctrl modifier so F2 can be used to rename nodes in the scene tree dock.
7933 ED_SHORTCUT_AND_COMMAND("editor/editor_2d", TTR("Open 2D Editor"), KeyModifierMask::CTRL | Key::F1);
7934 ED_SHORTCUT_AND_COMMAND("editor/editor_3d", TTR("Open 3D Editor"), KeyModifierMask::CTRL | Key::F2);
7935 ED_SHORTCUT_AND_COMMAND("editor/editor_script", TTR("Open Script Editor"), KeyModifierMask::CTRL | Key::F3);
7936 ED_SHORTCUT_AND_COMMAND("editor/editor_assetlib", TTR("Open Asset Library"), KeyModifierMask::CTRL | Key::F4);
7937
7938 ED_SHORTCUT_OVERRIDE("editor/editor_2d", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::KEY_1);
7939 ED_SHORTCUT_OVERRIDE("editor/editor_3d", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::KEY_2);
7940 ED_SHORTCUT_OVERRIDE("editor/editor_script", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::KEY_3);
7941 ED_SHORTCUT_OVERRIDE("editor/editor_assetlib", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::KEY_4);
7942
7943 ED_SHORTCUT_AND_COMMAND("editor/editor_next", TTR("Open the next Editor"));
7944 ED_SHORTCUT_AND_COMMAND("editor/editor_prev", TTR("Open the previous Editor"));
7945
7946 screenshot_timer = memnew(Timer);
7947 screenshot_timer->set_one_shot(true);
7948 screenshot_timer->set_wait_time(settings_menu->get_submenu_popup_delay() + 0.1f);
7949 screenshot_timer->connect("timeout", callable_mp(this, &EditorNode::_request_screenshot));
7950 add_child(screenshot_timer);
7951 screenshot_timer->set_owner(get_owner());
7952
7953 // Adjust spacers to center 2D / 3D / Script buttons.
7954 int max_w = MAX(project_run_bar->get_minimum_size().x + right_menu_hb->get_minimum_size().x, main_menu->get_minimum_size().x);
7955 left_spacer->set_custom_minimum_size(Size2(MAX(0, max_w - main_menu->get_minimum_size().x), 0));
7956 right_spacer->set_custom_minimum_size(Size2(MAX(0, max_w - project_run_bar->get_minimum_size().x - right_menu_hb->get_minimum_size().x), 0));
7957
7958 // Extend menu bar to window title.
7959 if (can_expand) {
7960 DisplayServer::get_singleton()->window_set_flag(DisplayServer::WINDOW_FLAG_EXTEND_TO_TITLE, true, DisplayServer::MAIN_WINDOW_ID);
7961 title_bar->set_can_move_window(true);
7962 }
7963
7964 String exec = OS::get_singleton()->get_executable_path();
7965 // Save editor executable path for third-party tools.
7966 EditorSettings::get_singleton()->set_project_metadata("editor_metadata", "executable_path", exec);
7967}
7968
7969EditorNode::~EditorNode() {
7970 EditorInspector::cleanup_plugins();
7971 EditorTranslationParser::get_singleton()->clean_parsers();
7972 ResourceImporterScene::clean_up_importer_plugins();
7973
7974 remove_print_handler(&print_handler);
7975 EditorHelp::cleanup_doc();
7976 memdelete(editor_selection);
7977 memdelete(editor_plugins_over);
7978 memdelete(editor_plugins_force_over);
7979 memdelete(editor_plugins_force_input_forwarding);
7980 memdelete(progress_hb);
7981
7982 EditorSettings::destroy();
7983
7984 GDExtensionEditorPlugins::editor_node_add_plugin = nullptr;
7985 GDExtensionEditorPlugins::editor_node_remove_plugin = nullptr;
7986
7987 singleton = nullptr;
7988}
7989
7990/*
7991 * EDITOR PLUGIN LIST
7992 */
7993
7994void EditorPluginList::make_visible(bool p_visible) {
7995 for (int i = 0; i < plugins_list.size(); i++) {
7996 plugins_list[i]->make_visible(p_visible);
7997 }
7998}
7999
8000void EditorPluginList::edit(Object *p_object) {
8001 for (int i = 0; i < plugins_list.size(); i++) {
8002 plugins_list[i]->edit(p_object);
8003 }
8004}
8005
8006bool EditorPluginList::forward_gui_input(const Ref<InputEvent> &p_event) {
8007 bool discard = false;
8008
8009 for (int i = 0; i < plugins_list.size(); i++) {
8010 if (plugins_list[i]->forward_canvas_gui_input(p_event)) {
8011 discard = true;
8012 }
8013 }
8014
8015 return discard;
8016}
8017
8018EditorPlugin::AfterGUIInput EditorPluginList::forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event, bool serve_when_force_input_enabled) {
8019 EditorPlugin::AfterGUIInput after = EditorPlugin::AFTER_GUI_INPUT_PASS;
8020
8021 for (int i = 0; i < plugins_list.size(); i++) {
8022 if ((!serve_when_force_input_enabled) && plugins_list[i]->is_input_event_forwarding_always_enabled()) {
8023 continue;
8024 }
8025
8026 EditorPlugin::AfterGUIInput current_after = plugins_list[i]->forward_3d_gui_input(p_camera, p_event);
8027 if (current_after == EditorPlugin::AFTER_GUI_INPUT_STOP) {
8028 after = EditorPlugin::AFTER_GUI_INPUT_STOP;
8029 }
8030 if (after != EditorPlugin::AFTER_GUI_INPUT_STOP && current_after == EditorPlugin::AFTER_GUI_INPUT_CUSTOM) {
8031 after = EditorPlugin::AFTER_GUI_INPUT_CUSTOM;
8032 }
8033 }
8034
8035 return after;
8036}
8037
8038void EditorPluginList::forward_canvas_draw_over_viewport(Control *p_overlay) {
8039 for (int i = 0; i < plugins_list.size(); i++) {
8040 plugins_list[i]->forward_canvas_draw_over_viewport(p_overlay);
8041 }
8042}
8043
8044void EditorPluginList::forward_canvas_force_draw_over_viewport(Control *p_overlay) {
8045 for (int i = 0; i < plugins_list.size(); i++) {
8046 plugins_list[i]->forward_canvas_force_draw_over_viewport(p_overlay);
8047 }
8048}
8049
8050void EditorPluginList::forward_3d_draw_over_viewport(Control *p_overlay) {
8051 for (int i = 0; i < plugins_list.size(); i++) {
8052 plugins_list[i]->forward_3d_draw_over_viewport(p_overlay);
8053 }
8054}
8055
8056void EditorPluginList::forward_3d_force_draw_over_viewport(Control *p_overlay) {
8057 for (int i = 0; i < plugins_list.size(); i++) {
8058 plugins_list[i]->forward_3d_force_draw_over_viewport(p_overlay);
8059 }
8060}
8061
8062void EditorPluginList::add_plugin(EditorPlugin *p_plugin) {
8063 ERR_FAIL_COND(plugins_list.has(p_plugin));
8064 plugins_list.push_back(p_plugin);
8065}
8066
8067void EditorPluginList::remove_plugin(EditorPlugin *p_plugin) {
8068 plugins_list.erase(p_plugin);
8069}
8070
8071bool EditorPluginList::is_empty() {
8072 return plugins_list.is_empty();
8073}
8074
8075void EditorPluginList::clear() {
8076 plugins_list.clear();
8077}
8078
8079EditorPluginList::EditorPluginList() {
8080}
8081
8082EditorPluginList::~EditorPluginList() {
8083}
8084