diff options
Diffstat (limited to 'editor/editor_node.cpp')
-rw-r--r-- | editor/editor_node.cpp | 1061 |
1 files changed, 697 insertions, 364 deletions
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 8b7014fabe..ec8430e645 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,21 +30,21 @@ #include "editor_node.h" -#include "core/bind/core_bind.h" -#include "core/class_db.h" +#include "core/config/project_settings.h" +#include "core/core_bind.h" #include "core/input/input.h" #include "core/io/config_file.h" #include "core/io/image_loader.h" #include "core/io/resource_loader.h" #include "core/io/resource_saver.h" #include "core/io/stream_peer_ssl.h" -#include "core/message_queue.h" +#include "core/object/class_db.h" +#include "core/object/message_queue.h" #include "core/os/file_access.h" #include "core/os/keyboard.h" #include "core/os/os.h" -#include "core/print_string.h" -#include "core/project_settings.h" -#include "core/translation.h" +#include "core/string/print_string.h" +#include "core/string/translation.h" #include "core/version.h" #include "main/main.h" #include "scene/gui/center_container.h" @@ -57,9 +57,10 @@ #include "scene/gui/split_container.h" #include "scene/gui/tab_container.h" #include "scene/gui/tabs.h" -#include "scene/gui/texture_progress.h" -#include "scene/gui/tool_button.h" +#include "scene/gui/texture_progress_bar.h" +#include "scene/main/window.h" #include "scene/resources/packed_scene.h" +#include "servers/display_server.h" #include "servers/navigation_server_2d.h" #include "servers/navigation_server_3d.h" #include "servers/physics_server_2d.h" @@ -86,10 +87,10 @@ #include "editor/editor_settings.h" #include "editor/editor_spin_slider.h" #include "editor/editor_themes.h" +#include "editor/editor_translation_parser.h" #include "editor/export_template_manager.h" #include "editor/filesystem_dock.h" #include "editor/import/editor_import_collada.h" -#include "editor/import/editor_scene_importer_gltf.h" #include "editor/import/resource_importer_bitmask.h" #include "editor/import/resource_importer_csv.h" #include "editor/import/resource_importer_csv_translation.h" @@ -101,10 +102,10 @@ #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_importer_mesh_node_3d.h" #include "editor/import_dock.h" #include "editor/multi_node_edit.h" #include "editor/node_dock.h" -#include "editor/pane_drag.h" #include "editor/plugin_config_dialog.h" #include "editor/plugins/animation_blend_space_1d_editor.h" #include "editor/plugins/animation_blend_space_2d_editor.h" @@ -124,10 +125,13 @@ #include "editor/plugins/cpu_particles_3d_editor_plugin.h" #include "editor/plugins/curve_editor_plugin.h" #include "editor/plugins/debugger_editor_plugin.h" +#include "editor/plugins/editor_debugger_plugin.h" #include "editor/plugins/editor_preview_plugins.h" +#include "editor/plugins/font_editor_plugin.h" #include "editor/plugins/gi_probe_editor_plugin.h" #include "editor/plugins/gpu_particles_2d_editor_plugin.h" #include "editor/plugins/gpu_particles_3d_editor_plugin.h" +#include "editor/plugins/gpu_particles_collision_sdf_editor_plugin.h" #include "editor/plugins/gradient_editor_plugin.h" #include "editor/plugins/item_list_editor_plugin.h" #include "editor/plugins/light_occluder_2d_editor_plugin.h" @@ -139,6 +143,8 @@ #include "editor/plugins/multimesh_editor_plugin.h" #include "editor/plugins/navigation_polygon_editor_plugin.h" #include "editor/plugins/node_3d_editor_plugin.h" +#include "editor/plugins/ot_features_plugin.h" +#include "editor/plugins/packed_scene_translation_parser_plugin.h" #include "editor/plugins/path_2d_editor_plugin.h" #include "editor/plugins/path_3d_editor_plugin.h" #include "editor/plugins/physical_bone_3d_editor_plugin.h" @@ -156,6 +162,7 @@ #include "editor/plugins/sprite_frames_editor_plugin.h" #include "editor/plugins/style_box_editor_plugin.h" #include "editor/plugins/text_editor.h" +#include "editor/plugins/texture_3d_editor_plugin.h" #include "editor/plugins/texture_editor_plugin.h" #include "editor/plugins/texture_layered_editor_plugin.h" #include "editor/plugins/texture_region_editor_plugin.h" @@ -167,18 +174,120 @@ #include "editor/progress_dialog.h" #include "editor/project_export.h" #include "editor/project_settings_editor.h" -#include "editor/pvrtc_compress.h" #include "editor/quick_open.h" #include "editor/register_exporters.h" -#include "editor/run_settings_dialog.h" #include "editor/settings_config_dialog.h" -#include "scene/main/window.h" -#include "servers/display_server.h" + #include <stdio.h> #include <stdlib.h> EditorNode *EditorNode::singleton = nullptr; +void EditorNode::disambiguate_filenames(const Vector<String> p_full_paths, Vector<String> &r_filenames) { + // Keep track of a list of "index sets," i.e. sets of indices + // within disambiguated_scene_names which contain the same name. + Vector<Set<int>> index_sets; + Map<String, int> 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(Set<int>()); + 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++) { + Set<int> iset = index_sets[i]; + while (iset.size() > 1) { + // Append the parent folder to each scene name + for (Set<int>::Element *E = iset.front(); E; E = E->next()) { + int set_idx = E->get(); + String scene_name = r_filenames[set_idx]; + String full_path = p_full_paths[set_idx]; + + // Get rid of file extensions and res:// prefixes + if (scene_name.rfind(".") >= 0) { + scene_name = scene_name.substr(0, scene_name.rfind(".")); + } + if (full_path.begins_with("res://")) { + full_path = full_path.substr(6); + } + if (full_path.rfind(".") >= 0) { + full_path = full_path.substr(0, full_path.rfind(".")); + } + + 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.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; + Set<int>::Element *E = iset.front(); + while (E) { + String scene_name = r_filenames[E->get()]; + bool duplicate_found = false; + for (Set<int>::Element *F = iset.front(); F; F = F->next()) { + if (E->get() == F->get()) { + continue; + } + String other_scene_name = r_filenames[F->get()]; + if (other_scene_name == scene_name) { + duplicate_found = true; + break; + } + } + + Set<int>::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()]; + if (path.begins_with("res://")) { + path = path.substr(6); + } + if (path.rfind(".") >= 0) { + path = path.substr(0, path.rfind(".")); + } + if (scene_name.rfind(".") >= 0) { + scene_name = scene_name.substr(0, scene_name.rfind(".")); + } + + // We can proceed iff 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::_update_scene_tabs() { bool show_rb = EditorSettings::get_singleton()->get("interface/scene_tabs/show_script_button"); @@ -186,6 +295,16 @@ void EditorNode::_update_scene_tabs() { DisplayServer::get_singleton()->global_menu_clear("_dock"); } + // Get all scene names, which may be ambiguous + Vector<String> disambiguated_scene_names; + Vector<String> 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); + scene_tabs->clear_tabs(); Ref<Texture2D> script_icon = gui_base->get_theme_icon("Script", "EditorIcons"); for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { @@ -197,7 +316,7 @@ void EditorNode::_update_scene_tabs() { int current = editor_data.get_edited_scene(); bool unsaved = (i == current) ? saved_version != editor_data.get_undo_redo().get_version() : editor_data.get_scene_version(i) != 0; - scene_tabs->add_tab(editor_data.get_scene_title(i) + (unsaved ? "(*)" : ""), icon); + 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), i); @@ -218,7 +337,11 @@ void EditorNode::_update_scene_tabs() { if (scene_tabs->get_offset_buttons_visible()) { // move add button to fixed position on the tabbar if (scene_tab_add->get_parent() == scene_tabs) { - scene_tab_add->set_position(Point2(0, 0)); + if (scene_tabs->is_layout_rtl()) { + scene_tab_add->set_position(Point2(tabbar_container->get_size().x - scene_tab_add->get_size().x, 0)); + } else { + scene_tab_add->set_position(Point2(0, 0)); + } scene_tabs->remove_child(scene_tab_add); tabbar_container->add_child(scene_tab_add); tabbar_container->move_child(scene_tab_add, 1); @@ -233,7 +356,11 @@ void EditorNode::_update_scene_tabs() { if (scene_tabs->get_tab_count() != 0) { last_tab = scene_tabs->get_tab_rect(scene_tabs->get_tab_count() - 1); } - scene_tab_add->set_position(Point2(last_tab.get_position().x + last_tab.get_size().x + 3, last_tab.get_position().y)); + if (scene_tabs->is_layout_rtl()) { + scene_tab_add->set_position(Point2(last_tab.get_position().x - scene_tab_add->get_size().x - 3, last_tab.get_position().y)); + } else { + scene_tab_add->set_position(Point2(last_tab.get_position().x + last_tab.get_size().x + 3, last_tab.get_position().y)); + } } } @@ -250,9 +377,9 @@ void EditorNode::_version_control_menu_option(int p_idx) { void EditorNode::_update_title() { String appname = ProjectSettings::get_singleton()->get("application/config/name"); - String title = appname.empty() ? String(VERSION_FULL_NAME) : String(VERSION_NAME + String(" - ") + appname); + String title = appname.is_empty() ? String(VERSION_FULL_NAME) : String(VERSION_NAME + String(" - ") + appname); String edited = editor_data.get_edited_scene_root() ? editor_data.get_edited_scene_root()->get_filename() : String(); - if (!edited.empty()) { + if (!edited.is_empty()) { title += " - " + String(edited.get_file()); } if (unsaved_cache) { @@ -303,6 +430,74 @@ void EditorNode::_unhandled_input(const Ref<InputEvent> &p_event) { } } +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_effects_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_effects_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_glow_set_use_bicubic_upscale(glow_bicubic); + bool glow_high_quality = GLOBAL_GET("rendering/environment/glow/use_high_quality"); + RS::get_singleton()->environment_glow_set_use_high_quality(glow_high_quality); + 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/shadows/directional_shadow/size"); + uint32_t directional_shadow_16_bits = GLOBAL_GET("rendering/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/shadows/shadows/soft_shadow_quality"))); + RS::get_singleton()->shadows_quality_set(shadows_quality); + RS::ShadowQuality directional_shadow_quality = RS::ShadowQuality(int(GLOBAL_GET("rendering/shadows/directional_shadow/soft_shadow_quality"))); + RS::get_singleton()->directional_shadow_quality_set(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::GIProbeQuality gi_probe_quality = RS::GIProbeQuality(int(GLOBAL_GET("rendering/global_illumination/gi_probes/quality"))); + RS::get_singleton()->gi_probe_set_quality(gi_probe_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_DEF("rendering/global_illumination/gi/use_half_resolution", false); + 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); + + float lod_threshold = GLOBAL_GET("rendering/mesh_lod/lod_change/threshold_pixels"); + scene_root->set_lod_threshold(lod_threshold); +} + void EditorNode::_notification(int p_what) { switch (p_what) { case NOTIFICATION_PROCESS: { @@ -341,45 +536,13 @@ void EditorNode::_notification(int p_what) { editor_selection->update(); - //scene_root->set_size_override(true, Size2(ProjectSettings::get_singleton()->get("display/window/size/width"), ProjectSettings::get_singleton()->get("display/window/size/height"))); - - { //TODO should only happen on settings changed - int current_filter = GLOBAL_GET("rendering/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/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); - } + ResourceImporterTexture::get_singleton()->update_imports(); - RS::DOFBokehShape dof_shape = RS::DOFBokehShape(int(GLOBAL_GET("rendering/quality/depth_of_field/depth_of_field_bokeh_shape"))); - RS::get_singleton()->camera_effects_set_dof_blur_bokeh_shape(dof_shape); - RS::DOFBlurQuality dof_quality = RS::DOFBlurQuality(int(GLOBAL_GET("rendering/quality/depth_of_field/depth_of_field_bokeh_quality"))); - bool dof_jitter = GLOBAL_GET("rendering/quality/depth_of_field/depth_of_field_use_jitter"); - RS::get_singleton()->camera_effects_set_dof_blur_quality(dof_quality, dof_jitter); - RS::get_singleton()->environment_set_ssao_quality(RS::EnvironmentSSAOQuality(int(GLOBAL_GET("rendering/quality/ssao/quality"))), GLOBAL_GET("rendering/quality/ssao/half_size")); - RS::get_singleton()->screen_space_roughness_limiter_set_active(GLOBAL_GET("rendering/quality/screen_filters/screen_space_roughness_limiter"), GLOBAL_GET("rendering/quality/screen_filters/screen_space_roughness_limiter_curve")); - bool glow_bicubic = int(GLOBAL_GET("rendering/quality/glow/upscale_mode")) > 0; - RS::get_singleton()->environment_glow_set_use_bicubic_upscale(glow_bicubic); - RS::EnvironmentSSRRoughnessQuality ssr_roughness_quality = RS::EnvironmentSSRRoughnessQuality(int(GLOBAL_GET("rendering/quality/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/quality/subsurface_scattering/subsurface_scattering_quality"))); - RS::get_singleton()->sub_surface_scattering_set_quality(sss_quality); - float sss_scale = GLOBAL_GET("rendering/quality/subsurface_scattering/subsurface_scattering_scale"); - float sss_depth_scale = GLOBAL_GET("rendering/quality/subsurface_scattering/subsurface_scattering_depth_scale"); - RS::get_singleton()->sub_surface_scattering_set_scale(sss_scale, sss_depth_scale); - RS::ShadowQuality shadows_quality = RS::ShadowQuality(int(GLOBAL_GET("rendering/quality/shadows/soft_shadow_quality"))); - RS::get_singleton()->shadows_quality_set(shadows_quality); - RS::ShadowQuality directional_shadow_quality = RS::ShadowQuality(int(GLOBAL_GET("rendering/quality/directional_shadow/soft_shadow_quality"))); - RS::get_singleton()->directional_shadow_quality_set(directional_shadow_quality); - float probe_update_speed = GLOBAL_GET("rendering/lightmapper/probe_capture_update_speed"); - RS::get_singleton()->lightmap_set_probe_capture_update_speed(probe_update_speed); + if (settings_changed) { + _update_from_settings(); + settings_changed = false; + emit_signal("project_settings_changed"); } - - ResourceImporterTexture::get_singleton()->update_imports(); } break; case NOTIFICATION_ENTER_TREE: { @@ -388,6 +551,8 @@ void EditorNode::_notification(int p_what) { 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(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); get_tree()->get_root()->connect("files_dropped", callable_mp(this, &EditorNode::_dropped_files)); @@ -430,14 +595,15 @@ void EditorNode::_notification(int p_what) { /* DO NOT LOAD SCENES HERE, WAIT FOR FILE SCANNING AND REIMPORT TO COMPLETE */ } break; - case NOTIFICATION_WM_FOCUS_IN: { + 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_WM_FOCUS_OUT: { + case NOTIFICATION_APPLICATION_FOCUS_OUT: { // 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; @@ -460,8 +626,8 @@ void EditorNode::_notification(int p_what) { gui_base->add_theme_style_override("panel", gui_base->get_theme_stylebox("Background", "EditorStyles")); scene_root_parent->add_theme_style_override("panel", gui_base->get_theme_stylebox("Content", "EditorStyles")); bottom_panel->add_theme_style_override("panel", gui_base->get_theme_stylebox("panel", "TabContainer")); - scene_tabs->add_theme_style_override("tab_fg", gui_base->get_theme_stylebox("SceneTabFG", "EditorStyles")); - scene_tabs->add_theme_style_override("tab_bg", gui_base->get_theme_stylebox("SceneTabBG", "EditorStyles")); + scene_tabs->add_theme_style_override("tab_selected", gui_base->get_theme_stylebox("SceneTabFG", "EditorStyles")); + scene_tabs->add_theme_style_override("tab_unselected", gui_base->get_theme_stylebox("SceneTabBG", "EditorStyles")); file_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox("MenuHover", "EditorStyles")); project_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox("MenuHover", "EditorStyles")); @@ -485,7 +651,7 @@ void EditorNode::_notification(int p_what) { // update_icons for (int i = 0; i < singleton->main_editor_buttons.size(); i++) { - ToolButton *tb = singleton->main_editor_buttons[i]; + Button *tb = singleton->main_editor_buttons[i]; EditorPlugin *p_editor = singleton->editor_table[i]; Ref<Texture2D> icon = p_editor->get_icon(); @@ -511,8 +677,13 @@ void EditorNode::_notification(int p_what) { bottom_panel_raise->set_icon(gui_base->get_theme_icon("ExpandBottomDock", "EditorIcons")); // clear_button->set_icon(gui_base->get_icon("Close", "EditorIcons")); don't have access to that node. needs to become a class property - dock_tab_move_left->set_icon(theme->get_icon("Back", "EditorIcons")); - dock_tab_move_right->set_icon(theme->get_icon("Forward", "EditorIcons")); + if (gui_base->is_layout_rtl()) { + dock_tab_move_left->set_icon(theme->get_icon("Forward", "EditorIcons")); + dock_tab_move_right->set_icon(theme->get_icon("Back", "EditorIcons")); + } else { + dock_tab_move_left->set_icon(theme->get_icon("Back", "EditorIcons")); + dock_tab_move_right->set_icon(theme->get_icon("Forward", "EditorIcons")); + } PopupMenu *p = help_menu->get_popup(); p->set_item_icon(p->get_item_index(HELP_SEARCH), gui_base->get_theme_icon("HelpSearch", "EditorIcons")); @@ -619,18 +790,23 @@ void EditorNode::_fs_changed() { preset.unref(); } if (preset.is_null()) { - export_error = vformat("Invalid export preset name: %s.", preset_name); + export_error = vformat( + "Invalid export preset name: %s. Make sure `export_presets.cfg` is present in the current directory.", + preset_name); } else { Ref<EditorExportPlatform> platform = preset->get_platform(); - if (platform.is_null()) { + const String export_path = export_defer.path.is_empty() ? preset->get_export_path() : export_defer.path; + if (export_path.is_empty()) { + export_error = vformat("Export preset '%s' doesn't have a default export path, and none was specified.", preset_name); + } else if (platform.is_null()) { export_error = vformat("Export preset '%s' doesn't have a matching platform.", preset_name); } else { Error err = OK; if (export_defer.pack_only) { // Only export .pck or .zip data pack. - if (export_defer.path.ends_with(".zip")) { - err = platform->export_zip(preset, export_defer.debug, export_defer.path); - } else if (export_defer.path.ends_with(".pck")) { - err = platform->export_pack(preset, export_defer.debug, export_defer.path); + if (export_path.ends_with(".zip")) { + err = platform->export_zip(preset, export_defer.debug, export_path); + } else if (export_path.ends_with(".pck")) { + err = platform->export_pack(preset, export_defer.debug, export_path); } } else { // Normal project export. String config_error; @@ -639,7 +815,7 @@ void EditorNode::_fs_changed() { ERR_PRINT(vformat("Cannot export project with preset '%s' due to configuration errors:\n%s", preset_name, config_error)); err = missing_templates ? ERR_FILE_NOT_FOUND : ERR_UNCONFIGURED; } else { - err = platform->export_project(preset, export_defer.debug, export_defer.path); + err = platform->export_project(preset, export_defer.debug, export_path); } } switch (err) { @@ -649,7 +825,7 @@ void EditorNode::_fs_changed() { export_error = vformat("Project export failed for preset '%s', the export template appears to be missing.", preset_name); break; case ERR_FILE_BAD_PATH: - export_error = vformat("Project export failed for preset '%s', the target path '%s' appears to be invalid.", preset_name, export_defer.path); + export_error = vformat("Project export failed for preset '%s', the target path '%s' appears to be invalid.", preset_name, export_path); break; default: export_error = vformat("Project export failed with error code %d for preset '%s'.", (int)err, preset_name); @@ -658,7 +834,7 @@ void EditorNode::_fs_changed() { } } - if (!export_error.empty()) { + if (!export_error.is_empty()) { ERR_PRINT(export_error); OS::get_singleton()->set_exit_code(EXIT_FAILURE); } @@ -717,6 +893,82 @@ void EditorNode::_sources_changed(bool p_exist) { } } +void EditorNode::_scan_external_changes() { + disk_changed_list->clear(); + TreeItem *r = disk_changed_list->create_item(); + disk_changed_list->set_hide_root(true); + bool need_reload = false; + + // Check if any edited scene has changed. + + for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { + if (editor_data.get_scene_path(i) == "") { + continue; + } + + uint64_t last_date = editor_data.get_scene_modified_time(i); + uint64_t date = FileAccess::get_modified_time(editor_data.get_scene_path(i)); + + if (date > last_date) { + TreeItem *ti = disk_changed_list->create_item(r); + ti->set_text(0, editor_data.get_scene_path(i).get_file()); + need_reload = true; + } + } + + String project_settings_path = ProjectSettings::get_singleton()->get_resource_path().plus_file("project.godot"); + if (FileAccess::get_modified_time(project_settings_path) > ProjectSettings::get_singleton()->get_last_saved_time()) { + TreeItem *ti = disk_changed_list->create_item(r); + ti->set_text(0, "project.godot"); + need_reload = true; + } + + if (need_reload) { + disk_changed->call_deferred("popup_centered_ratio", 0.5); + } +} + +void EditorNode::_resave_scenes(String p_str) { + save_all_scenes(); + ProjectSettings::get_singleton()->save(); + disk_changed->hide(); +} + +void EditorNode::_reload_modified_scenes() { + int current_idx = editor_data.get_edited_scene(); + + for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { + if (editor_data.get_scene_path(i) == "") { + continue; + } + + uint64_t last_date = editor_data.get_scene_modified_time(i); + uint64_t date = FileAccess::get_modified_time(editor_data.get_scene_path(i)); + + if (date > last_date) { + String filename = editor_data.get_scene_path(i); + editor_data.set_edited_scene(i); + _remove_edited_scene(false); + + Error err = load_scene(filename, false, false, true, false, true); + if (err != OK) { + ERR_PRINT(vformat("Failed to load scene: %s", filename)); + } + editor_data.move_edited_scene_to_index(i); + } + } + + get_undo_redo()->clear_history(false); + set_current_scene(current_idx); + _update_scene_tabs(); + disk_changed->hide(); +} + +void EditorNode::_reload_project_settings() { + ProjectSettings::get_singleton()->setup(ProjectSettings::get_singleton()->get_resource_path(), String(), true); + settings_changed = true; +} + void EditorNode::_vp_resized() { } @@ -758,7 +1010,7 @@ Error EditorNode::load_resource(const String &p_resource, bool p_ignore_broken_d dependency_errors.clear(); Error err; - RES res = ResourceLoader::load(p_resource, "", false, &err); + RES res = ResourceLoader::load(p_resource, "", ResourceFormatLoader::CACHE_MODE_REUSE, &err); ERR_FAIL_COND_V(!res.is_valid(), ERR_CANT_OPEN); if (!p_ignore_broken_deps && dependency_errors.has(p_resource)) { @@ -871,8 +1123,8 @@ void EditorNode::save_resource_as(const Ref<Resource> &p_resource, const String } file->set_current_path(existing); } - file->popup_centered_ratio(); file->set_title(TTR("Save Resource As...")); + file->popup_file_dialog(); } void EditorNode::_menu_option(int p_option) { @@ -903,19 +1155,19 @@ void EditorNode::_dialog_display_load_error(String p_file, Error p_error) { if (p_error) { switch (p_error) { case ERR_CANT_OPEN: { - show_accept(vformat(TTR("Can't open '%s'. The file could have been moved or deleted."), p_file.get_file()), TTR("OK")); + show_accept(vformat(TTR("Can't open file '%s'. The file could have been moved or deleted."), p_file.get_file()), TTR("OK")); } break; case ERR_PARSE_ERROR: { - show_accept(vformat(TTR("Error while parsing '%s'."), p_file.get_file()), TTR("OK")); + show_accept(vformat(TTR("Error while parsing file '%s'."), p_file.get_file()), TTR("OK")); } break; case ERR_FILE_CORRUPT: { - show_accept(vformat(TTR("Unexpected end of file '%s'."), p_file.get_file()), TTR("OK")); + show_accept(vformat(TTR("Scene file '%s' appears to be invalid/corrupt."), p_file.get_file()), TTR("OK")); } break; case ERR_FILE_NOT_FOUND: { - show_accept(vformat(TTR("Missing '%s' or its dependencies."), p_file.get_file()), TTR("OK")); + show_accept(vformat(TTR("Missing file '%s' or one its dependencies."), p_file.get_file()), TTR("OK")); } break; default: { - show_accept(vformat(TTR("Error while loading '%s'."), p_file.get_file()), TTR("OK")); + show_accept(vformat(TTR("Error while loading file '%s'."), p_file.get_file()), TTR("OK")); } break; } } @@ -1102,23 +1354,31 @@ void EditorNode::_save_scene_with_preview(String p_file, int p_idx) { _find_node_types(editor_data.get_edited_scene_root(), c2d, c3d); - bool is2d; - if (c3d < c2d) { - is2d = true; - } else { - is2d = false; - } save.step(TTR("Creating Thumbnail"), 1); //current view? Ref<Image> img; - if (is2d) { - img = scene_root->get_texture()->get_data(); + // If neither 3D or 2D nodes are present, make a 1x1 black texture. + // We cannot fallback on the 2D editor, because it may not have been used yet, + // which would result in an invalid texture. + if (c3d == 0 && c2d == 0) { + img.instance(); + img->create(1, 1, 0, Image::FORMAT_RGB8); + } else if (c3d < c2d) { + Ref<ViewportTexture> viewport_texture = scene_root->get_texture(); + if (viewport_texture->get_width() > 0 && viewport_texture->get_height() > 0) { + img = viewport_texture->get_data(); + } } else { - img = Node3DEditor::get_singleton()->get_editor_viewport(0)->get_viewport_node()->get_texture()->get_data(); + // The 3D editor may be disabled as a feature, but scenes can still be opened. + // This check prevents the preview from regenerating in case those scenes are then saved. + Ref<EditorFeatureProfile> profile = feature_profile_manager->get_current_profile(); + if (profile.is_valid() && !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_3D)) { + img = Node3DEditor::get_singleton()->get_editor_viewport(0)->get_viewport_node()->get_texture()->get_data(); + } } - if (img.is_valid()) { + if (img.is_valid() && img->get_width() > 0 && img->get_height() > 0) { img = img->duplicate(); save.step(TTR("Creating Thumbnail"), 2); @@ -1248,6 +1508,17 @@ int EditorNode::_save_external_resources() { return saved; } +static void _reset_animation_players(Node *p_node, List<Ref<AnimatedValuesBackup>> *r_anim_backups) { + for (int i = 0; i < p_node->get_child_count(); i++) { + AnimationPlayer *player = Object::cast_to<AnimationPlayer>(p_node->get_child(i)); + if (player && player->is_reset_on_save_enabled() && player->can_apply_reset()) { + Ref<AnimatedValuesBackup> old_values = player->apply_reset(); + r_anim_backups->push_back(old_values); + } + _reset_animation_players(p_node->get_child(i), r_anim_backups); + } +} + void EditorNode::_save_scene(String p_file, int idx) { Node *scene = editor_data.get_edited_scene_root(idx); @@ -1262,6 +1533,8 @@ void EditorNode::_save_scene(String p_file, int idx) { } editor_data.apply_changes_in_editors(); + List<Ref<AnimatedValuesBackup>> anim_backups; + _reset_animation_players(scene, &anim_backups); _save_default_environment(); _set_scene_metadata(p_file, idx); @@ -1309,6 +1582,11 @@ void EditorNode::_save_scene(String p_file, int idx) { _save_external_resources(); editor_data.save_editor_external_data(); + + for (List<Ref<AnimatedValuesBackup>>::Element *E = anim_backups.front(); E; E = E->next()) { + E->get()->restore(); + } + if (err == OK) { scene->set_filename(ProjectSettings::get_singleton()->localize_path(p_file)); if (idx < 0 || idx == editor_data.get_edited_scene()) { @@ -1316,6 +1594,7 @@ void EditorNode::_save_scene(String p_file, int idx) { } else { editor_data.set_edited_scene_version(0, idx); } + editor_data.set_scene_modified_time(idx, FileAccess::get_modified_time(p_file)); editor_folding.save_scene_folding(scene, p_file); @@ -1519,10 +1798,10 @@ void EditorNode::_dialog_action(String p_file) { ObjectID current = editor_history.get_current(); Object *current_obj = current.is_valid() ? ObjectDB::get_instance(current) : nullptr; ERR_FAIL_COND(!current_obj); - current_obj->_change_notify(); + current_obj->notify_property_list_changed(); } break; case SETTINGS_LAYOUT_SAVE: { - if (p_file.empty()) { + if (p_file.is_empty()) { return; } @@ -1533,7 +1812,7 @@ void EditorNode::_dialog_action(String p_file) { if (err == ERR_FILE_CANT_OPEN || err == ERR_FILE_NOT_FOUND) { config.instance(); // new config } else if (err != OK) { - show_warning(TTR("Error trying to save layout!")); + show_warning(TTR("An error occurred while trying to save the editor layout.\nMake sure the editor's user data path is writable.")); return; } @@ -1545,12 +1824,12 @@ void EditorNode::_dialog_action(String p_file) { _update_layouts_menu(); if (p_file == "Default") { - show_warning(TTR("Default editor layout overridden.")); + 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.")); } } break; case SETTINGS_LAYOUT_DELETE: { - if (p_file.empty()) { + if (p_file.is_empty()) { return; } @@ -1576,7 +1855,7 @@ void EditorNode::_dialog_action(String p_file) { _update_layouts_menu(); if (p_file == "Default") { - show_warning(TTR("Restored default layout to base settings.")); + show_warning(TTR("Restored the Default layout to its base settings.")); } } break; @@ -1633,7 +1912,7 @@ void EditorNode::edit_item(Object *p_object) { sub_plugins = editor_data.get_subeditors(p_object); } - if (!sub_plugins.empty()) { + if (!sub_plugins.is_empty()) { bool same = true; if (sub_plugins.size() == editor_plugins_over->get_plugins_list().size()) { for (int i = 0; i < sub_plugins.size(); i++) { @@ -1661,6 +1940,7 @@ void EditorNode::push_item(Object *p_object, const String &p_property, bool p_in node_dock->set_node(nullptr); scene_tree_dock->set_selected(nullptr); inspector_dock->update(nullptr); + _display_top_editors(false); return; } @@ -1809,7 +2089,7 @@ void EditorNode::_edit_current() { multi_nodes.push_back(node); } } - if (!multi_nodes.empty()) { + if (!multi_nodes.is_empty()) { // Pick the top-most node multi_nodes.sort_custom<Node::Comparator>(); selected_node = multi_nodes.front()->get(); @@ -1891,13 +2171,13 @@ void EditorNode::_edit_current() { sub_plugins = editor_data.get_subeditors(current_obj); } - if (!sub_plugins.empty()) { + if (!sub_plugins.is_empty()) { _display_top_editors(false); _set_top_editors(sub_plugins); _set_editing_top_editors(current_obj); _display_top_editors(true); - } else if (!editor_plugins_over->get_plugins_list().empty()) { + } else if (!editor_plugins_over->get_plugins_list().is_empty()) { hide_top_editors(); } } @@ -1920,12 +2200,11 @@ void EditorNode::_run(bool p_current, const String &p_custom) { play_custom_scene_button->set_pressed(false); play_custom_scene_button->set_icon(gui_base->get_theme_icon("PlayCustom", "EditorIcons")); - String main_scene; String run_filename; String args; bool skip_breakpoints; - if (p_current || (editor_data.get_edited_scene_root() && p_custom == editor_data.get_edited_scene_root()->get_filename())) { + if (p_current || (editor_data.get_edited_scene_root() && p_custom != String() && p_custom == editor_data.get_edited_scene_root()->get_filename())) { Node *scene = editor_data.get_edited_scene_root(); if (!scene) { @@ -1935,7 +2214,10 @@ void EditorNode::_run(bool p_current, const String &p_custom) { if (scene->get_filename() == "") { current_option = -1; - _menu_option_confirm(FILE_SAVE_BEFORE_RUN, false); + _menu_option(FILE_SAVE_AS_SCENE); + // Set the option to save and run so when the dialog is accepted, the scene runs. + current_option = FILE_SAVE_AND_RUN; + file->set_title(TTR("Save scene before running...")); return; } @@ -1955,13 +2237,7 @@ void EditorNode::_run(bool p_current, const String &p_custom) { if (unsaved_cache) { Node *scene = editor_data.get_edited_scene_root(); - if (scene) { //only autosave if there is a scene obviously - - if (scene->get_filename() == "") { - show_accept(TTR("Current scene was never saved, please save it prior to running."), TTR("OK")); - return; - } - + if (scene && scene->get_filename() != "") { // Only autosave if there is a scene and if it has a path. _save_scene_with_preview(scene->get_filename()); } } @@ -1984,7 +2260,7 @@ void EditorNode::_run(bool p_current, const String &p_custom) { List<String> breakpoints; editor_data.get_editor_breakpoints(&breakpoints); - args = ProjectSettings::get_singleton()->get("editor/main_run_args"); + args = ProjectSettings::get_singleton()->get("editor/run/main_run_args"); skip_breakpoints = EditorDebuggerNode::get_singleton()->is_skip_breakpoints(); EditorDebuggerNode::get_singleton()->start(); @@ -2055,7 +2331,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { file->set_current_path(scene->get_filename()); }; file->set_title(p_option == FILE_OPEN_SCENE ? TTR("Open Scene") : TTR("Open Base Scene")); - file->popup_centered_ratio(); + file->popup_file_dialog(); } break; case FILE_QUICK_OPEN: { @@ -2074,7 +2350,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { } break; case FILE_OPEN_PREV: { - if (previous_scenes.empty()) { + if (previous_scenes.is_empty()) { break; } opening_prev = true; @@ -2107,10 +2383,11 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { case FILE_CLOSE: { if (!p_confirmed) { tab_closing = p_option == FILE_CLOSE ? editor_data.get_edited_scene() : _next_unsaved_scene(false); + _scene_tab_changed(tab_closing); if (unsaved_cache || p_option == FILE_CLOSE_ALL_AND_QUIT || p_option == FILE_CLOSE_ALL_AND_RUN_PROJECT_MANAGER) { String scene_filename = editor_data.get_edited_scene_root(tab_closing)->get_filename(); - save_confirmation->get_ok()->set_text(TTR("Save & Close")); + save_confirmation->get_ok_button()->set_text(TTR("Save & Close")); save_confirmation->set_text(vformat(TTR("Save changes to '%s' before closing?"), scene_filename != "" ? scene_filename : "unsaved scene")); save_confirmation->popup_centered(); break; @@ -2176,11 +2453,12 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { } if (scene->get_filename() != "") { - file->set_current_path(scene->get_filename()); + String path = scene->get_filename(); + file->set_current_path(path); if (extensions.size()) { - String ext = scene->get_filename().get_extension().to_lower(); + String ext = path.get_extension().to_lower(); if (extensions.find(ext) == nullptr) { - file->set_current_path(scene->get_filename().replacen("." + ext, "." + extensions.front()->get())); + file->set_current_path(path.replacen("." + ext, "." + extensions.front()->get())); } } } else { @@ -2191,7 +2469,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { } file->set_current_path(existing); } - file->popup_centered_ratio(); + file->popup_file_dialog(); file->set_title(TTR("Save Scene As...")); } break; @@ -2199,18 +2477,6 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { case FILE_SAVE_ALL_SCENES: { _save_all_scenes(); } break; - case FILE_SAVE_BEFORE_RUN: { - if (!p_confirmed) { - confirmation->get_cancel()->set_text(TTR("No")); - confirmation->get_ok()->set_text(TTR("Yes")); - confirmation->set_text(TTR("This scene has never been saved. Save before running?")); - confirmation->popup_centered(); - break; - } - - _menu_option(FILE_SAVE_AS_SCENE); - _menu_option_confirm(FILE_SAVE_AND_RUN, false); - } break; case FILE_EXPORT_PROJECT: { project_export->popup_export(); @@ -2230,7 +2496,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { file_export_lib->add_filter("*." + E->get()); } - file_export_lib->popup_centered_ratio(); + file_export_lib->popup_file_dialog(); file_export_lib->set_title(TTR("Export Mesh Library")); } break; @@ -2249,24 +2515,14 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { file_export_lib->add_filter("*." + E->get()); } - file_export_lib->popup_centered_ratio(); + file_export_lib->popup_file_dialog(); file_export_lib->set_title(TTR("Export Tile Set")); } break; - case FILE_IMPORT_SUBSCENE: { - if (!editor_data.get_edited_scene_root()) { - show_accept(TTR("This operation can't be done without a selected node."), TTR("OK")); - break; - } - - scene_tree_dock->import_subscene(); - - } break; - case FILE_EXTERNAL_OPEN_SCENE: { if (unsaved_cache && !p_confirmed) { - confirmation->get_ok()->set_text(TTR("Open")); + confirmation->get_ok_button()->set_text(TTR("Open")); confirmation->set_text(TTR("Current scene not saved. Open anyway?")); confirmation->popup_centered(); break; @@ -2322,7 +2578,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { } if (unsaved_cache && !p_confirmed) { - confirmation->get_ok()->set_text(TTR("Reload Saved Scene")); + confirmation->get_ok_button()->set_text(TTR("Reload Saved Scene")); confirmation->set_text( TTR("The current scene has unsaved changes.\nReload the saved scene anyway? This action cannot be undone.")); confirmation->popup_centered(); @@ -2341,20 +2597,18 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { } break; case RUN_PLAY: { - _menu_option_confirm(RUN_STOP, true); - _run(false); + run_play(); } break; case RUN_PLAY_CUSTOM_SCENE: { - if (run_custom_filename.empty() || editor_run.get_status() == EditorRun::STATUS_STOP) { + if (run_custom_filename.is_empty() || editor_run.get_status() == EditorRun::STATUS_STOP) { _menu_option_confirm(RUN_STOP, true); quick_run->popup_dialog("PackedScene", true); quick_run->set_title(TTR("Quick Run Scene...")); play_custom_scene_button->set_pressed(false); } else { String last_custom_scene = run_custom_filename; - _menu_option_confirm(RUN_STOP, true); - _run(false, last_custom_scene); + run_play_custom(last_custom_scene); } } break; @@ -2394,14 +2648,9 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { } break; case RUN_PLAY_SCENE: { - _save_default_environment(); - _menu_option_confirm(RUN_STOP, true); - _run(true); + run_play_current(); } break; - case RUN_SCENE_SETTINGS: { - run_settings_dialog->popup_run_settings(); - } break; case RUN_SETTINGS: { project_settings->popup_project_settings(); } break; @@ -2419,6 +2668,8 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { } } break; case RUN_PROJECT_DATA_FOLDER: { + // ensure_user_data_dir() to prevent the edge case: "Open Project Data Folder" won't work after the project was renamed in ProjectSettingsEditor unless the project is saved + OS::get_singleton()->ensure_user_data_dir(); OS::get_singleton()->shell_open(String("file://") + OS::get_singleton()->get_user_data_dir()); } break; case FILE_EXPLORE_ANDROID_BUILD_TEMPLATES: { @@ -2429,15 +2680,8 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { if (!p_confirmed) { bool save_each = EDITOR_GET("interface/editor/save_each_scene_on_quit"); if (_next_unsaved_scene(!save_each) == -1) { - bool confirm = EDITOR_GET("interface/editor/quit_confirmation"); - if (confirm) { - confirmation->get_ok()->set_text(p_option == FILE_QUIT ? TTR("Quit") : TTR("Yes")); - confirmation->set_text(p_option == FILE_QUIT ? TTR("Exit the editor?") : TTR("Open Project Manager?")); - confirmation->popup_centered(); - } else { - _discard_changes(); - break; - } + _discard_changes(); + break; } else { if (save_each) { _menu_option_confirm(p_option == FILE_QUIT ? FILE_CLOSE_ALL_AND_QUIT : FILE_CLOSE_ALL_AND_RUN_PROJECT_MANAGER, false); @@ -2449,7 +2693,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { i = _next_unsaved_scene(true, ++i); } - save_confirmation->get_ok()->set_text(TTR("Save & Quit")); + save_confirmation->get_ok_button()->set_text(TTR("Save & Quit")); save_confirmation->set_text((p_option == FILE_QUIT ? TTR("Save changes to the following scene(s) before quitting?") : TTR("Save changes the following scene(s) before opening Project Manager?")) + unsaved_scenes); save_confirmation->popup_centered(); } @@ -2519,7 +2763,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { file->set_current_path(scene->get_filename()); }; file->set_title(TTR("Pick a Main Scene")); - file->popup_centered_ratio(); + file->popup_file_dialog(); } break; case HELP_SEARCH: { @@ -2545,7 +2789,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { } break; case SET_VIDEO_DRIVER_SAVE_AND_RESTART: { - ProjectSettings::get_singleton()->set("rendering/quality/driver/driver_name", video_driver_request); + ProjectSettings::get_singleton()->set("rendering/driver/driver_name", video_driver_request); ProjectSettings::get_singleton()->save(); save_all_scenes(); @@ -2568,10 +2812,14 @@ void EditorNode::_screenshot(bool p_use_utc) { } void EditorNode::_save_screenshot(NodePath p_path) { - SubViewport *viewport = Object::cast_to<SubViewport>(EditorInterface::get_singleton()->get_editor_viewport()->get_viewport()); - viewport->set_clear_mode(SubViewport::CLEAR_MODE_ONLY_NEXT_FRAME); - Ref<Image> img = viewport->get_texture()->get_data(); - viewport->set_clear_mode(SubViewport::CLEAR_MODE_ALWAYS); + Control *editor_main_control = EditorInterface::get_singleton()->get_editor_main_control(); + ERR_FAIL_COND_MSG(!editor_main_control, "Cannot get editor main control."); + Viewport *viewport = editor_main_control->get_viewport(); + ERR_FAIL_COND_MSG(!viewport, "Cannot get editor main control viewport."); + Ref<ViewportTexture> texture = viewport->get_texture(); + ERR_FAIL_COND_MSG(texture.is_null(), "Cannot get editor main control viewport texture."); + Ref<Image> img = texture->get_data(); + ERR_FAIL_COND_MSG(img.is_null(), "Cannot get editor main control viewport texture image."); Error error = img->save_png(p_path); ERR_FAIL_COND_MSG(error != OK, "Cannot save screenshot to file '" + p_path + "'."); } @@ -2583,16 +2831,13 @@ void EditorNode::_tool_menu_option(int p_idx) { } break; case TOOLS_CUSTOM: { if (tool_menu->get_item_submenu(p_idx) == "") { - Array params = tool_menu->get_item_metadata(p_idx); - - Object *handler = ObjectDB::get_instance(params[0]); - String callback = params[1]; - Variant *ud = ¶ms[2]; + Callable callback = tool_menu->get_item_metadata(p_idx); Callable::CallError ce; + Variant result; + callback.call(nullptr, 0, result, ce); - handler->call(callback, (const Variant **)&ud, 1, ce); if (ce.error != Callable::CallError::CALL_OK) { - String err = Variant::get_call_error_text(handler, callback, (const Variant **)&ud, 1, ce); + String err = Variant::get_callable_error_text(callback, nullptr, 0, ce); ERR_PRINT("Error calling function from tool menu: " + err); } } // else it's a submenu so don't do anything. @@ -2650,6 +2895,10 @@ void EditorNode::_discard_changes(const String &p_str) { _update_scene_tabs(); if (current_option == FILE_CLOSE_ALL_AND_QUIT || current_option == FILE_CLOSE_ALL_AND_RUN_PROJECT_MANAGER) { + // If restore tabs is enabled, reopen the scene that has just been closed, so it's remembered properly. + if (bool(EDITOR_GET("interface/scene_tabs/restore_scenes_on_load"))) { + _menu_option_confirm(FILE_OPEN_PREV, true); + } if (_next_unsaved_scene(false) == -1) { current_option = current_option == FILE_CLOSE_ALL_AND_QUIT ? FILE_QUIT : RUN_PROJECT_MANAGER; _discard_changes(); @@ -2685,20 +2934,19 @@ void EditorNode::_discard_changes(const String &p_str) { args.push_back(exec.get_base_dir()); args.push_back("--project-manager"); - OS::ProcessID pid = 0; - Error err = OS::get_singleton()->execute(exec, args, false, &pid); + Error err = OS::get_singleton()->create_process(exec, args); ERR_FAIL_COND(err); } break; } } void EditorNode::_update_file_menu_opened() { - Ref<ShortCut> close_scene_sc = ED_GET_SHORTCUT("editor/close_scene"); + Ref<Shortcut> close_scene_sc = ED_GET_SHORTCUT("editor/close_scene"); close_scene_sc->set_name(TTR("Close Scene")); - Ref<ShortCut> reopen_closed_scene_sc = ED_GET_SHORTCUT("editor/reopen_closed_scene"); + Ref<Shortcut> reopen_closed_scene_sc = ED_GET_SHORTCUT("editor/reopen_closed_scene"); reopen_closed_scene_sc->set_name(TTR("Reopen Closed Scene")); PopupMenu *pop = file_menu->get_popup(); - pop->set_item_disabled(pop->get_item_index(FILE_OPEN_PREV), previous_scenes.empty()); + pop->set_item_disabled(pop->get_item_index(FILE_OPEN_PREV), previous_scenes.is_empty()); } void EditorNode::_update_file_menu_closed() { @@ -2706,8 +2954,8 @@ void EditorNode::_update_file_menu_closed() { pop->set_item_disabled(pop->get_item_index(FILE_OPEN_PREV), false); } -Control *EditorNode::get_viewport() { - return viewport; +Control *EditorNode::get_main_control() { + return main_control; } void EditorNode::_editor_select(int p_which) { @@ -2774,7 +3022,8 @@ void EditorNode::select_editor_by_name(const String &p_name) { void EditorNode::add_editor_plugin(EditorPlugin *p_editor, bool p_config_changed) { if (p_editor->has_main_screen()) { - ToolButton *tb = memnew(ToolButton); + Button *tb = memnew(Button); + tb->set_flat(true); tb->set_toggle_mode(true); tb->connect("pressed", callable_mp(singleton, &EditorNode::_editor_select), varray(singleton->main_editor_buttons.size())); tb->set_text(p_editor->get_name()); @@ -2863,8 +3112,7 @@ void EditorNode::set_addon_plugin_enabled(const String &p_addon, bool p_enabled, Ref<ConfigFile> cf; cf.instance(); - String addon_path = String("res://addons").plus_file(p_addon).plus_file("plugin.cfg"); - if (!DirAccess::exists(addon_path.get_base_dir())) { + if (!DirAccess::exists(p_addon.get_base_dir())) { ProjectSettings *ps = ProjectSettings::get_singleton(); PackedStringArray enabled_plugins = ps->get("editor_plugins/enabled"); for (int i = 0; i < enabled_plugins.size(); ++i) { @@ -2878,14 +3126,14 @@ void EditorNode::set_addon_plugin_enabled(const String &p_addon, bool p_enabled, WARN_PRINT("Addon '" + p_addon + "' failed to load. No directory found. Removing from enabled plugins."); return; } - Error err = cf->load(addon_path); + Error err = cf->load(p_addon); if (err != OK) { - show_warning(vformat(TTR("Unable to enable addon plugin at: '%s' parsing of config failed."), addon_path)); + show_warning(vformat(TTR("Unable to enable addon plugin at: '%s' parsing of config failed."), p_addon)); return; } if (!cf->has_section_key("plugin", "script")) { - show_warning(vformat(TTR("Unable to find script field for addon plugin at: 'res://addons/%s'."), p_addon)); + show_warning(vformat(TTR("Unable to find script field for addon plugin at: '%s'."), p_addon)); return; } @@ -2894,7 +3142,7 @@ void EditorNode::set_addon_plugin_enabled(const String &p_addon, bool p_enabled, // Only try to load the script if it has a name. Else, the plugin has no init script. if (script_path.length() > 0) { - script_path = String("res://addons").plus_file(p_addon).plus_file(script_path); + script_path = p_addon.get_base_dir().plus_file(script_path); script = ResourceLoader::load(script_path); if (script.is_null()) { @@ -3152,7 +3400,7 @@ int EditorNode::new_scene() { return idx; } -Error 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) { +Error 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) { if (!is_inside_tree()) { defer_load_scene = p_scene; return OK; @@ -3192,14 +3440,16 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b if (!editor_data.get_edited_scene_root() && editor_data.get_edited_scene_count() == 2) { _remove_edited_scene(); - } else { + } else if (!p_silent_change_tab) { _scene_tab_changed(idx); + } else { + set_current_scene(idx); } dependency_errors.clear(); Error err; - Ref<PackedScene> sdata = ResourceLoader::load(lpath, "", true, &err); + Ref<PackedScene> sdata = ResourceLoader::load(lpath, "", ResourceFormatLoader::CACHE_MODE_REPLACE, &err); if (!sdata.is_valid()) { _dialog_display_load_error(lpath, err); opening_prev = false; @@ -3254,13 +3504,13 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b if (!new_scene) { sdata.unref(); - _dialog_display_load_error(lpath, ERR_FILE_NOT_FOUND); + _dialog_display_load_error(lpath, ERR_FILE_CORRUPT); opening_prev = false; if (prev != -1) { set_current_scene(prev); editor_data.remove_scene(idx); } - return ERR_FILE_NOT_FOUND; + return ERR_FILE_CORRUPT; } if (p_set_inherited) { @@ -3403,10 +3653,14 @@ void EditorNode::_update_recent_scenes() { void EditorNode::_quick_opened() { Vector<String> files = quick_open->get_selected_files(); + bool open_scene_dialog = quick_open->get_base_type() == "PackedScene"; for (int i = 0; i < files.size(); i++) { String res_path = files[i]; - if (quick_open->get_base_type() == "PackedScene") { + List<String> scene_extensions; + ResourceLoader::get_recognized_extensions_for_type("PackedScene", &scene_extensions); + + if (open_scene_dialog || scene_extensions.find(files[i].get_extension())) { open_request(res_path); } else { load_resource(res_path); @@ -3462,6 +3716,7 @@ void EditorNode::register_editor_types() { ResourceSaver::set_timestamp_on_save(true); ClassDB::register_class<EditorPlugin>(); + ClassDB::register_class<EditorTranslationParserPlugin>(); ClassDB::register_class<EditorImportPlugin>(); ClassDB::register_class<EditorScript>(); ClassDB::register_class<EditorSelection>(); @@ -3475,6 +3730,8 @@ void EditorNode::register_editor_types() { ClassDB::register_class<EditorFileSystemDirectory>(); ClassDB::register_class<EditorVCSInterface>(); ClassDB::register_virtual_class<ScriptEditor>(); + ClassDB::register_virtual_class<ScriptEditorBase>(); + ClassDB::register_class<EditorSyntaxHighlighter>(); ClassDB::register_virtual_class<EditorInterface>(); ClassDB::register_class<EditorExportPlugin>(); ClassDB::register_class<EditorResourceConversionPlugin>(); @@ -3486,11 +3743,15 @@ void EditorNode::register_editor_types() { ClassDB::register_class<ScriptCreateDialog>(); ClassDB::register_class<EditorFeatureProfile>(); ClassDB::register_class<EditorSpinSlider>(); + ClassDB::register_class<EditorSceneImporterMesh>(); + ClassDB::register_class<EditorSceneImporterMeshNode3D>(); + ClassDB::register_virtual_class<FileSystemDock>(); // FIXME: Is this stuff obsolete, or should it be ported to new APIs? ClassDB::register_class<EditorScenePostImport>(); //ClassDB::register_type<EditorImportExport>(); + ClassDB::register_class<EditorDebuggerPlugin>(); } void EditorNode::unregister_editor_types() { @@ -3633,24 +3894,16 @@ Ref<Texture2D> EditorNode::get_object_icon(const Object *p_object, const String } Ref<Texture2D> EditorNode::get_class_icon(const String &p_class, const String &p_fallback) const { - ERR_FAIL_COND_V_MSG(p_class.empty(), nullptr, "Class name cannot be empty."); - - if (gui_base->has_theme_icon(p_class, "EditorIcons")) { - return gui_base->get_theme_icon(p_class, "EditorIcons"); - } + ERR_FAIL_COND_V_MSG(p_class.is_empty(), nullptr, "Class name cannot be empty."); if (ScriptServer::is_global_class(p_class)) { - String icon_path = EditorNode::get_editor_data().script_class_get_icon_path(p_class); - Ref<ImageTexture> icon = _load_custom_class_icon(icon_path); - if (icon.is_valid()) { - return icon; - } - - Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(p_class), "Script"); + Ref<ImageTexture> icon; + Ref<Script> script = EditorNode::get_editor_data().script_class_load_script(p_class); + StringName name = p_class; while (script.is_valid()) { - String current_icon_path; - script->get_language()->get_global_class_name(script->get_path(), nullptr, ¤t_icon_path); + name = EditorNode::get_editor_data().script_class_get_name(script->get_path()); + String current_icon_path = EditorNode::get_editor_data().script_class_get_icon_path(name); icon = _load_custom_class_icon(current_icon_path); if (icon.is_valid()) { return icon; @@ -3659,10 +3912,8 @@ Ref<Texture2D> EditorNode::get_class_icon(const String &p_class, const String &p } if (icon.is_null()) { - icon = gui_base->get_theme_icon(ScriptServer::get_global_class_base(p_class), "EditorIcons"); + icon = gui_base->get_theme_icon(ScriptServer::get_global_class_base(name), "EditorIcons"); } - - return icon; } const Map<String, Vector<EditorData::CustomType>> &p_map = EditorNode::get_editor_data().get_custom_types(); @@ -3677,6 +3928,10 @@ Ref<Texture2D> EditorNode::get_class_icon(const String &p_class, const String &p } } + if (gui_base->has_theme_icon(p_class, "EditorIcons")) { + return gui_base->get_theme_icon(p_class, "EditorIcons"); + } + if (p_fallback.length() && gui_base->has_theme_icon(p_fallback, "EditorIcons")) { return gui_base->get_theme_icon(p_fallback, "EditorIcons"); } @@ -3782,7 +4037,7 @@ Error EditorNode::export_preset(const String &p_preset, const String &p_path, bo void EditorNode::show_accept(const String &p_text, const String &p_title) { current_option = -1; - accept->get_ok()->set_text(p_title); + accept->get_ok_button()->set_text(p_title); accept->set_text(p_text); accept->popup_centered(); } @@ -3834,16 +4089,16 @@ void EditorNode::_dock_make_float() { window->set_title(dock->get_name()); Panel *p = memnew(Panel); p->set_mode(Panel::MODE_FOREGROUND); - p->set_anchors_and_margins_preset(Control::PRESET_WIDE); + p->set_anchors_and_offsets_preset(Control::PRESET_WIDE); window->add_child(p); MarginContainer *margin = memnew(MarginContainer); - margin->set_anchors_and_margins_preset(Control::PRESET_WIDE); + margin->set_anchors_and_offsets_preset(Control::PRESET_WIDE); margin->add_theme_constant_override("margin_right", borders.width); margin->add_theme_constant_override("margin_top", borders.height); margin->add_theme_constant_override("margin_left", borders.width); margin->add_theme_constant_override("margin_bottom", borders.height); window->add_child(margin); - dock->set_anchors_and_margins_preset(Control::PRESET_WIDE); + dock->set_anchors_and_offsets_preset(Control::PRESET_WIDE); margin->add_child(dock); window->set_wrap_controls(true); window->set_size(dock_size); @@ -4093,6 +4348,7 @@ void EditorNode::_save_docks_to_config(Ref<ConfigFile> p_layout, const String &p p_layout->set_value(p_section, "dock_filesystem_split", filesystem_dock->get_split_offset()); p_layout->set_value(p_section, "dock_filesystem_display_mode", filesystem_dock->get_display_mode()); + p_layout->set_value(p_section, "dock_filesystem_file_sort", filesystem_dock->get_file_sort()); p_layout->set_value(p_section, "dock_filesystem_file_list_display_mode", filesystem_dock->get_file_list_display_mode()); for (int i = 0; i < vsplits.size(); i++) { @@ -4155,7 +4411,6 @@ void EditorNode::_update_dock_slots_visibility() { } right_hsplit->hide(); - bottom_panel->hide(); } else { for (int i = 0; i < DOCK_SLOT_MAX; i++) { int tabs_visible = 0; @@ -4185,7 +4440,6 @@ void EditorNode::_update_dock_slots_visibility() { dock_slot[i]->set_current_tab(0); } } - bottom_panel->show(); if (right_l_vsplit->is_visible() || right_r_vsplit->is_visible()) { right_hsplit->show(); @@ -4289,6 +4543,11 @@ void EditorNode::_load_docks_from_config(Ref<ConfigFile> p_layout, const String filesystem_dock->set_display_mode(dock_filesystem_display_mode); } + if (p_layout->has_section_key(p_section, "dock_filesystem_file_sort")) { + FileSystemDock::FileSortOption dock_filesystem_file_sort = FileSystemDock::FileSortOption(int(p_layout->get_value(p_section, "dock_filesystem_file_sort"))); + filesystem_dock->set_file_sort(dock_filesystem_file_sort); + } + if (p_layout->has_section_key(p_section, "dock_filesystem_file_list_display_mode")) { FileSystemDock::FileListDisplayMode dock_filesystem_file_list_display_mode = FileSystemDock::FileListDisplayMode(int(p_layout->get_value(p_section, "dock_filesystem_file_list_display_mode"))); filesystem_dock->set_file_list_display_mode(dock_filesystem_file_list_display_mode); @@ -4367,7 +4626,7 @@ bool EditorNode::has_scenes_in_session() { return false; } Array scenes = config->get_value("EditorNode", "open_scenes"); - return !scenes.empty(); + return !scenes.is_empty(); } bool EditorNode::ensure_main_scene(bool p_from_native) { @@ -4403,10 +4662,35 @@ void EditorNode::run_play() { _run(false); } +void EditorNode::run_play_current() { + _save_default_environment(); + _menu_option_confirm(RUN_STOP, true); + _run(true); +} + +void EditorNode::run_play_custom(const String &p_custom) { + _menu_option_confirm(RUN_STOP, true); + _run(false, p_custom); +} + void EditorNode::run_stop() { _menu_option_confirm(RUN_STOP, false); } +bool EditorNode::is_run_playing() const { + EditorRun::Status status = editor_run.get_status(); + return (status == EditorRun::STATUS_PLAY || status == EditorRun::STATUS_PAUSED); +} + +String EditorNode::get_run_playing_scene() const { + String run_filename = editor_run.get_running_scene(); + if (run_filename == "" && is_run_playing()) { + run_filename = GLOBAL_DEF("application/run/main_scene", ""); // Must be the main scene then. + } + + return run_filename; +} + int EditorNode::get_current_tab() { return scene_tabs->get_current_tab(); } @@ -4452,14 +4736,14 @@ void EditorNode::_layout_menu_option(int p_id) { case SETTINGS_LAYOUT_SAVE: { current_option = p_id; layout_dialog->set_title(TTR("Save Layout")); - layout_dialog->get_ok()->set_text(TTR("Save")); + layout_dialog->get_ok_button()->set_text(TTR("Save")); layout_dialog->popup_centered(); layout_dialog->set_name_line_enabled(true); } break; case SETTINGS_LAYOUT_DELETE: { current_option = p_id; layout_dialog->set_title(TTR("Delete Layout")); - layout_dialog->get_ok()->set_text(TTR("Delete")); + layout_dialog->get_ok_button()->set_text(TTR("Delete")); layout_dialog->popup_centered(); layout_dialog->set_name_line_enabled(false); } break; @@ -4498,10 +4782,10 @@ void EditorNode::_scene_tab_closed(int p_tab, int option) { } bool unsaved = (p_tab == editor_data.get_edited_scene()) ? - saved_version != editor_data.get_undo_redo().get_version() : - editor_data.get_scene_version(p_tab) != 0; + saved_version != editor_data.get_undo_redo().get_version() : + editor_data.get_scene_version(p_tab) != 0; if (unsaved) { - save_confirmation->get_ok()->set_text(TTR("Save & Close")); + save_confirmation->get_ok_button()->set_text(TTR("Save & Close")); save_confirmation->set_text(vformat(TTR("Save changes to '%s' before closing?"), scene->get_filename() != "" ? scene->get_filename() : "unsaved scene")); save_confirmation->popup_centered(); } else { @@ -4512,7 +4796,7 @@ void EditorNode::_scene_tab_closed(int p_tab, int option) { _update_scene_tabs(); } -void EditorNode::_scene_tab_hover(int p_tab) { +void EditorNode::_scene_tab_hovered(int p_tab) { if (!bool(EDITOR_GET("interface/scene_tabs/show_thumbnail_on_hover"))) { return; } @@ -4562,13 +4846,13 @@ void EditorNode::_scene_tab_input(const Ref<InputEvent> &p_input) { scene_tabs_context_menu->add_item(TTR("Play This Scene"), RUN_PLAY_SCENE); scene_tabs_context_menu->add_separator(); - Ref<ShortCut> close_tab_sc = ED_GET_SHORTCUT("editor/close_scene"); + Ref<Shortcut> close_tab_sc = ED_GET_SHORTCUT("editor/close_scene"); close_tab_sc->set_name(TTR("Close Tab")); scene_tabs_context_menu->add_shortcut(close_tab_sc, FILE_CLOSE); - Ref<ShortCut> undo_close_tab_sc = ED_GET_SHORTCUT("editor/reopen_closed_scene"); + Ref<Shortcut> undo_close_tab_sc = ED_GET_SHORTCUT("editor/reopen_closed_scene"); undo_close_tab_sc->set_name(TTR("Undo Close Tab")); scene_tabs_context_menu->add_shortcut(undo_close_tab_sc, FILE_OPEN_PREV); - if (previous_scenes.empty()) { + if (previous_scenes.is_empty()) { scene_tabs_context_menu->set_item_disabled(scene_tabs_context_menu->get_item_index(FILE_OPEN_PREV), true); } scene_tabs_context_menu->add_item(TTR("Close Other Tabs"), FILE_CLOSE_OTHERS); @@ -4619,8 +4903,9 @@ void EditorNode::_scene_tab_changed(int p_tab) { editor_data.get_undo_redo().commit_action(); } -ToolButton *EditorNode::add_bottom_panel_item(String p_text, Control *p_item) { - ToolButton *tb = memnew(ToolButton); +Button *EditorNode::add_bottom_panel_item(String p_text, Control *p_item) { + Button *tb = memnew(Button); + tb->set_flat(true); tb->connect("toggled", callable_mp(this, &EditorNode::_bottom_panel_switch), varray(bottom_panel_items.size())); tb->set_text(p_text); tb->set_toggle_mode(true); @@ -4639,16 +4924,6 @@ ToolButton *EditorNode::add_bottom_panel_item(String p_text, Control *p_item) { return tb; } -bool EditorNode::are_bottom_panels_hidden() const { - for (int i = 0; i < bottom_panel_items.size(); i++) { - if (bottom_panel_items[i].button->is_pressed()) { - return false; - } - } - - return true; -} - void EditorNode::hide_bottom_panel() { for (int i = 0; i < bottom_panel_items.size(); i++) { if (bottom_panel_items[i].control->is_visible()) { @@ -4782,7 +5057,7 @@ void EditorNode::set_distraction_free_mode(bool p_enter) { } } -bool EditorNode::get_distraction_free_mode() const { +bool EditorNode::is_distraction_free_mode_enabled() const { return distraction_free->is_pressed(); } @@ -4900,17 +5175,10 @@ Variant EditorNode::drag_files_and_dirs(const Vector<String> &p_paths, Control * return drag_data; } -void EditorNode::add_tool_menu_item(const String &p_name, Object *p_handler, const String &p_callback, const Variant &p_ud) { - ERR_FAIL_NULL(p_handler); +void EditorNode::add_tool_menu_item(const String &p_name, const Callable &p_callback) { int idx = tool_menu->get_item_count(); tool_menu->add_item(p_name, TOOLS_CUSTOM); - - Array parameters; - parameters.push_back(p_handler->get_instance_id()); - parameters.push_back(p_callback); - parameters.push_back(p_ud); - - tool_menu->set_item_metadata(idx, parameters); + tool_menu->set_item_metadata(idx, p_callback); } void EditorNode::add_tool_submenu_item(const String &p_name, PopupMenu *p_submenu) { @@ -4950,9 +5218,7 @@ void EditorNode::_global_menu_new_window(const Variant &p_tag) { List<String> args; args.push_back("-p"); String exec = OS::get_singleton()->get_executable_path(); - - OS::ProcessID pid = 0; - OS::get_singleton()->execute(exec, args, false, &pid); + OS::get_singleton()->create_process(exec, args); } } @@ -4966,7 +5232,6 @@ void EditorNode::_dropped_files(const Vector<String> &p_files, int p_screen) { void EditorNode::_add_dropped_files_recursive(const Vector<String> &p_files, String to_path) { DirAccessRef dir = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - Vector<String> just_copy = String("ttf,otf").split(","); for (int i = 0; i < p_files.size(); i++) { String from = p_files[i]; @@ -4989,7 +5254,7 @@ void EditorNode::_add_dropped_files_recursive(const Vector<String> &p_files, Str next_file = sub_dir->get_next(); } - if (!sub_files.empty()) { + if (!sub_files.is_empty()) { dir->make_dir(to); _add_dropped_files_recursive(sub_files, to); } @@ -4997,9 +5262,6 @@ void EditorNode::_add_dropped_files_recursive(const Vector<String> &p_files, Str continue; } - if (!ResourceFormatImporter::get_singleton()->can_be_imported(from) && (just_copy.find(from.get_extension().to_lower()) == -1)) { - continue; - } dir->copy(from, to); } } @@ -5009,6 +5271,8 @@ void EditorNode::_file_access_close_error_notify(const String &p_str) { } void EditorNode::reload_scene(const String &p_path) { + /* + * No longer necesary since scenes now reset and reload their internal resource if needed. //first of all, reload internal textures, materials, meshes, etc. as they might have changed on disk List<Ref<Resource>> cached; @@ -5026,6 +5290,8 @@ void EditorNode::reload_scene(const String &p_path) { to_clear.pop_front(); } + */ + int scene_idx = -1; for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { if (editor_data.get_scene_path(i) == p_path) { @@ -5195,9 +5461,11 @@ void EditorNode::_feature_profile_changed() { TabContainer *node_tabs = cast_to<TabContainer>(node_dock->get_parent()); TabContainer *fs_tabs = cast_to<TabContainer>(filesystem_dock->get_parent()); if (profile.is_valid()) { - import_tabs->set_tab_hidden(import_dock->get_index(), profile->is_feature_disabled(EditorFeatureProfile::FEATURE_IMPORT_DOCK)); node_tabs->set_tab_hidden(node_dock->get_index(), profile->is_feature_disabled(EditorFeatureProfile::FEATURE_NODE_DOCK)); - fs_tabs->set_tab_hidden(filesystem_dock->get_index(), profile->is_feature_disabled(EditorFeatureProfile::FEATURE_FILESYSTEM_DOCK)); + // The Import dock is useless without the FileSystem dock. Ensure the configuration is valid. + bool fs_dock_disabled = profile->is_feature_disabled(EditorFeatureProfile::FEATURE_FILESYSTEM_DOCK); + fs_tabs->set_tab_hidden(filesystem_dock->get_index(), fs_dock_disabled); + import_tabs->set_tab_hidden(import_dock->get_index(), fs_dock_disabled || profile->is_feature_disabled(EditorFeatureProfile::FEATURE_IMPORT_DOCK)); main_editor_buttons[EDITOR_3D]->set_visible(!profile->is_feature_disabled(EditorFeatureProfile::FEATURE_3D)); main_editor_buttons[EDITOR_SCRIPT]->set_visible(!profile->is_feature_disabled(EditorFeatureProfile::FEATURE_SCRIPT)); @@ -5267,6 +5535,7 @@ void EditorNode::_bind_methods() { ADD_SIGNAL(MethodInfo("request_help_search")); ADD_SIGNAL(MethodInfo("script_add_function_request", PropertyInfo(Variant::OBJECT, "obj"), PropertyInfo(Variant::STRING, "function"), PropertyInfo(Variant::PACKED_STRING_ARRAY, "args"))); ADD_SIGNAL(MethodInfo("resource_saved", PropertyInfo(Variant::OBJECT, "obj"))); + ADD_SIGNAL(MethodInfo("project_settings_changed")); } static Node *_resource_get_edited_scene() { @@ -5280,18 +5549,19 @@ void EditorNode::_print_handler(void *p_this, const String &p_string, bool p_err static void _execute_thread(void *p_ud) { EditorNode::ExecuteThreadArgs *eta = (EditorNode::ExecuteThreadArgs *)p_ud; - Error err = OS::get_singleton()->execute(eta->path, eta->args, true, nullptr, &eta->output, &eta->exitcode, true, &eta->execute_output_mutex); + Error err = OS::get_singleton()->execute(eta->path, eta->args, &eta->output, &eta->exitcode, true, &eta->execute_output_mutex); print_verbose("Thread exit status: " + itos(eta->exitcode)); if (err != OK) { eta->exitcode = err; } - eta->done = true; + eta->done.set(); + ; } int 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) { execute_output_dialog->set_title(p_title); - execute_output_dialog->get_ok()->set_disabled(true); + execute_output_dialog->get_ok_button()->set_disabled(true); execute_outputs->clear(); execute_outputs->set_scroll_follow(true); execute_output_dialog->popup_centered_ratio(); @@ -5300,15 +5570,12 @@ int EditorNode::execute_and_show_output(const String &p_title, const String &p_p eta.path = p_path; eta.args = p_arguments; eta.exitcode = 255; - eta.done = false; int prev_len = 0; - eta.execute_output_thread = Thread::create(_execute_thread, &eta); + eta.execute_output_thread.start(_execute_thread, &eta); - ERR_FAIL_COND_V(!eta.execute_output_thread, 0); - - while (!eta.done) { + while (!eta.done.is_set()) { { MutexLock lock(eta.execute_output_mutex); if (prev_len != eta.output.length()) { @@ -5321,8 +5588,7 @@ int EditorNode::execute_and_show_output(const String &p_title, const String &p_p OS::get_singleton()->delay_usec(1000); } - Thread::wait_to_finish(eta.execute_output_thread); - memdelete(eta.execute_output_thread); + eta.execute_output_thread.wait_to_finish(); execute_outputs->add_text("\nExit Code: " + itos(eta.exitcode)); if (p_close_on_errors && eta.exitcode != 0) { @@ -5332,11 +5598,15 @@ int EditorNode::execute_and_show_output(const String &p_title, const String &p_p execute_output_dialog->hide(); } - execute_output_dialog->get_ok()->set_disabled(false); + execute_output_dialog->get_ok_button()->set_disabled(false); return eta.exitcode; } +void EditorNode::notify_settings_changed() { + settings_changed = true; +} + EditorNode::EditorNode() { Input::get_singleton()->set_use_accumulated_input(true); Resource::_get_local_scene_func = _resource_get_edited_scene; @@ -5393,46 +5663,57 @@ EditorNode::EditorNode() { { int display_scale = EditorSettings::get_singleton()->get("interface/editor/display_scale"); - float custom_display_scale = EditorSettings::get_singleton()->get("interface/editor/custom_display_scale"); switch (display_scale) { case 0: { - // Try applying a suitable display scale automatically - const int screen = DisplayServer::get_singleton()->window_get_current_screen(); + // Try applying a suitable display scale automatically. + // The code below is adapted in `editor/editor_settings.cpp` and `editor/project_manager.cpp`. + // Make sure to update those when modifying the code below. #ifdef OSX_ENABLED - editor_set_scale(DisplayServer::get_singleton()->screen_get_scale(screen)); + editor_set_scale(DisplayServer::get_singleton()->screen_get_max_scale()); #else - editor_set_scale(DisplayServer::get_singleton()->screen_get_dpi(screen) >= 192 && DisplayServer::get_singleton()->screen_get_size(screen).x > 2000 ? 2.0 : 1.0); + const int screen = DisplayServer::get_singleton()->window_get_current_screen(); + float scale; + if (DisplayServer::get_singleton()->screen_get_dpi(screen) >= 192 && DisplayServer::get_singleton()->screen_get_size(screen).y >= 1400) { + // hiDPI display. + scale = 2.0; + } else if (DisplayServer::get_singleton()->screen_get_size(screen).y >= 1700) { + // Likely a hiDPI display, but we aren't certain due to the returned DPI. + // Use an intermediate scale to handle this situation. + scale = 1.5; + } else if (DisplayServer::get_singleton()->screen_get_size(screen).y <= 800) { + // Small loDPI display. Use a smaller display scale so that editor elements fit more easily. + // Icons won't look great, but this is better than having editor elements overflow from its window. + scale = 0.75; + } else { + scale = 1.0; + } + + editor_set_scale(scale); #endif } break; - case 1: { + case 1: editor_set_scale(0.75); - } break; - - case 2: { + break; + case 2: editor_set_scale(1.0); - } break; - - case 3: { + break; + case 3: editor_set_scale(1.25); - } break; - - case 4: { + break; + case 4: editor_set_scale(1.5); - } break; - - case 5: { + break; + case 5: editor_set_scale(1.75); - } break; - - case 6: { + break; + case 6: editor_set_scale(2.0); - } break; - - default: { - editor_set_scale(custom_display_scale); - } break; + break; + default: + editor_set_scale(EditorSettings::get_singleton()->get("interface/editor/custom_display_scale")); + break; } } @@ -5466,10 +5747,10 @@ EditorNode::EditorNode() { import_cubemap_array->set_mode(ResourceImporterLayeredTexture::MODE_CUBEMAP_ARRAY); ResourceFormatImporter::get_singleton()->add_importer(import_cubemap_array); - /*Ref<ResourceImporterLayeredTexture> import_3d; + Ref<ResourceImporterLayeredTexture> import_3d; import_3d.instance(); import_3d->set_mode(ResourceImporterLayeredTexture::MODE_3D); - ResourceFormatImporter::get_singleton()->add_importer(import_3d);*/ + ResourceFormatImporter::get_singleton()->add_importer(import_3d); Ref<ResourceImporterImage> import_image; import_image.instance(); @@ -5512,10 +5793,6 @@ EditorNode::EditorNode() { import_obj2.instance(); import_scene->add_importer(import_obj2); - Ref<EditorSceneImporterGLTF> import_gltf; - import_gltf.instance(); - import_scene->add_importer(import_gltf); - Ref<EditorSceneImporterESCN> import_escn; import_escn.instance(); import_scene->add_importer(import_escn); @@ -5540,8 +5817,6 @@ EditorNode::EditorNode() { EditorInspector::add_inspector_plugin(smp); } - _pvrtc_register_compressors(); - editor_selection = memnew(EditorSelection); EditorFileSystem *efs = memnew(EditorFileSystem); @@ -5564,7 +5839,7 @@ EditorNode::EditorNode() { register_exporters(); - GLOBAL_DEF("editor/main_run_args", ""); + GLOBAL_DEF("editor/run/main_run_args", ""); ClassDB::set_class_enabled("RootMotionView", true); @@ -5577,7 +5852,6 @@ EditorNode::EditorNode() { EDITOR_DEF("run/output/always_close_output_on_stop", true); EDITOR_DEF("run/auto_save/save_before_running", true); EDITOR_DEF_RST("interface/editor/save_each_scene_on_quit", true); - EDITOR_DEF("interface/editor/quit_confirmation", true); EDITOR_DEF("interface/editor/show_update_spinner", false); EDITOR_DEF("interface/editor/update_continuously", false); EDITOR_DEF_RST("interface/scene_tabs/restore_scenes_on_load", false); @@ -5590,18 +5864,18 @@ EditorNode::EditorNode() { EDITOR_DEF("interface/inspector/horizontal_vector2_editing", false); EDITOR_DEF("interface/inspector/horizontal_vector_types_editing", true); EDITOR_DEF("interface/inspector/open_resources_in_current_inspector", true); - EDITOR_DEF("interface/inspector/resources_to_open_in_new_inspector", "StandardMaterial3D,ORMMaterial3D,Script,MeshLibrary,TileSet"); + EDITOR_DEF("interface/inspector/resources_to_open_in_new_inspector", "Script,MeshLibrary,TileSet"); EDITOR_DEF("interface/inspector/default_color_picker_mode", 0); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "interface/inspector/default_color_picker_mode", PROPERTY_HINT_ENUM, "RGB,HSV,RAW", PROPERTY_USAGE_DEFAULT)); EDITOR_DEF("run/auto_save/save_before_running", true); theme_base = memnew(Control); add_child(theme_base); - theme_base->set_anchors_and_margins_preset(Control::PRESET_WIDE); + theme_base->set_anchors_and_offsets_preset(Control::PRESET_WIDE); gui_base = memnew(Panel); theme_base->add_child(gui_base); - gui_base->set_anchors_and_margins_preset(Control::PRESET_WIDE); + gui_base->set_anchors_and_offsets_preset(Control::PRESET_WIDE); theme_base->set_theme(theme); gui_base->set_theme(theme); @@ -5613,13 +5887,13 @@ EditorNode::EditorNode() { gui_base->add_child(progress_dialog); // take up all screen - gui_base->set_anchor(MARGIN_RIGHT, Control::ANCHOR_END); - gui_base->set_anchor(MARGIN_BOTTOM, Control::ANCHOR_END); + gui_base->set_anchor(SIDE_RIGHT, Control::ANCHOR_END); + gui_base->set_anchor(SIDE_BOTTOM, Control::ANCHOR_END); gui_base->set_end(Point2(0, 0)); main_vbox = memnew(VBoxContainer); gui_base->add_child(main_vbox); - main_vbox->set_anchors_and_margins_preset(Control::PRESET_WIDE, Control::PRESET_MODE_MINSIZE, 8); + main_vbox->set_anchors_and_offsets_preset(Control::PRESET_WIDE, Control::PRESET_MODE_MINSIZE, 8); main_vbox->add_theme_constant_override("separation", 8 * EDSCALE); menu_hb = memnew(HBoxContainer); @@ -5696,8 +5970,13 @@ EditorNode::EditorNode() { dock_select_popup->add_child(dock_vb); HBoxContainer *dock_hb = memnew(HBoxContainer); - dock_tab_move_left = memnew(ToolButton); - dock_tab_move_left->set_icon(theme->get_icon("Back", "EditorIcons")); + dock_tab_move_left = memnew(Button); + dock_tab_move_left->set_flat(true); + if (gui_base->is_layout_rtl()) { + dock_tab_move_left->set_icon(theme->get_icon("Forward", "EditorIcons")); + } else { + dock_tab_move_left->set_icon(theme->get_icon("Back", "EditorIcons")); + } dock_tab_move_left->set_focus_mode(Control::FOCUS_NONE); dock_tab_move_left->connect("pressed", callable_mp(this, &EditorNode::_dock_move_left)); dock_hb->add_child(dock_tab_move_left); @@ -5708,8 +5987,13 @@ EditorNode::EditorNode() { dock_label->set_align(Label::ALIGN_CENTER); dock_hb->add_child(dock_label); - dock_tab_move_right = memnew(ToolButton); - dock_tab_move_right->set_icon(theme->get_icon("Forward", "EditorIcons")); + dock_tab_move_right = memnew(Button); + dock_tab_move_right->set_flat(true); + if (gui_base->is_layout_rtl()) { + dock_tab_move_right->set_icon(theme->get_icon("Forward", "EditorIcons")); + } else { + dock_tab_move_right->set_icon(theme->get_icon("Back", "EditorIcons")); + } dock_tab_move_right->set_focus_mode(Control::FOCUS_NONE); dock_tab_move_right->connect("pressed", callable_mp(this, &EditorNode::_dock_move_right)); @@ -5776,8 +6060,8 @@ EditorNode::EditorNode() { tab_preview_panel->add_child(tab_preview); scene_tabs = memnew(Tabs); - scene_tabs->add_theme_style_override("tab_fg", gui_base->get_theme_stylebox("SceneTabFG", "EditorStyles")); - scene_tabs->add_theme_style_override("tab_bg", gui_base->get_theme_stylebox("SceneTabBG", "EditorStyles")); + scene_tabs->add_theme_style_override("tab_selected", gui_base->get_theme_stylebox("SceneTabFG", "EditorStyles")); + scene_tabs->add_theme_style_override("tab_unselected", gui_base->get_theme_stylebox("SceneTabBG", "EditorStyles")); scene_tabs->set_select_with_rmb(true); scene_tabs->add_tab("unsaved"); scene_tabs->set_tab_align(Tabs::ALIGN_LEFT); @@ -5786,8 +6070,8 @@ EditorNode::EditorNode() { scene_tabs->set_drag_to_rearrange_enabled(true); scene_tabs->connect("tab_changed", callable_mp(this, &EditorNode::_scene_tab_changed)); scene_tabs->connect("right_button_pressed", callable_mp(this, &EditorNode::_scene_tab_script_edited)); - scene_tabs->connect("tab_close", callable_mp(this, &EditorNode::_scene_tab_closed), varray(SCENE_TAB_CLOSE)); - scene_tabs->connect("tab_hover", callable_mp(this, &EditorNode::_scene_tab_hover)); + scene_tabs->connect("tab_closed", callable_mp(this, &EditorNode::_scene_tab_closed), varray(SCENE_TAB_CLOSE)); + scene_tabs->connect("tab_hovered", callable_mp(this, &EditorNode::_scene_tab_hovered)); scene_tabs->connect("mouse_exited", callable_mp(this, &EditorNode::_scene_tab_exit)); scene_tabs->connect("gui_input", callable_mp(this, &EditorNode::_scene_tab_input)); scene_tabs->connect("reposition_active_tab_request", callable_mp(this, &EditorNode::_reposition_active_tab)); @@ -5802,7 +6086,8 @@ EditorNode::EditorNode() { srt->add_child(tabbar_container); tabbar_container->add_child(scene_tabs); - distraction_free = memnew(ToolButton); + distraction_free = memnew(Button); + distraction_free->set_flat(true); #ifdef OSX_ENABLED distraction_free->set_shortcut(ED_SHORTCUT("editor/distraction_free_mode", TTR("Distraction Free Mode"), KEY_MASK_CMD | KEY_MASK_CTRL | KEY_D)); #else @@ -5813,12 +6098,13 @@ EditorNode::EditorNode() { distraction_free->set_icon(gui_base->get_theme_icon("DistractionFree", "EditorIcons")); distraction_free->set_toggle_mode(true); - scene_tab_add = memnew(ToolButton); + scene_tab_add = memnew(Button); + scene_tab_add->set_flat(true); tabbar_container->add_child(scene_tab_add); tabbar_container->add_child(distraction_free); scene_tab_add->set_tooltip(TTR("Add a new scene.")); scene_tab_add->set_icon(gui_base->get_theme_icon("Add", "EditorIcons")); - scene_tab_add->add_theme_color_override("icon_color_normal", Color(0.6f, 0.6f, 0.6f, 0.8f)); + scene_tab_add->add_theme_color_override("icon_normal_color", Color(0.6f, 0.6f, 0.6f, 0.8f)); scene_tab_add->connect("pressed", callable_mp(this, &EditorNode::_menu_option), make_binds(FILE_NEW_SCENE)); scene_root_parent = memnew(PanelContainer); @@ -5835,10 +6121,10 @@ EditorNode::EditorNode() { scene_root->set_disable_input(true); scene_root->set_as_audio_listener_2d(true); - viewport = memnew(VBoxContainer); - viewport->set_v_size_flags(Control::SIZE_EXPAND_FILL); - viewport->add_theme_constant_override("separation", 0); - scene_root_parent->add_child(viewport); + main_control = memnew(VBoxContainer); + main_control->set_v_size_flags(Control::SIZE_EXPAND_FILL); + main_control->add_theme_constant_override("separation", 0); + scene_root_parent->add_child(main_control); HBoxContainer *left_menu_hb = memnew(HBoxContainer); menu_hb->add_child(left_menu_hb); @@ -5850,7 +6136,8 @@ EditorNode::EditorNode() { file_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox("MenuHover", "EditorStyles")); left_menu_hb->add_child(file_menu); - prev_scene = memnew(ToolButton); + prev_scene = memnew(Button); + prev_scene->set_flat(true); prev_scene->set_icon(gui_base->get_theme_icon("PrevScene", "EditorIcons")); prev_scene->set_tooltip(TTR("Go to previously opened scene.")); prev_scene->set_disabled(true); @@ -5878,9 +6165,6 @@ EditorNode::EditorNode() { project_settings = memnew(ProjectSettingsEditor(&editor_data)); gui_base->add_child(project_settings); - run_settings_dialog = memnew(RunSettingsDialog); - gui_base->add_child(run_settings_dialog); - export_template_manager = memnew(ExportTemplateManager); gui_base->add_child(export_template_manager); @@ -5931,8 +6215,8 @@ EditorNode::EditorNode() { pm_export->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option)); p->add_separator(); - p->add_shortcut(ED_SHORTCUT("editor/undo", TTR("Undo"), KEY_MASK_CMD + KEY_Z), EDIT_UNDO, true); - p->add_shortcut(ED_SHORTCUT("editor/redo", TTR("Redo"), KEY_MASK_CMD + KEY_MASK_SHIFT + KEY_Z), EDIT_REDO, true); + p->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO, true); + p->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO, true); p->add_separator(); p->add_shortcut(ED_SHORTCUT("editor/reload_saved_scene", TTR("Reload Saved Scene")), EDIT_RELOAD_SAVED_SCENE); @@ -6038,7 +6322,9 @@ EditorNode::EditorNode() { #else p->add_shortcut(ED_SHORTCUT("editor/fullscreen_mode", TTR("Toggle Fullscreen"), KEY_MASK_SHIFT | KEY_F11), SETTINGS_TOGGLE_FULLSCREEN); #endif -#ifdef WINDOWS_ENABLED +#if defined(WINDOWS_ENABLED) && defined(WINDOWS_SUBSYSTEM_CONSOLE) + // The console can only be toggled if the application was built for the console subsystem, + // not the GUI subsystem. p->add_item(TTR("Toggle System Console"), SETTINGS_TOGGLE_CONSOLE); #endif p->add_separator(); @@ -6066,7 +6352,7 @@ EditorNode::EditorNode() { p = help_menu->get_popup(); p->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option)); - p->add_icon_shortcut(gui_base->get_theme_icon("HelpSearch", "EditorIcons"), ED_SHORTCUT("editor/editor_help", TTR("Search"), KEY_MASK_SHIFT | KEY_F1), HELP_SEARCH); + p->add_icon_shortcut(gui_base->get_theme_icon("HelpSearch", "EditorIcons"), ED_SHORTCUT("editor/editor_help", TTR("Search")), HELP_SEARCH); p->add_separator(); p->add_icon_shortcut(gui_base->get_theme_icon("Instance", "EditorIcons"), ED_SHORTCUT("editor/online_docs", TTR("Online Docs")), HELP_DOCS); p->add_icon_shortcut(gui_base->get_theme_icon("Instance", "EditorIcons"), ED_SHORTCUT("editor/q&a", TTR("Q&A")), HELP_QA); @@ -6079,7 +6365,8 @@ EditorNode::EditorNode() { HBoxContainer *play_hb = memnew(HBoxContainer); menu_hb->add_child(play_hb); - play_button = memnew(ToolButton); + play_button = memnew(Button); + play_button->set_flat(true); play_hb->add_child(play_button); play_button->set_toggle_mode(true); play_button->set_icon(gui_base->get_theme_icon("MainPlay", "EditorIcons")); @@ -6092,7 +6379,8 @@ EditorNode::EditorNode() { play_button->set_shortcut(ED_SHORTCUT("editor/play", TTR("Play"), KEY_F5)); #endif - pause_button = memnew(ToolButton); + pause_button = memnew(Button); + pause_button->set_flat(true); pause_button->set_toggle_mode(true); pause_button->set_icon(gui_base->get_theme_icon("Pause", "EditorIcons")); pause_button->set_focus_mode(Control::FOCUS_NONE); @@ -6105,7 +6393,8 @@ EditorNode::EditorNode() { pause_button->set_shortcut(ED_SHORTCUT("editor/pause_scene", TTR("Pause Scene"), KEY_F7)); #endif - stop_button = memnew(ToolButton); + stop_button = memnew(Button); + stop_button->set_flat(true); play_hb->add_child(stop_button); stop_button->set_focus_mode(Control::FOCUS_NONE); stop_button->set_icon(gui_base->get_theme_icon("Stop", "EditorIcons")); @@ -6122,7 +6411,8 @@ EditorNode::EditorNode() { play_hb->add_child(run_native); run_native->connect("native_run", callable_mp(this, &EditorNode::_run_native)); - play_scene_button = memnew(ToolButton); + play_scene_button = memnew(Button); + play_scene_button->set_flat(true); play_hb->add_child(play_scene_button); play_scene_button->set_toggle_mode(true); play_scene_button->set_focus_mode(Control::FOCUS_NONE); @@ -6135,7 +6425,8 @@ EditorNode::EditorNode() { play_scene_button->set_shortcut(ED_SHORTCUT("editor/play_scene", TTR("Play Scene"), KEY_F6)); #endif - play_custom_scene_button = memnew(ToolButton); + play_custom_scene_button = memnew(Button); + play_custom_scene_button->set_flat(true); play_hb->add_child(play_custom_scene_button); play_custom_scene_button->set_toggle_mode(true); play_custom_scene_button->set_focus_mode(Control::FOCUS_NONE); @@ -6157,15 +6448,16 @@ EditorNode::EditorNode() { video_driver->set_focus_mode(Control::FOCUS_NONE); video_driver->connect("item_selected", callable_mp(this, &EditorNode::_video_driver_selected)); video_driver->add_theme_font_override("font", gui_base->get_theme_font("bold", "EditorFonts")); + video_driver->add_theme_font_size_override("font_size", gui_base->get_theme_font_size("bold_size", "EditorFonts")); // TODO re-enable when GLES2 is ported video_driver->set_disabled(true); right_menu_hb->add_child(video_driver); #ifndef _MSC_VER -#warning neeeds to be reimplemented +#warning needs to be reimplemented #endif #if 0 - String video_drivers = ProjectSettings::get_singleton()->get_custom_property_info()["rendering/quality/driver/driver_name"].hint_string; + String video_drivers = ProjectSettings::get_singleton()->get_custom_property_info()["rendering/driver/driver_name"].hint_string; String current_video_driver = OS::get_singleton()->get_video_driver_name(OS::get_singleton()->get_current_video_driver()); video_driver_current = 0; for (int i = 0; i < video_drivers.get_slice_count(","); i++) { @@ -6183,7 +6475,7 @@ EditorNode::EditorNode() { #endif video_restart_dialog = memnew(ConfirmationDialog); video_restart_dialog->set_text(TTR("Changing the video driver requires restarting the editor.")); - video_restart_dialog->get_ok()->set_text(TTR("Save & Restart")); + video_restart_dialog->get_ok_button()->set_text(TTR("Save & Restart")); video_restart_dialog->connect("confirmed", callable_mp(this, &EditorNode::_menu_option), varray(SET_VIDEO_DRIVER_SAVE_AND_RESTART)); gui_base->add_child(video_restart_dialog); @@ -6296,7 +6588,8 @@ EditorNode::EditorNode() { version_label->set_self_modulate(Color(1, 1, 1, 0.6)); bottom_panel_hb->add_child(version_label); - bottom_panel_raise = memnew(ToolButton); + bottom_panel_raise = memnew(Button); + bottom_panel_raise->set_flat(true); bottom_panel_raise->set_icon(gui_base->get_theme_icon("ExpandBottomDock", "EditorIcons")); bottom_panel_raise->set_shortcut(ED_SHORTCUT("editor/bottom_panel_expand", TTR("Expand Bottom Panel"), KEY_MASK_SHIFT | KEY_F12)); @@ -6307,13 +6600,16 @@ EditorNode::EditorNode() { bottom_panel_raise->connect("toggled", callable_mp(this, &EditorNode::_bottom_panel_raise_toggled)); log = memnew(EditorLog); - ToolButton *output_button = add_bottom_panel_item(TTR("Output"), log); + Button *output_button = add_bottom_panel_item(TTR("Output"), log); log->set_tool_button(output_button); old_split_ofs = 0; center_split->connect("resized", callable_mp(this, &EditorNode::_vp_resized)); + native_shader_source_visualizer = memnew(EditorNativeShaderSourceVisualizer); + gui_base->add_child(native_shader_source_visualizer); + orphan_resources = memnew(OrphanResourcesDialog); gui_base->add_child(orphan_resources); @@ -6322,26 +6618,26 @@ EditorNode::EditorNode() { confirmation->connect("confirmed", callable_mp(this, &EditorNode::_menu_confirm_current)); save_confirmation = memnew(ConfirmationDialog); - save_confirmation->add_button(TTR("Don't Save"), DisplayServer::get_singleton()->get_swap_ok_cancel(), "discard"); + save_confirmation->add_button(TTR("Don't Save"), DisplayServer::get_singleton()->get_swap_cancel_ok(), "discard"); gui_base->add_child(save_confirmation); save_confirmation->connect("confirmed", callable_mp(this, &EditorNode::_menu_confirm_current)); save_confirmation->connect("custom_action", callable_mp(this, &EditorNode::_discard_changes)); custom_build_manage_templates = memnew(ConfirmationDialog); custom_build_manage_templates->set_text(TTR("Android build template is missing, please install relevant templates.")); - custom_build_manage_templates->get_ok()->set_text(TTR("Manage Templates")); + custom_build_manage_templates->get_ok_button()->set_text(TTR("Manage Templates")); custom_build_manage_templates->connect("confirmed", callable_mp(this, &EditorNode::_menu_option), varray(SETTINGS_MANAGE_EXPORT_TEMPLATES)); gui_base->add_child(custom_build_manage_templates); install_android_build_template = memnew(ConfirmationDialog); install_android_build_template->set_text(TTR("This will set up your project for custom 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 custom builds instead of using pre-built APKs, the \"Use Custom Build\" option should be enabled in the Android export preset.")); - install_android_build_template->get_ok()->set_text(TTR("Install")); + install_android_build_template->get_ok_button()->set_text(TTR("Install")); install_android_build_template->connect("confirmed", callable_mp(this, &EditorNode::_menu_confirm_current)); gui_base->add_child(install_android_build_template); remove_android_build_template = memnew(ConfirmationDialog); 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.")); - remove_android_build_template->get_ok()->set_text(TTR("Show in File Manager")); + remove_android_build_template->get_ok_button()->set_text(TTR("Show in File Manager")); remove_android_build_template->connect("confirmed", callable_mp(this, &EditorNode::_menu_option), varray(FILE_EXPLORE_ANDROID_BUILD_TEMPLATES)); gui_base->add_child(remove_android_build_template); @@ -6394,6 +6690,30 @@ EditorNode::EditorNode() { //plugin stuff add_editor_plugin(memnew(DebuggerEditorPlugin(this, debug_menu))); + + disk_changed = memnew(ConfirmationDialog); + { + VBoxContainer *vbc = memnew(VBoxContainer); + disk_changed->add_child(vbc); + + Label *dl = memnew(Label); + dl->set_text(TTR("The following files are newer on disk.\nWhat action should be taken?")); + vbc->add_child(dl); + + disk_changed_list = memnew(Tree); + vbc->add_child(disk_changed_list); + disk_changed_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); + + disk_changed->connect("confirmed", callable_mp(this, &EditorNode::_reload_modified_scenes)); + disk_changed->connect("confirmed", callable_mp(this, &EditorNode::_reload_project_settings)); + disk_changed->get_ok_button()->set_text(TTR("Reload")); + + disk_changed->add_button(TTR("Resave"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave"); + disk_changed->connect("custom_action", callable_mp(this, &EditorNode::_resave_scenes)); + } + + gui_base->add_child(disk_changed); + add_editor_plugin(memnew(AnimationPlayerEditorPlugin(this))); add_editor_plugin(memnew(CanvasItemEditorPlugin(this))); add_editor_plugin(memnew(Node3DEditorPlugin(this))); @@ -6455,8 +6775,11 @@ EditorNode::EditorNode() { add_editor_plugin(memnew(GradientEditorPlugin(this))); add_editor_plugin(memnew(CollisionShape2DEditorPlugin(this))); add_editor_plugin(memnew(CurveEditorPlugin(this))); + add_editor_plugin(memnew(FontEditorPlugin(this))); + add_editor_plugin(memnew(OpenTypeFeaturesEditorPlugin(this))); add_editor_plugin(memnew(TextureEditorPlugin(this))); add_editor_plugin(memnew(TextureLayeredEditorPlugin(this))); + add_editor_plugin(memnew(Texture3DEditorPlugin(this))); add_editor_plugin(memnew(AudioStreamEditorPlugin(this))); add_editor_plugin(memnew(AudioBusesEditorPlugin(audio_bus_editor))); add_editor_plugin(memnew(Skeleton3DEditorPlugin(this))); @@ -6464,6 +6787,7 @@ EditorNode::EditorNode() { add_editor_plugin(memnew(PhysicalBone3DEditorPlugin(this))); add_editor_plugin(memnew(MeshEditorPlugin(this))); add_editor_plugin(memnew(MaterialEditorPlugin(this))); + add_editor_plugin(memnew(GPUParticlesCollisionSDFEditorPlugin(this))); for (int i = 0; i < EditorPlugins::get_plugin_count(); i++) { add_editor_plugin(EditorPlugins::create(i, this)); @@ -6474,6 +6798,7 @@ EditorNode::EditorNode() { } resource_preview->add_preview_generator(Ref<EditorTexturePreviewPlugin>(memnew(EditorTexturePreviewPlugin))); + resource_preview->add_preview_generator(Ref<EditorImagePreviewPlugin>(memnew(EditorImagePreviewPlugin))); resource_preview->add_preview_generator(Ref<EditorPackedScenePreviewPlugin>(memnew(EditorPackedScenePreviewPlugin))); resource_preview->add_preview_generator(Ref<EditorMaterialPreviewPlugin>(memnew(EditorMaterialPreviewPlugin))); resource_preview->add_preview_generator(Ref<EditorScriptPreviewPlugin>(memnew(EditorScriptPreviewPlugin))); @@ -6525,6 +6850,10 @@ EditorNode::EditorNode() { EditorExport::get_singleton()->add_export_plugin(export_text_to_binary_plugin); + Ref<PackedSceneEditorTranslationParserPlugin> packed_scene_translation_parser_plugin; + packed_scene_translation_parser_plugin.instance(); + EditorTranslationParser::get_singleton()->add_parser(packed_scene_translation_parser_plugin, EditorTranslationParser::STANDARD); + _edit_current(); current = nullptr; saving_resource = Ref<Resource>(); @@ -6535,8 +6864,8 @@ EditorNode::EditorNode() { set_process(true); open_imported = memnew(ConfirmationDialog); - open_imported->get_ok()->set_text(TTR("Open Anyway")); - new_inherited_button = open_imported->add_button(TTR("New Inherited"), !DisplayServer::get_singleton()->get_swap_ok_cancel(), "inherit"); + open_imported->get_ok_button()->set_text(TTR("Open Anyway")); + new_inherited_button = open_imported->add_button(TTR("New Inherited"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "inherit"); open_imported->connect("confirmed", callable_mp(this, &EditorNode::_open_imported)); open_imported->connect("custom_action", callable_mp(this, &EditorNode::_inherit_imported)); gui_base->add_child(open_imported); @@ -6568,6 +6897,7 @@ EditorNode::EditorNode() { gui_base->add_child(load_error_dialog); execute_outputs = memnew(RichTextLabel); + execute_outputs->set_selection_enabled(true); execute_output_dialog = memnew(AcceptDialog); execute_output_dialog->add_child(execute_outputs); execute_output_dialog->set_title(""); @@ -6584,7 +6914,7 @@ EditorNode::EditorNode() { pick_main_scene = memnew(ConfirmationDialog); gui_base->add_child(pick_main_scene); - pick_main_scene->get_ok()->set_text(TTR("Select")); + pick_main_scene->get_ok_button()->set_text(TTR("Select")); pick_main_scene->connect("confirmed", callable_mp(this, &EditorNode::_menu_option), varray(SETTINGS_PICK_MAIN_SCENE)); for (int i = 0; i < _init_callbacks.size(); i++) { @@ -6612,14 +6942,16 @@ EditorNode::EditorNode() { ED_SHORTCUT("editor/editor_2d", TTR("Open 2D Editor"), KEY_MASK_ALT | KEY_1); ED_SHORTCUT("editor/editor_3d", TTR("Open 3D Editor"), KEY_MASK_ALT | KEY_2); ED_SHORTCUT("editor/editor_script", TTR("Open Script Editor"), KEY_MASK_ALT | KEY_3); + ED_SHORTCUT("editor/editor_assetlib", TTR("Open Asset Library"), KEY_MASK_ALT | KEY_4); ED_SHORTCUT("editor/editor_help", TTR("Search Help"), KEY_MASK_ALT | KEY_SPACE); #else - ED_SHORTCUT("editor/editor_2d", TTR("Open 2D Editor"), KEY_F1); - ED_SHORTCUT("editor/editor_3d", TTR("Open 3D Editor"), KEY_F2); - ED_SHORTCUT("editor/editor_script", TTR("Open Script Editor"), KEY_F3); //hack needed for script editor F3 search to work :) Assign like this or don't use F3 - ED_SHORTCUT("editor/editor_help", TTR("Search Help"), KEY_MASK_SHIFT | KEY_F1); + // Use the Ctrl modifier so F2 can be used to rename nodes in the scene tree dock. + ED_SHORTCUT("editor/editor_2d", TTR("Open 2D Editor"), KEY_MASK_CTRL | KEY_F1); + ED_SHORTCUT("editor/editor_3d", TTR("Open 3D Editor"), KEY_MASK_CTRL | KEY_F2); + ED_SHORTCUT("editor/editor_script", TTR("Open Script Editor"), KEY_MASK_CTRL | KEY_F3); + ED_SHORTCUT("editor/editor_assetlib", TTR("Open Asset Library"), KEY_MASK_CTRL | KEY_F4); + ED_SHORTCUT("editor/editor_help", TTR("Search Help"), KEY_F1); #endif - ED_SHORTCUT("editor/editor_assetlib", TTR("Open Asset Library")); ED_SHORTCUT("editor/editor_next", TTR("Open the next Editor")); ED_SHORTCUT("editor/editor_prev", TTR("Open the previous Editor")); @@ -6636,6 +6968,7 @@ EditorNode::EditorNode() { EditorNode::~EditorNode() { EditorInspector::cleanup_plugins(); + EditorTranslationParser::get_singleton()->clean_parsers(); remove_print_handler(&print_handler); memdelete(EditorHelp::get_doc_data()); @@ -6724,8 +7057,8 @@ void EditorPluginList::remove_plugin(EditorPlugin *p_plugin) { plugins_list.erase(p_plugin); } -bool EditorPluginList::empty() { - return plugins_list.empty(); +bool EditorPluginList::is_empty() { + return plugins_list.is_empty(); } void EditorPluginList::clear() { |