/**************************************************************************/ /* editor_node.cpp */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /**************************************************************************/ /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ #include "editor_node.h" #include "core/config/project_settings.h" #include "core/extension/gdextension_manager.h" #include "core/input/input.h" #include "core/io/config_file.h" #include "core/io/file_access.h" #include "core/io/resource_loader.h" #include "core/io/resource_saver.h" #include "core/object/class_db.h" #include "core/os/keyboard.h" #include "core/os/os.h" #include "core/os/time.h" #include "core/string/print_string.h" #include "core/string/translation_server.h" #include "core/version.h" #include "editor/editor_string_names.h" #include "main/main.h" #include "scene/3d/bone_attachment_3d.h" #include "scene/animation/animation_tree.h" #include "scene/gui/color_picker.h" #include "scene/gui/dialogs.h" #include "scene/gui/file_dialog.h" #include "scene/gui/link_button.h" #include "scene/gui/menu_bar.h" #include "scene/gui/menu_button.h" #include "scene/gui/panel.h" #include "scene/gui/panel_container.h" #include "scene/gui/popup.h" #include "scene/gui/rich_text_label.h" #include "scene/gui/split_container.h" #include "scene/gui/tab_container.h" #include "scene/main/window.h" #include "scene/property_utils.h" #include "scene/resources/image_texture.h" #include "scene/resources/packed_scene.h" #include "scene/resources/portable_compressed_texture.h" #include "scene/theme/theme_db.h" #include "servers/display_server.h" #include "servers/navigation_server_3d.h" #include "servers/physics_server_2d.h" #include "servers/rendering_server.h" #include "editor/audio_stream_preview.h" #include "editor/debugger/editor_debugger_node.h" #include "editor/debugger/script_editor_debugger.h" #include "editor/dependency_editor.h" #include "editor/editor_about.h" #include "editor/editor_audio_buses.h" #include "editor/editor_build_profile.h" #include "editor/editor_command_palette.h" #include "editor/editor_data.h" #include "editor/editor_dock_manager.h" #include "editor/editor_feature_profile.h" #include "editor/editor_folding.h" #include "editor/editor_help.h" #include "editor/editor_inspector.h" #include "editor/editor_interface.h" #include "editor/editor_layouts_dialog.h" #include "editor/editor_log.h" #include "editor/editor_native_shader_source_visualizer.h" #include "editor/editor_paths.h" #include "editor/editor_properties.h" #include "editor/editor_property_name_processor.h" #include "editor/editor_quick_open.h" #include "editor/editor_resource_preview.h" #include "editor/editor_run.h" #include "editor/editor_run_native.h" #include "editor/editor_settings.h" #include "editor/editor_settings_dialog.h" #include "editor/editor_translation_parser.h" #include "editor/editor_undo_redo_manager.h" #include "editor/export/editor_export.h" #include "editor/export/export_template_manager.h" #include "editor/export/project_export.h" #include "editor/fbx_importer_manager.h" #include "editor/filesystem_dock.h" #include "editor/gui/editor_bottom_panel.h" #include "editor/gui/editor_file_dialog.h" #include "editor/gui/editor_run_bar.h" #include "editor/gui/editor_scene_tabs.h" #include "editor/gui/editor_title_bar.h" #include "editor/gui/editor_toaster.h" #include "editor/history_dock.h" #include "editor/import/3d/editor_import_collada.h" #include "editor/import/3d/resource_importer_obj.h" #include "editor/import/3d/resource_importer_scene.h" #include "editor/import/3d/scene_import_settings.h" #include "editor/import/audio_stream_import_settings.h" #include "editor/import/dynamic_font_import_settings.h" #include "editor/import/resource_importer_bitmask.h" #include "editor/import/resource_importer_bmfont.h" #include "editor/import/resource_importer_csv_translation.h" #include "editor/import/resource_importer_dynamic_font.h" #include "editor/import/resource_importer_image.h" #include "editor/import/resource_importer_imagefont.h" #include "editor/import/resource_importer_layered_texture.h" #include "editor/import/resource_importer_shader_file.h" #include "editor/import/resource_importer_texture.h" #include "editor/import/resource_importer_texture_atlas.h" #include "editor/import/resource_importer_wav.h" #include "editor/import_dock.h" #include "editor/inspector_dock.h" #include "editor/multi_node_edit.h" #include "editor/node_dock.h" #include "editor/plugins/animation_player_editor_plugin.h" #include "editor/plugins/asset_library_editor_plugin.h" #include "editor/plugins/canvas_item_editor_plugin.h" #include "editor/plugins/debugger_editor_plugin.h" #include "editor/plugins/dedicated_server_export_plugin.h" #include "editor/plugins/editor_plugin.h" #include "editor/plugins/editor_preview_plugins.h" #include "editor/plugins/editor_resource_conversion_plugin.h" #include "editor/plugins/gdextension_export_plugin.h" #include "editor/plugins/material_editor_plugin.h" #include "editor/plugins/mesh_library_editor_plugin.h" #include "editor/plugins/node_3d_editor_plugin.h" #include "editor/plugins/packed_scene_translation_parser_plugin.h" #include "editor/plugins/particle_process_material_editor_plugin.h" #include "editor/plugins/plugin_config_dialog.h" #include "editor/plugins/root_motion_editor_plugin.h" #include "editor/plugins/script_text_editor.h" #include "editor/plugins/text_editor.h" #include "editor/plugins/version_control_editor_plugin.h" #include "editor/plugins/visual_shader_editor_plugin.h" #include "editor/progress_dialog.h" #include "editor/project_settings_editor.h" #include "editor/register_exporters.h" #include "editor/scene_tree_dock.h" #include "editor/surface_upgrade_tool.h" #include "editor/themes/editor_scale.h" #include "editor/themes/editor_theme_manager.h" #include "editor/window_wrapper.h" #include #include #include "modules/modules_enabled.gen.h" // For gdscript, mono. EditorNode *EditorNode::singleton = nullptr; static const String EDITOR_NODE_CONFIG_SECTION = "EditorNode"; static const String REMOVE_ANDROID_BUILD_TEMPLATE_MESSAGE = "The Android build template is already installed in this project and it won't be overwritten.\nRemove the \"%s\" directory manually before attempting this operation again."; static const String INSTALL_ANDROID_BUILD_TEMPLATE_MESSAGE = "This will set up your project for gradle Android builds by installing the source template to \"%s\".\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."; bool EditorProgress::step(const String &p_state, int p_step, bool p_force_refresh) { if (Thread::is_main_thread()) { return EditorNode::progress_task_step(task, p_state, p_step, p_force_refresh); } else { EditorNode::progress_task_step_bg(task, p_step); return false; } } EditorProgress::EditorProgress(const String &p_task, const String &p_label, int p_amount, bool p_can_cancel) { if (Thread::is_main_thread()) { EditorNode::progress_add_task(p_task, p_label, p_amount, p_can_cancel); } else { EditorNode::progress_add_task_bg(p_task, p_label, p_amount); } task = p_task; } EditorProgress::~EditorProgress() { if (Thread::is_main_thread()) { EditorNode::progress_end_task(task); } else { EditorNode::progress_end_task_bg(task); } } void EditorNode::disambiguate_filenames(const Vector p_full_paths, Vector &r_filenames) { 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())); // Keep track of a list of "index sets," i.e. sets of indices // within disambiguated_scene_names which contain the same name. Vector> index_sets; HashMap scene_name_to_set_index; for (int i = 0; i < r_filenames.size(); i++) { const String &scene_name = r_filenames[i]; if (!scene_name_to_set_index.has(scene_name)) { index_sets.append(RBSet()); scene_name_to_set_index.insert(r_filenames[i], index_sets.size() - 1); } index_sets.write[scene_name_to_set_index[scene_name]].insert(i); } // For each index set with a size > 1, we need to disambiguate. for (int i = 0; i < index_sets.size(); i++) { RBSet iset = index_sets[i]; while (iset.size() > 1) { // Append the parent folder to each scene name. for (const int &E : iset) { int set_idx = E; String scene_name = r_filenames[set_idx]; String full_path = p_full_paths[set_idx]; // Get rid of file extensions and res:// prefixes. scene_name = scene_name.get_basename(); if (full_path.begins_with("res://")) { full_path = full_path.substr(6); } full_path = full_path.get_basename(); // Normalize trailing slashes when normalizing directory names. scene_name = scene_name.trim_suffix("/"); full_path = full_path.trim_suffix("/"); int scene_name_size = scene_name.size(); int full_path_size = full_path.size(); int difference = full_path_size - scene_name_size; // Find just the parent folder of the current path and append it. // If the current name is foo.tscn, and the full path is /some/folder/foo.tscn // then slash_idx is the second '/', so that we select just "folder", and // append that to yield "folder/foo.tscn". if (difference > 0) { String parent = full_path.substr(0, difference); int slash_idx = parent.rfind("/"); slash_idx = parent.rfind("/", slash_idx - 1); parent = (slash_idx >= 0 && parent.length() > 1) ? parent.substr(slash_idx + 1) : parent; r_filenames.write[set_idx] = parent + r_filenames[set_idx]; } } // Loop back through scene names and remove non-ambiguous names. bool can_proceed = false; RBSet::Element *E = iset.front(); while (E) { String scene_name = r_filenames[E->get()]; bool duplicate_found = false; for (const int &F : iset) { if (E->get() == F) { continue; } const String &other_scene_name = r_filenames[F]; if (other_scene_name == scene_name) { duplicate_found = true; break; } } RBSet::Element *to_erase = duplicate_found ? nullptr : E; // We need to check that we could actually append anymore names // if we wanted to for disambiguation. If we can't, then we have // to abort even with ambiguous names. We clean the full path // and the scene name first to remove extensions so that this // comparison actually works. String path = p_full_paths[E->get()]; // Get rid of file extensions and res:// prefixes. scene_name = scene_name.get_basename(); if (path.begins_with("res://")) { path = path.substr(6); } path = path.get_basename(); // Normalize trailing slashes when normalizing directory names. scene_name = scene_name.trim_suffix("/"); path = path.trim_suffix("/"); // We can proceed if the full path is longer than the scene name, // meaning that there is at least one more parent folder we can // tack onto the name. can_proceed = can_proceed || (path.size() - scene_name.size()) >= 1; E = E->next(); if (to_erase) { iset.erase(to_erase); } } if (!can_proceed) { break; } } } } void EditorNode::_version_control_menu_option(int p_idx) { switch (vcs_actions_menu->get_item_id(p_idx)) { case RUN_VCS_METADATA: { VersionControlEditorPlugin::get_singleton()->popup_vcs_metadata_dialog(); } break; case RUN_VCS_SETTINGS: { VersionControlEditorPlugin::get_singleton()->popup_vcs_set_up_dialog(gui_base); } break; } } void EditorNode::_update_title() { const String appname = GLOBAL_GET("application/config/name"); String title = (appname.is_empty() ? TTR("Unnamed Project") : appname); const String edited = editor_data.get_edited_scene_root() ? editor_data.get_edited_scene_root()->get_scene_file_path() : String(); if (!edited.is_empty()) { // Display the edited scene name before the program name so that it can be seen in the OS task bar. title = vformat("%s - %s", edited.get_file(), title); } if (unsaved_cache) { // Display the "modified" mark before anything else so that it can always be seen in the OS task bar. title = vformat("(*) %s", title); } DisplayServer::get_singleton()->window_set_title(title + String(" - ") + VERSION_NAME); if (project_title) { project_title->set_text(title); } } void EditorNode::shortcut_input(const Ref &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref k = p_event; if ((k.is_valid() && k->is_pressed() && !k->is_echo()) || Object::cast_to(*p_event)) { bool is_handled = true; if (ED_IS_SHORTCUT("editor/filter_files", p_event)) { FileSystemDock::get_singleton()->focus_on_filter(); } else if (ED_IS_SHORTCUT("editor/editor_2d", p_event)) { editor_select(EDITOR_2D); } else if (ED_IS_SHORTCUT("editor/editor_3d", p_event)) { editor_select(EDITOR_3D); } else if (ED_IS_SHORTCUT("editor/editor_script", p_event)) { editor_select(EDITOR_SCRIPT); } else if (ED_IS_SHORTCUT("editor/editor_help", p_event)) { emit_signal(SNAME("request_help_search"), ""); } else if (ED_IS_SHORTCUT("editor/editor_assetlib", p_event) && AssetLibraryEditorPlugin::is_available()) { editor_select(EDITOR_ASSETLIB); } else if (ED_IS_SHORTCUT("editor/editor_next", p_event)) { _editor_select_next(); } else if (ED_IS_SHORTCUT("editor/editor_prev", p_event)) { _editor_select_prev(); } else if (ED_IS_SHORTCUT("editor/command_palette", p_event)) { _open_command_palette(); } else if (ED_IS_SHORTCUT("editor/toggle_last_opened_bottom_panel", p_event)) { bottom_panel->toggle_last_opened_bottom_panel(); } else { is_handled = false; } if (is_handled) { get_tree()->get_root()->set_input_as_handled(); } } } void EditorNode::_update_vsync_mode() { const DisplayServer::VSyncMode window_vsync_mode = DisplayServer::VSyncMode(int(EDITOR_GET("interface/editor/vsync_mode"))); DisplayServer::get_singleton()->window_set_vsync_mode(window_vsync_mode); } void EditorNode::_update_from_settings() { _update_title(); int current_filter = GLOBAL_GET("rendering/textures/canvas_textures/default_texture_filter"); if (current_filter != scene_root->get_default_canvas_item_texture_filter()) { Viewport::DefaultCanvasItemTextureFilter tf = (Viewport::DefaultCanvasItemTextureFilter)current_filter; scene_root->set_default_canvas_item_texture_filter(tf); } int current_repeat = GLOBAL_GET("rendering/textures/canvas_textures/default_texture_repeat"); if (current_repeat != scene_root->get_default_canvas_item_texture_repeat()) { Viewport::DefaultCanvasItemTextureRepeat tr = (Viewport::DefaultCanvasItemTextureRepeat)current_repeat; scene_root->set_default_canvas_item_texture_repeat(tr); } RS::DOFBokehShape dof_shape = RS::DOFBokehShape(int(GLOBAL_GET("rendering/camera/depth_of_field/depth_of_field_bokeh_shape"))); RS::get_singleton()->camera_attributes_set_dof_blur_bokeh_shape(dof_shape); RS::DOFBlurQuality dof_quality = RS::DOFBlurQuality(int(GLOBAL_GET("rendering/camera/depth_of_field/depth_of_field_bokeh_quality"))); bool dof_jitter = GLOBAL_GET("rendering/camera/depth_of_field/depth_of_field_use_jitter"); RS::get_singleton()->camera_attributes_set_dof_blur_quality(dof_quality, dof_jitter); 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")); 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")); bool glow_bicubic = int(GLOBAL_GET("rendering/environment/glow/upscale_mode")) > 0; 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")); RS::get_singleton()->environment_glow_set_use_bicubic_upscale(glow_bicubic); RS::EnvironmentSSRRoughnessQuality ssr_roughness_quality = RS::EnvironmentSSRRoughnessQuality(int(GLOBAL_GET("rendering/environment/screen_space_reflection/roughness_quality"))); RS::get_singleton()->environment_set_ssr_roughness_quality(ssr_roughness_quality); RS::SubSurfaceScatteringQuality sss_quality = RS::SubSurfaceScatteringQuality(int(GLOBAL_GET("rendering/environment/subsurface_scattering/subsurface_scattering_quality"))); RS::get_singleton()->sub_surface_scattering_set_quality(sss_quality); float sss_scale = GLOBAL_GET("rendering/environment/subsurface_scattering/subsurface_scattering_scale"); float sss_depth_scale = GLOBAL_GET("rendering/environment/subsurface_scattering/subsurface_scattering_depth_scale"); RS::get_singleton()->sub_surface_scattering_set_scale(sss_scale, sss_depth_scale); uint32_t directional_shadow_size = GLOBAL_GET("rendering/lights_and_shadows/directional_shadow/size"); uint32_t directional_shadow_16_bits = GLOBAL_GET("rendering/lights_and_shadows/directional_shadow/16_bits"); RS::get_singleton()->directional_shadow_atlas_set_size(directional_shadow_size, directional_shadow_16_bits); RS::ShadowQuality shadows_quality = RS::ShadowQuality(int(GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/soft_shadow_filter_quality"))); RS::get_singleton()->positional_soft_shadow_filter_set_quality(shadows_quality); RS::ShadowQuality directional_shadow_quality = RS::ShadowQuality(int(GLOBAL_GET("rendering/lights_and_shadows/directional_shadow/soft_shadow_filter_quality"))); RS::get_singleton()->directional_soft_shadow_filter_set_quality(directional_shadow_quality); float probe_update_speed = GLOBAL_GET("rendering/lightmapping/probe_capture/update_speed"); RS::get_singleton()->lightmap_set_probe_capture_update_speed(probe_update_speed); RS::EnvironmentSDFGIFramesToConverge frames_to_converge = RS::EnvironmentSDFGIFramesToConverge(int(GLOBAL_GET("rendering/global_illumination/sdfgi/frames_to_converge"))); RS::get_singleton()->environment_set_sdfgi_frames_to_converge(frames_to_converge); RS::EnvironmentSDFGIRayCount ray_count = RS::EnvironmentSDFGIRayCount(int(GLOBAL_GET("rendering/global_illumination/sdfgi/probe_ray_count"))); RS::get_singleton()->environment_set_sdfgi_ray_count(ray_count); RS::VoxelGIQuality voxel_gi_quality = RS::VoxelGIQuality(int(GLOBAL_GET("rendering/global_illumination/voxel_gi/quality"))); RS::get_singleton()->voxel_gi_set_quality(voxel_gi_quality); 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")); RS::get_singleton()->environment_set_volumetric_fog_filter_active(bool(GLOBAL_GET("rendering/environment/volumetric_fog/use_filter"))); RS::get_singleton()->canvas_set_shadow_texture_size(GLOBAL_GET("rendering/2d/shadow_atlas/size")); bool use_half_res_gi = GLOBAL_GET("rendering/global_illumination/gi/use_half_resolution"); RS::get_singleton()->gi_set_use_half_resolution(use_half_res_gi); bool snap_2d_transforms = GLOBAL_GET("rendering/2d/snap/snap_2d_transforms_to_pixel"); scene_root->set_snap_2d_transforms_to_pixel(snap_2d_transforms); bool snap_2d_vertices = GLOBAL_GET("rendering/2d/snap/snap_2d_vertices_to_pixel"); scene_root->set_snap_2d_vertices_to_pixel(snap_2d_vertices); Viewport::SDFOversize sdf_oversize = Viewport::SDFOversize(int(GLOBAL_GET("rendering/2d/sdf/oversize"))); scene_root->set_sdf_oversize(sdf_oversize); Viewport::SDFScale sdf_scale = Viewport::SDFScale(int(GLOBAL_GET("rendering/2d/sdf/scale"))); scene_root->set_sdf_scale(sdf_scale); Viewport::MSAA msaa = Viewport::MSAA(int(GLOBAL_GET("rendering/anti_aliasing/quality/msaa_2d"))); scene_root->set_msaa_2d(msaa); bool use_hdr_2d = GLOBAL_GET("rendering/viewport/hdr_2d"); scene_root->set_use_hdr_2d(use_hdr_2d); float mesh_lod_threshold = GLOBAL_GET("rendering/mesh_lod/lod_change/threshold_pixels"); scene_root->set_mesh_lod_threshold(mesh_lod_threshold); RS::get_singleton()->decals_set_filter(RS::DecalFilter(int(GLOBAL_GET("rendering/textures/decals/filter")))); RS::get_singleton()->light_projectors_set_filter(RS::LightProjectorFilter(int(GLOBAL_GET("rendering/textures/light_projectors/filter")))); RS::get_singleton()->lightmaps_set_bicubic_filter(GLOBAL_GET("rendering/lightmapping/lightmap_gi/use_bicubic_filter")); SceneTree *tree = get_tree(); tree->set_debug_collisions_color(GLOBAL_GET("debug/shapes/collision/shape_color")); tree->set_debug_collision_contact_color(GLOBAL_GET("debug/shapes/collision/contact_color")); ResourceImporterTexture::get_singleton()->update_imports(); #ifdef DEBUG_ENABLED NavigationServer3D::get_singleton()->set_debug_navigation_edge_connection_color(GLOBAL_GET("debug/shapes/navigation/edge_connection_color")); NavigationServer3D::get_singleton()->set_debug_navigation_geometry_edge_color(GLOBAL_GET("debug/shapes/navigation/geometry_edge_color")); NavigationServer3D::get_singleton()->set_debug_navigation_geometry_face_color(GLOBAL_GET("debug/shapes/navigation/geometry_face_color")); NavigationServer3D::get_singleton()->set_debug_navigation_geometry_edge_disabled_color(GLOBAL_GET("debug/shapes/navigation/geometry_edge_disabled_color")); NavigationServer3D::get_singleton()->set_debug_navigation_geometry_face_disabled_color(GLOBAL_GET("debug/shapes/navigation/geometry_face_disabled_color")); NavigationServer3D::get_singleton()->set_debug_navigation_enable_edge_connections(GLOBAL_GET("debug/shapes/navigation/enable_edge_connections")); NavigationServer3D::get_singleton()->set_debug_navigation_enable_edge_connections_xray(GLOBAL_GET("debug/shapes/navigation/enable_edge_connections_xray")); NavigationServer3D::get_singleton()->set_debug_navigation_enable_edge_lines(GLOBAL_GET("debug/shapes/navigation/enable_edge_lines")); NavigationServer3D::get_singleton()->set_debug_navigation_enable_edge_lines_xray(GLOBAL_GET("debug/shapes/navigation/enable_edge_lines_xray")); NavigationServer3D::get_singleton()->set_debug_navigation_enable_geometry_face_random_color(GLOBAL_GET("debug/shapes/navigation/enable_geometry_face_random_color")); #endif // DEBUG_ENABLED } void EditorNode::_gdextensions_reloaded() { // In case the developer is inspecting an object that will be changed by the reload. InspectorDock::get_inspector_singleton()->update_tree(); // Regenerate documentation. EditorHelp::generate_doc(); } void EditorNode::_select_default_main_screen_plugin() { if (EDITOR_3D < main_editor_buttons.size() && main_editor_buttons[EDITOR_3D]->is_visible()) { // If the 3D editor is enabled, use this as the default. editor_select(EDITOR_3D); return; } // Switch to the first main screen plugin that is enabled. Usually this is // 2D, but may be subsequent ones if 2D is disabled in the feature profile. for (int i = 0; i < main_editor_buttons.size(); i++) { Button *editor_button = main_editor_buttons[i]; if (editor_button->is_visible()) { editor_select(i); return; } } editor_select(-1); } void EditorNode::_update_theme(bool p_skip_creation) { if (!p_skip_creation) { theme = EditorThemeManager::generate_theme(theme); DisplayServer::set_early_window_clear_color_override(true, theme->get_color(SNAME("background"), EditorStringName(Editor))); } Vector> editor_themes; editor_themes.push_back(theme); editor_themes.push_back(ThemeDB::get_singleton()->get_default_theme()); ThemeContext *node_tc = ThemeDB::get_singleton()->get_theme_context(this); if (node_tc) { node_tc->set_themes(editor_themes); } else { ThemeDB::get_singleton()->create_theme_context(this, editor_themes); } Window *window = get_window(); if (window) { ThemeContext *window_tc = ThemeDB::get_singleton()->get_theme_context(window); if (window_tc) { window_tc->set_themes(editor_themes); } else { ThemeDB::get_singleton()->create_theme_context(window, editor_themes); } } if (CanvasItemEditor::get_singleton()->get_theme_preview() == CanvasItemEditor::THEME_PREVIEW_EDITOR) { update_preview_themes(CanvasItemEditor::THEME_PREVIEW_EDITOR); } // Update styles. { gui_base->add_theme_style_override(SceneStringName(panel), theme->get_stylebox(SNAME("Background"), EditorStringName(EditorStyles))); main_vbox->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, theme->get_constant(SNAME("window_border_margin"), EditorStringName(Editor))); main_vbox->add_theme_constant_override("separation", theme->get_constant(SNAME("top_bar_separation"), EditorStringName(Editor))); scene_root_parent->add_theme_style_override(SceneStringName(panel), theme->get_stylebox(SNAME("Content"), EditorStringName(EditorStyles))); bottom_panel->add_theme_style_override(SceneStringName(panel), theme->get_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles))); distraction_free->set_icon(theme->get_icon(SNAME("DistractionFree"), EditorStringName(EditorIcons))); distraction_free->add_theme_style_override(SceneStringName(pressed), theme->get_stylebox(CoreStringName(normal), "FlatMenuButton")); help_menu->set_item_icon(help_menu->get_item_index(HELP_SEARCH), theme->get_icon(SNAME("HelpSearch"), EditorStringName(EditorIcons))); help_menu->set_item_icon(help_menu->get_item_index(HELP_COPY_SYSTEM_INFO), theme->get_icon(SNAME("ActionCopy"), EditorStringName(EditorIcons))); help_menu->set_item_icon(help_menu->get_item_index(HELP_ABOUT), theme->get_icon(SNAME("Godot"), EditorStringName(EditorIcons))); help_menu->set_item_icon(help_menu->get_item_index(HELP_SUPPORT_GODOT_DEVELOPMENT), theme->get_icon(SNAME("Heart"), EditorStringName(EditorIcons))); if (EditorDebuggerNode::get_singleton()->is_visible()) { bottom_panel->add_theme_style_override(SceneStringName(panel), theme->get_stylebox(SNAME("BottomPanelDebuggerOverride"), EditorStringName(EditorStyles))); } for (int i = 0; i < main_editor_buttons.size(); i++) { Button *tb = main_editor_buttons[i]; EditorPlugin *p_editor = editor_table[i]; Ref icon = p_editor->get_icon(); if (icon.is_valid()) { tb->set_icon(icon); } else if (theme->has_icon(p_editor->get_name(), EditorStringName(EditorIcons))) { tb->set_icon(theme->get_icon(p_editor->get_name(), EditorStringName(EditorIcons))); } } _update_renderer_color(); } editor_dock_manager->update_tab_styles(); editor_dock_manager->set_tab_icon_max_width(theme->get_constant(SNAME("class_icon_size"), EditorStringName(Editor))); } void EditorNode::update_preview_themes(int p_mode) { if (!scene_root->is_inside_tree()) { return; // Too early. } Vector> preview_themes; switch (p_mode) { case CanvasItemEditor::THEME_PREVIEW_PROJECT: preview_themes.push_back(ThemeDB::get_singleton()->get_project_theme()); break; case CanvasItemEditor::THEME_PREVIEW_EDITOR: preview_themes.push_back(get_editor_theme()); break; default: break; } preview_themes.push_back(ThemeDB::get_singleton()->get_default_theme()); ThemeContext *preview_context = ThemeDB::get_singleton()->get_theme_context(scene_root); if (preview_context) { preview_context->set_themes(preview_themes); } else { ThemeDB::get_singleton()->create_theme_context(scene_root, preview_themes); } } void EditorNode::_notification(int p_what) { switch (p_what) { case NOTIFICATION_POSTINITIALIZE: { EditorHelp::generate_doc(); #if defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED) EditorHelpHighlighter::create_singleton(); #endif } break; case NOTIFICATION_PROCESS: { if (opening_prev && !confirmation->is_visible()) { opening_prev = false; } bool global_unsaved = EditorUndoRedoManager::get_singleton()->is_history_unsaved(EditorUndoRedoManager::GLOBAL_HISTORY); bool scene_or_global_unsaved = global_unsaved || EditorUndoRedoManager::get_singleton()->is_history_unsaved(editor_data.get_current_edited_scene_history_id()); if (unsaved_cache != scene_or_global_unsaved) { unsaved_cache = scene_or_global_unsaved; _update_title(); } if (editor_data.is_scene_changed(-1)) { scene_tabs->update_scene_tabs(); } // Update the animation frame of the update spinner. uint64_t frame = Engine::get_singleton()->get_frames_drawn(); uint64_t tick = OS::get_singleton()->get_ticks_msec(); if (frame != update_spinner_step_frame && (tick - update_spinner_step_msec) > (1000 / 8)) { update_spinner_step++; if (update_spinner_step >= 8) { update_spinner_step = 0; } update_spinner_step_msec = tick; update_spinner_step_frame = frame + 1; // Update the icon itself only when the spinner is visible. if (_should_display_update_spinner()) { update_spinner->set_icon(theme->get_icon("Progress" + itos(update_spinner_step + 1), EditorStringName(EditorIcons))); } } editor_selection->update(); ResourceImporterTexture::get_singleton()->update_imports(); if (requested_first_scan) { requested_first_scan = false; OS::get_singleton()->benchmark_begin_measure("Editor", "First Scan"); if (run_surface_upgrade_tool) { run_surface_upgrade_tool = false; SurfaceUpgradeTool::get_singleton()->connect("upgrade_finished", callable_mp(EditorFileSystem::get_singleton(), &EditorFileSystem::scan), CONNECT_ONE_SHOT); SurfaceUpgradeTool::get_singleton()->finish_upgrade(); } else { EditorFileSystem::get_singleton()->scan(); } } } break; case NOTIFICATION_ENTER_TREE: { get_tree()->set_disable_node_threading(true); // No node threading while running editor. Engine::get_singleton()->set_editor_hint(true); Window *window = get_window(); if (window) { // Handle macOS fullscreen and extend-to-title changes. window->connect("titlebar_changed", callable_mp(this, &EditorNode::_titlebar_resized)); } // Theme has already been created in the constructor, so we can skip that step. _update_theme(true); OS::get_singleton()->set_low_processor_usage_mode_sleep_usec(int(EDITOR_GET("interface/editor/low_processor_mode_sleep_usec"))); get_tree()->get_root()->set_as_audio_listener_3d(false); get_tree()->get_root()->set_as_audio_listener_2d(false); get_tree()->get_root()->set_snap_2d_transforms_to_pixel(false); get_tree()->get_root()->set_snap_2d_vertices_to_pixel(false); get_tree()->set_auto_accept_quit(false); #ifdef ANDROID_ENABLED get_tree()->set_quit_on_go_back(false); #endif get_tree()->get_root()->connect("files_dropped", callable_mp(this, &EditorNode::_dropped_files)); command_palette->register_shortcuts_as_command(); callable_mp(this, &EditorNode::_begin_first_scan).call_deferred(); last_dark_mode_state = DisplayServer::get_singleton()->is_dark_mode(); last_system_accent_color = DisplayServer::get_singleton()->get_accent_color(); last_system_base_color = DisplayServer::get_singleton()->get_base_color(); DisplayServer::get_singleton()->set_system_theme_change_callback(callable_mp(this, &EditorNode::_check_system_theme_changed)); get_viewport()->connect("size_changed", callable_mp(this, &EditorNode::_viewport_resized)); /* DO NOT LOAD SCENES HERE, WAIT FOR FILE SCANNING AND REIMPORT TO COMPLETE */ } break; case NOTIFICATION_EXIT_TREE: { singleton->active_plugins.clear(); if (progress_dialog) { progress_dialog->queue_free(); } if (load_error_dialog) { load_error_dialog->queue_free(); } if (execute_output_dialog) { execute_output_dialog->queue_free(); } if (warning) { warning->queue_free(); } if (accept) { accept->queue_free(); } if (save_accept) { save_accept->queue_free(); } editor_data.save_editor_external_data(); FileAccess::set_file_close_fail_notify_callback(nullptr); log->deinit(); // Do not get messages anymore. editor_data.clear_edited_scenes(); get_viewport()->disconnect("size_changed", callable_mp(this, &EditorNode::_viewport_resized)); } break; case NOTIFICATION_READY: { started_timestamp = Time::get_singleton()->get_unix_time_from_system(); RenderingServer::get_singleton()->viewport_set_disable_2d(get_scene_root()->get_viewport_rid(), true); RenderingServer::get_singleton()->viewport_set_environment_mode(get_viewport()->get_viewport_rid(), RenderingServer::VIEWPORT_ENVIRONMENT_DISABLED); DisplayServer::get_singleton()->screen_set_keep_on(EDITOR_GET("interface/editor/keep_screen_on")); feature_profile_manager->notify_changed(); _select_default_main_screen_plugin(); // Save the project after opening to mark it as last modified, except in headless mode. if (DisplayServer::get_singleton()->window_can_draw()) { ProjectSettings::get_singleton()->save(); } _titlebar_resized(); // Set up a theme context for the 2D preview viewport using the stored preview theme. CanvasItemEditor::ThemePreviewMode theme_preview_mode = (CanvasItemEditor::ThemePreviewMode)(int)EditorSettings::get_singleton()->get_project_metadata("2d_editor", "theme_preview", CanvasItemEditor::THEME_PREVIEW_PROJECT); update_preview_themes(theme_preview_mode); /* DO NOT LOAD SCENES HERE, WAIT FOR FILE SCANNING AND REIMPORT TO COMPLETE */ } break; case NOTIFICATION_APPLICATION_FOCUS_IN: { // Restore the original FPS cap after focusing back on the editor. OS::get_singleton()->set_low_processor_usage_mode_sleep_usec(int(EDITOR_GET("interface/editor/low_processor_mode_sleep_usec"))); EditorFileSystem::get_singleton()->scan_changes(); _scan_external_changes(); GDExtensionManager *gdextension_manager = GDExtensionManager::get_singleton(); callable_mp(gdextension_manager, &GDExtensionManager::reload_extensions).call_deferred(); } break; case NOTIFICATION_APPLICATION_FOCUS_OUT: { // Save on focus loss before applying the FPS limit to avoid slowing down the saving process. if (EDITOR_GET("interface/editor/save_on_focus_loss")) { _menu_option_confirm(FILE_SAVE_SCENE_SILENTLY, false); } // Set a low FPS cap to decrease CPU/GPU usage while the editor is unfocused. OS::get_singleton()->set_low_processor_usage_mode_sleep_usec(int(EDITOR_GET("interface/editor/unfocused_low_processor_mode_sleep_usec"))); } break; case NOTIFICATION_WM_ABOUT: { show_about(); } break; case NOTIFICATION_WM_CLOSE_REQUEST: { _menu_option_confirm(FILE_QUIT, false); } break; case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { if (EditorSettings::get_singleton()->check_changed_settings_in_group("filesystem/file_dialog")) { FileDialog::set_default_show_hidden_files(EDITOR_GET("filesystem/file_dialog/show_hidden_files")); EditorFileDialog::set_default_show_hidden_files(EDITOR_GET("filesystem/file_dialog/show_hidden_files")); EditorFileDialog::set_default_display_mode((EditorFileDialog::DisplayMode)EDITOR_GET("filesystem/file_dialog/display_mode").operator int()); } if (EDITOR_GET("interface/editor/import_resources_when_unfocused")) { scan_changes_timer->start(); } else { scan_changes_timer->stop(); } follow_system_theme = EDITOR_GET("interface/theme/follow_system_theme"); use_system_accent_color = EDITOR_GET("interface/theme/use_system_accent_color"); if (EditorThemeManager::is_generated_theme_outdated()) { _update_theme(); _build_icon_type_cache(); recent_scenes->reset_size(); } if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor/dock_tab_style")) { editor_dock_manager->update_tab_styles(); } if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/scene_tabs")) { scene_tabs->update_scene_tabs(); } if (EditorSettings::get_singleton()->check_changed_settings_in_group("docks/filesystem")) { HashSet updated_textfile_extensions; bool extensions_match = true; const Vector textfile_ext = ((String)(EDITOR_GET("docks/filesystem/textfile_extensions"))).split(",", false); for (const String &E : textfile_ext) { updated_textfile_extensions.insert(E); if (extensions_match && !textfile_extensions.has(E)) { extensions_match = false; } } if (!extensions_match || updated_textfile_extensions.size() < textfile_extensions.size()) { textfile_extensions = updated_textfile_extensions; EditorFileSystem::get_singleton()->scan(); } } if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor")) { _update_update_spinner(); _update_vsync_mode(); DisplayServer::get_singleton()->screen_set_keep_on(EDITOR_GET("interface/editor/keep_screen_on")); } #if defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED) if (EditorSettings::get_singleton()->check_changed_settings_in_group("text_editor/theme/highlighting")) { EditorHelpHighlighter::get_singleton()->reset_cache(); } #endif } break; } } void EditorNode::_update_update_spinner() { update_spinner->set_visible(!RenderingServer::get_singleton()->canvas_item_get_debug_redraw() && _should_display_update_spinner()); const bool update_continuously = EDITOR_GET("interface/editor/update_continuously"); PopupMenu *update_popup = update_spinner->get_popup(); update_popup->set_item_checked(update_popup->get_item_index(SETTINGS_UPDATE_CONTINUOUSLY), update_continuously); update_popup->set_item_checked(update_popup->get_item_index(SETTINGS_UPDATE_WHEN_CHANGED), !update_continuously); if (update_continuously) { 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.")); // Use a different color for the update spinner when Update Continuously is enabled, // as this feature should only be enabled for troubleshooting purposes. // Make the icon modulate color overbright because icons are not completely white on a dark theme. // On a light theme, icons are dark, so we need to modulate them with an even brighter color. const bool dark_theme = EditorThemeManager::is_dark_theme(); 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))); } else { update_spinner->set_tooltip_text(TTR("Spins when the editor window redraws.")); update_spinner->set_self_modulate(Color(1, 1, 1)); } OS::get_singleton()->set_low_processor_usage_mode(!update_continuously); } void EditorNode::init_plugins() { _initializing_plugins = true; Vector addons; if (ProjectSettings::get_singleton()->has_setting("editor_plugins/enabled")) { addons = GLOBAL_GET("editor_plugins/enabled"); } for (const String &addon : addons) { set_addon_plugin_enabled(addon, true); } _initializing_plugins = false; if (!pending_addons.is_empty()) { EditorFileSystem::get_singleton()->connect("script_classes_updated", callable_mp(this, &EditorNode::_enable_pending_addons), CONNECT_ONE_SHOT); } } void EditorNode::_on_plugin_ready(Object *p_script, const String &p_activate_name) { Ref