/**************************************************************************/ /* 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/input/input.h" #include "core/io/config_file.h" #include "core/io/file_access.h" #include "core/io/image_loader.h" #include "core/io/resource_loader.h" #include "core/io/resource_saver.h" #include "core/object/class_db.h" #include "core/object/message_queue.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.h" #include "core/version.h" #include "main/main.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/split_container.h" #include "scene/gui/tab_bar.h" #include "scene/gui/tab_container.h" #include "scene/main/window.h" #include "scene/property_utils.h" #include "scene/resources/packed_scene.h" #include "servers/display_server.h" #include "servers/navigation_server_3d.h" #include "servers/physics_server_2d.h" #include "editor/audio_stream_preview.h" #include "editor/debugger/editor_debugger_node.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_feature_profile.h" #include "editor/editor_file_dialog.h" #include "editor/editor_folding.h" #include "editor/editor_help.h" #include "editor/editor_inspector.h" #include "editor/editor_layouts_dialog.h" #include "editor/editor_log.h" #include "editor/editor_paths.h" #include "editor/editor_plugin.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_scale.h" #include "editor/editor_settings.h" #include "editor/editor_settings_dialog.h" #include "editor/editor_themes.h" #include "editor/editor_toaster.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/history_dock.h" #include "editor/import/audio_stream_import_settings.h" #include "editor/import/dynamic_font_import_settings.h" #include "editor/import/editor_import_collada.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_obj.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/scene_import_settings.h" #include "editor/import_dock.h" #include "editor/inspector_dock.h" #include "editor/multi_node_edit.h" #include "editor/node_dock.h" #include "editor/plugin_config_dialog.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/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/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 #include EditorNode *EditorNode::singleton = nullptr; // The metadata key used to store and retrieve the version text to copy to the clipboard. static const String META_TEXT_TO_COPY = "text_to_copy"; 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++) { 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; } 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; } } } } // TODO: This REALLY should be done in a better way than replacing all tabs after almost EVERY action. void EditorNode::_update_scene_tabs() { bool show_rb = EDITOR_GET("interface/scene_tabs/show_script_button"); if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU)) { DisplayServer::get_singleton()->global_menu_clear("_dock"); } // Get all scene names, which may be ambiguous. Vector disambiguated_scene_names; Vector full_path_names; for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { disambiguated_scene_names.append(editor_data.get_scene_title(i)); full_path_names.append(editor_data.get_scene_path(i)); } disambiguate_filenames(full_path_names, disambiguated_scene_names); // Workaround to ignore the tab_changed signal from the first added tab. scene_tabs->disconnect("tab_changed", callable_mp(this, &EditorNode::_scene_tab_changed)); scene_tabs->clear_tabs(); Ref script_icon = gui_base->get_theme_icon(SNAME("Script"), SNAME("EditorIcons")); for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { Node *type_node = editor_data.get_edited_scene_root(i); Ref icon; if (type_node) { icon = EditorNode::get_singleton()->get_object_icon(type_node, "Node"); } bool unsaved = EditorUndoRedoManager::get_singleton()->is_history_unsaved(editor_data.get_scene_history_id(i)); scene_tabs->add_tab(disambiguated_scene_names[i] + (unsaved ? "(*)" : ""), icon); if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU)) { DisplayServer::get_singleton()->global_menu_add_item("_dock", editor_data.get_scene_title(i) + (unsaved ? "(*)" : ""), callable_mp(this, &EditorNode::_global_menu_scene), Callable(), i); } if (show_rb && editor_data.get_scene_root_script(i).is_valid()) { scene_tabs->set_tab_button_icon(i, script_icon); } } if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU)) { DisplayServer::get_singleton()->global_menu_add_separator("_dock"); DisplayServer::get_singleton()->global_menu_add_item("_dock", TTR("New Window"), callable_mp(this, &EditorNode::_global_menu_new_window)); } if (scene_tabs->get_tab_count() > 0) { scene_tabs->set_current_tab(editor_data.get_edited_scene()); } if (scene_tabs->get_offset_buttons_visible()) { // Move the add button to a fixed position. if (scene_tab_add->get_parent() == scene_tabs) { scene_tabs->remove_child(scene_tab_add); scene_tab_add_ph->add_child(scene_tab_add); scene_tab_add->set_position(Point2()); } } else { // Move the add button to be after the last tab. if (scene_tab_add->get_parent() == scene_tab_add_ph) { scene_tab_add_ph->remove_child(scene_tab_add); scene_tabs->add_child(scene_tab_add); } if (scene_tabs->get_tab_count() == 0) { scene_tab_add->set_position(Point2()); return; } Rect2 last_tab = scene_tabs->get_tab_rect(scene_tabs->get_tab_count() - 1); int hsep = scene_tabs->get_theme_constant(SNAME("h_separation")); if (scene_tabs->is_layout_rtl()) { scene_tab_add->set_position(Point2(last_tab.position.x - scene_tab_add->get_size().x - hsep, last_tab.position.y)); } else { scene_tab_add->set_position(Point2(last_tab.position.x + last_tab.size.width + hsep, last_tab.position.y)); } } // Reconnect after everything is done. scene_tabs->connect("tab_changed", callable_mp(this, &EditorNode::_scene_tab_changed)); } 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)) { EditorPlugin *old_editor = editor_plugin_screen; if (ED_IS_SHORTCUT("editor/next_tab", p_event)) { int next_tab = editor_data.get_edited_scene() + 1; next_tab %= editor_data.get_edited_scene_count(); _scene_tab_changed(next_tab); } if (ED_IS_SHORTCUT("editor/prev_tab", p_event)) { int next_tab = editor_data.get_edited_scene() - 1; next_tab = next_tab >= 0 ? next_tab : editor_data.get_edited_scene_count() - 1; _scene_tab_changed(next_tab); } if (ED_IS_SHORTCUT("editor/filter_files", p_event)) { FileSystemDock::get_singleton()->focus_on_filter(); } 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 (old_editor != editor_plugin_screen) { get_tree()->get_root()->set_input_as_handled(); } } } void EditorNode::_update_from_settings() { 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); 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")))); 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")); #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::_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::_notification(int p_what) { switch (p_what) { 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)) { _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 (EDITOR_GET("interface/editor/show_update_spinner")) { update_spinner->set_icon(gui_base->get_theme_icon("Progress" + itos(update_spinner_step + 1), SNAME("EditorIcons"))); } } editor_selection->update(); ResourceImporterTexture::get_singleton()->update_imports(); if (settings_changed) { _update_title(); } if (settings_changed) { _update_from_settings(); settings_changed = false; emit_signal(SNAME("project_settings_changed")); } ResourceImporterTexture::get_singleton()->update_imports(); } break; case NOTIFICATION_ENTER_TREE: { 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)); window->set_theme(theme); } 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(); MessageQueue::get_singleton()->push_callable(callable_mp(this, &EditorNode::_begin_first_scan)); /* DO NOT LOAD SCENES HERE, WAIT FOR FILE SCANNING AND REIMPORT TO COMPLETE */ } break; case NOTIFICATION_EXIT_TREE: { if (progress_dialog) { progress_dialog->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(); } break; case Control::NOTIFICATION_THEME_CHANGED: { scene_tab_add_ph->set_custom_minimum_size(scene_tab_add->get_minimum_size()); } break; case NOTIFICATION_READY: { { _initializing_plugins = true; Vector addons; if (ProjectSettings::get_singleton()->has_setting("editor_plugins/enabled")) { addons = GLOBAL_GET("editor_plugins/enabled"); } for (int i = 0; i < addons.size(); i++) { set_addon_plugin_enabled(addons[i], true); } _initializing_plugins = false; if (!pending_addons.is_empty()) { EditorFileSystem::get_singleton()->connect("script_classes_updated", callable_mp(this, &EditorNode::_enable_pending_addons)); } } RenderingServer::get_singleton()->viewport_set_disable_2d(get_scene_root()->get_viewport_rid(), true); RenderingServer::get_singleton()->viewport_set_disable_environment(get_viewport()->get_viewport_rid(), true); 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(); /* 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(); } 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, 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: { scene_tabs->set_tab_close_display_policy((TabBar::CloseButtonDisplayPolicy)EDITOR_GET("interface/scene_tabs/display_close_button").operator int()); bool theme_changed = EditorSettings::get_singleton()->check_changed_settings_in_group("interface/theme") || EditorSettings::get_singleton()->check_changed_settings_in_group("text_editor/theme") || EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor/font") || EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor/main_font") || EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor/code_font") || EditorSettings::get_singleton()->check_changed_settings_in_group("filesystem/file_dialog/thumbnail_size"); if (theme_changed) { theme = create_custom_theme(theme_base->get_theme()); DisplayServer::set_early_window_clear_color_override(true, theme->get_color(SNAME("background"), SNAME("Editor"))); theme_base->set_theme(theme); gui_base->set_theme(theme); get_window()->set_theme(theme); gui_base->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("Background"), SNAME("EditorStyles"))); scene_root_parent->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("Content"), SNAME("EditorStyles"))); bottom_panel->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("BottomPanel"), SNAME("EditorStyles"))); tabbar_panel->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("tabbar_background"), SNAME("TabContainer"))); main_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles"))); } scene_tabs->set_max_tab_width(int(EDITOR_GET("interface/scene_tabs/maximum_width")) * EDSCALE); _update_scene_tabs(); recent_scenes->reset_size(); // Update debugger area. if (EditorDebuggerNode::get_singleton()->is_visible()) { bottom_panel->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("BottomPanelDebuggerOverride"), SNAME("EditorStyles"))); } // Update icons. for (int i = 0; i < singleton->main_editor_buttons.size(); i++) { Button *tb = singleton->main_editor_buttons[i]; EditorPlugin *p_editor = singleton->editor_table[i]; Ref icon = p_editor->get_icon(); if (icon.is_valid()) { tb->set_icon(icon); } else if (singleton->gui_base->has_theme_icon(p_editor->get_name(), SNAME("EditorIcons"))) { tb->set_icon(singleton->gui_base->get_theme_icon(p_editor->get_name(), SNAME("EditorIcons"))); } } _build_icon_type_cache(); if (write_movie_button->is_pressed()) { launch_pad->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("LaunchPadMovieMode"), SNAME("EditorStyles"))); write_movie_panel->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("MovieWriterButtonPressed"), SNAME("EditorStyles"))); } else { launch_pad->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("LaunchPadNormal"), SNAME("EditorStyles"))); write_movie_panel->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("MovieWriterButtonNormal"), SNAME("EditorStyles"))); } play_button->set_icon(gui_base->get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); play_scene_button->set_icon(gui_base->get_theme_icon(SNAME("PlayScene"), SNAME("EditorIcons"))); play_custom_scene_button->set_icon(gui_base->get_theme_icon(SNAME("PlayCustom"), SNAME("EditorIcons"))); pause_button->set_icon(gui_base->get_theme_icon(SNAME("Pause"), SNAME("EditorIcons"))); stop_button->set_icon(gui_base->get_theme_icon(SNAME("Stop"), SNAME("EditorIcons"))); prev_scene->set_icon(gui_base->get_theme_icon(SNAME("PrevScene"), SNAME("EditorIcons"))); distraction_free->set_icon(gui_base->get_theme_icon(SNAME("DistractionFree"), SNAME("EditorIcons"))); scene_tab_add->set_icon(gui_base->get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); bottom_panel_raise->set_icon(gui_base->get_theme_icon(SNAME("ExpandBottomDock"), SNAME("EditorIcons"))); if (gui_base->is_layout_rtl()) { dock_tab_move_left->set_icon(theme->get_icon(SNAME("Forward"), SNAME("EditorIcons"))); dock_tab_move_right->set_icon(theme->get_icon(SNAME("Back"), SNAME("EditorIcons"))); } else { dock_tab_move_left->set_icon(theme->get_icon(SNAME("Back"), SNAME("EditorIcons"))); dock_tab_move_right->set_icon(theme->get_icon(SNAME("Forward"), SNAME("EditorIcons"))); } help_menu->set_item_icon(help_menu->get_item_index(HELP_SEARCH), gui_base->get_theme_icon(SNAME("HelpSearch"), SNAME("EditorIcons"))); help_menu->set_item_icon(help_menu->get_item_index(HELP_DOCS), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons"))); help_menu->set_item_icon(help_menu->get_item_index(HELP_QA), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons"))); help_menu->set_item_icon(help_menu->get_item_index(HELP_REPORT_A_BUG), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons"))); help_menu->set_item_icon(help_menu->get_item_index(HELP_SUGGEST_A_FEATURE), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons"))); help_menu->set_item_icon(help_menu->get_item_index(HELP_SEND_DOCS_FEEDBACK), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons"))); help_menu->set_item_icon(help_menu->get_item_index(HELP_COMMUNITY), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons"))); help_menu->set_item_icon(help_menu->get_item_index(HELP_ABOUT), gui_base->get_theme_icon(SNAME("Godot"), SNAME("EditorIcons"))); help_menu->set_item_icon(help_menu->get_item_index(HELP_SUPPORT_GODOT_DEVELOPMENT), gui_base->get_theme_icon(SNAME("Heart"), SNAME("EditorIcons"))); for (int i = 0; i < main_editor_buttons.size(); i++) { main_editor_buttons.write[i]->add_theme_font_override("font", gui_base->get_theme_font(SNAME("main_button_font"), SNAME("EditorFonts"))); main_editor_buttons.write[i]->add_theme_font_size_override("font_size", gui_base->get_theme_font_size(SNAME("main_button_font_size"), SNAME("EditorFonts"))); } 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(); } _update_update_spinner(); } break; } } void EditorNode::_update_update_spinner() { update_spinner->set_visible(EDITOR_GET("interface/editor/show_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 = EditorSettings::get_singleton()->is_dark_theme(); update_spinner->set_self_modulate( gui_base->get_theme_color(SNAME("error_color"), SNAME("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::_on_plugin_ready(Object *p_script, const String &p_activate_name) { Ref