6824 lines
222 KiB
C++
6824 lines
222 KiB
C++
/*************************************************************************/
|
|
/* editor_node.cpp */
|
|
/*************************************************************************/
|
|
/* This file is part of: */
|
|
/* GODOT ENGINE */
|
|
/* https://godotengine.org */
|
|
/*************************************************************************/
|
|
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
|
|
/* Copyright (c) 2014-2020 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 */
|
|
/* "Software"), to deal in the Software without restriction, including */
|
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
/* the following conditions: */
|
|
/* */
|
|
/* The above copyright notice and this permission notice shall be */
|
|
/* included in all copies or substantial portions of the Software. */
|
|
/* */
|
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
/*************************************************************************/
|
|
|
|
#include "editor_node.h"
|
|
|
|
#include "core/bind/core_bind.h"
|
|
#include "core/class_db.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/os/file_access.h"
|
|
#include "core/os/input.h"
|
|
#include "core/os/keyboard.h"
|
|
#include "core/os/os.h"
|
|
#include "core/path_remap.h"
|
|
#include "core/print_string.h"
|
|
#include "core/project_settings.h"
|
|
#include "core/translation.h"
|
|
#include "core/version.h"
|
|
#include "main/input_default.h"
|
|
#include "main/main.h"
|
|
#include "scene/gui/center_container.h"
|
|
#include "scene/gui/control.h"
|
|
#include "scene/gui/dialogs.h"
|
|
#include "scene/gui/file_dialog.h"
|
|
#include "scene/gui/menu_button.h"
|
|
#include "scene/gui/panel.h"
|
|
#include "scene/gui/panel_container.h"
|
|
#include "scene/gui/split_container.h"
|
|
#include "scene/gui/tab_container.h"
|
|
#include "scene/gui/tabs.h"
|
|
#include "scene/gui/texture_progress.h"
|
|
#include "scene/gui/tool_button.h"
|
|
#include "scene/resources/packed_scene.h"
|
|
#include "servers/navigation_2d_server.h"
|
|
#include "servers/navigation_server.h"
|
|
#include "servers/physics_2d_server.h"
|
|
|
|
#include "editor/audio_stream_preview.h"
|
|
#include "editor/debugger/editor_debugger_node.h"
|
|
#include "editor/dependency_editor.h"
|
|
#include "editor/editor_about.h"
|
|
#include "editor/editor_audio_buses.h"
|
|
#include "editor/editor_export.h"
|
|
#include "editor/editor_feature_profile.h"
|
|
#include "editor/editor_file_system.h"
|
|
#include "editor/editor_help.h"
|
|
#include "editor/editor_inspector.h"
|
|
#include "editor/editor_layouts_dialog.h"
|
|
#include "editor/editor_log.h"
|
|
#include "editor/editor_plugin.h"
|
|
#include "editor/editor_properties.h"
|
|
#include "editor/editor_resource_preview.h"
|
|
#include "editor/editor_run_native.h"
|
|
#include "editor/editor_run_script.h"
|
|
#include "editor/editor_scale.h"
|
|
#include "editor/editor_settings.h"
|
|
#include "editor/editor_spin_slider.h"
|
|
#include "editor/editor_themes.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"
|
|
#include "editor/import/resource_importer_image.h"
|
|
#include "editor/import/resource_importer_layered_texture.h"
|
|
#include "editor/import/resource_importer_obj.h"
|
|
#include "editor/import/resource_importer_scene.h"
|
|
#include "editor/import/resource_importer_texture.h"
|
|
#include "editor/import/resource_importer_texture_atlas.h"
|
|
#include "editor/import/resource_importer_wav.h"
|
|
#include "editor/import_dock.h"
|
|
#include "editor/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"
|
|
#include "editor/plugins/animation_blend_tree_editor_plugin.h"
|
|
#include "editor/plugins/animation_player_editor_plugin.h"
|
|
#include "editor/plugins/animation_state_machine_editor.h"
|
|
#include "editor/plugins/animation_tree_editor_plugin.h"
|
|
#include "editor/plugins/asset_library_editor_plugin.h"
|
|
#include "editor/plugins/audio_stream_editor_plugin.h"
|
|
#include "editor/plugins/baked_lightmap_editor_plugin.h"
|
|
#include "editor/plugins/camera_editor_plugin.h"
|
|
#include "editor/plugins/canvas_item_editor_plugin.h"
|
|
#include "editor/plugins/collision_polygon_2d_editor_plugin.h"
|
|
#include "editor/plugins/collision_polygon_editor_plugin.h"
|
|
#include "editor/plugins/collision_shape_2d_editor_plugin.h"
|
|
#include "editor/plugins/cpu_particles_2d_editor_plugin.h"
|
|
#include "editor/plugins/cpu_particles_editor_plugin.h"
|
|
#include "editor/plugins/curve_editor_plugin.h"
|
|
#include "editor/plugins/debugger_editor_plugin.h"
|
|
#include "editor/plugins/editor_preview_plugins.h"
|
|
#include "editor/plugins/gi_probe_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"
|
|
#include "editor/plugins/line_2d_editor_plugin.h"
|
|
#include "editor/plugins/material_editor_plugin.h"
|
|
#include "editor/plugins/mesh_editor_plugin.h"
|
|
#include "editor/plugins/mesh_instance_editor_plugin.h"
|
|
#include "editor/plugins/mesh_library_editor_plugin.h"
|
|
#include "editor/plugins/multimesh_editor_plugin.h"
|
|
#include "editor/plugins/navigation_polygon_editor_plugin.h"
|
|
#include "editor/plugins/particles_2d_editor_plugin.h"
|
|
#include "editor/plugins/particles_editor_plugin.h"
|
|
#include "editor/plugins/path_2d_editor_plugin.h"
|
|
#include "editor/plugins/path_editor_plugin.h"
|
|
#include "editor/plugins/physical_bone_plugin.h"
|
|
#include "editor/plugins/polygon_2d_editor_plugin.h"
|
|
#include "editor/plugins/resource_preloader_editor_plugin.h"
|
|
#include "editor/plugins/root_motion_editor_plugin.h"
|
|
#include "editor/plugins/script_editor_plugin.h"
|
|
#include "editor/plugins/script_text_editor.h"
|
|
#include "editor/plugins/shader_editor_plugin.h"
|
|
#include "editor/plugins/skeleton_2d_editor_plugin.h"
|
|
#include "editor/plugins/skeleton_editor_plugin.h"
|
|
#include "editor/plugins/skeleton_ik_editor_plugin.h"
|
|
#include "editor/plugins/spatial_editor_plugin.h"
|
|
#include "editor/plugins/sprite_editor_plugin.h"
|
|
#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_editor_plugin.h"
|
|
#include "editor/plugins/texture_region_editor_plugin.h"
|
|
#include "editor/plugins/theme_editor_plugin.h"
|
|
#include "editor/plugins/tile_map_editor_plugin.h"
|
|
#include "editor/plugins/tile_set_editor_plugin.h"
|
|
#include "editor/plugins/version_control_editor_plugin.h"
|
|
#include "editor/plugins/visual_shader_editor_plugin.h"
|
|
#include "editor/progress_dialog.h"
|
|
#include "editor/project_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 <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
EditorNode *EditorNode::singleton = NULL;
|
|
|
|
void EditorNode::_update_scene_tabs() {
|
|
|
|
bool show_rb = EditorSettings::get_singleton()->get("interface/scene_tabs/show_script_button");
|
|
|
|
OS::get_singleton()->global_menu_clear("_dock");
|
|
|
|
scene_tabs->clear_tabs();
|
|
Ref<Texture2D> script_icon = gui_base->get_icon("Script", "EditorIcons");
|
|
for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
|
|
|
|
Node *type_node = editor_data.get_edited_scene_root(i);
|
|
Ref<Texture2D> icon;
|
|
if (type_node) {
|
|
icon = EditorNode::get_singleton()->get_object_icon(type_node, "Node");
|
|
}
|
|
|
|
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);
|
|
|
|
OS::get_singleton()->global_menu_add_item("_dock", editor_data.get_scene_title(i) + (unsaved ? "(*)" : ""), GLOBAL_SCENE, i);
|
|
|
|
if (show_rb && editor_data.get_scene_root_script(i).is_valid()) {
|
|
scene_tabs->set_tab_right_button(i, script_icon);
|
|
}
|
|
}
|
|
|
|
OS::get_singleton()->global_menu_add_separator("_dock");
|
|
OS::get_singleton()->global_menu_add_item("_dock", TTR("New Window"), GLOBAL_NEW_WINDOW, Variant());
|
|
|
|
scene_tabs->set_current_tab(editor_data.get_edited_scene());
|
|
|
|
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));
|
|
scene_tabs->remove_child(scene_tab_add);
|
|
tabbar_container->add_child(scene_tab_add);
|
|
tabbar_container->move_child(scene_tab_add, 1);
|
|
}
|
|
} else {
|
|
// move add button to after last tab
|
|
if (scene_tab_add->get_parent() == tabbar_container) {
|
|
tabbar_container->remove_child(scene_tab_add);
|
|
scene_tabs->add_child(scene_tab_add);
|
|
}
|
|
Rect2 last_tab = Rect2();
|
|
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));
|
|
}
|
|
}
|
|
|
|
void EditorNode::_version_control_menu_option(int p_idx) {
|
|
|
|
switch (vcs_actions_menu->get_item_id(p_idx)) {
|
|
case RUN_VCS_SETTINGS: {
|
|
|
|
VersionControlEditorPlugin::get_singleton()->popup_vcs_set_up_dialog(gui_base);
|
|
} break;
|
|
case RUN_VCS_SHUT_DOWN: {
|
|
|
|
VersionControlEditorPlugin::get_singleton()->shut_down();
|
|
} break;
|
|
}
|
|
}
|
|
|
|
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 edited = editor_data.get_edited_scene_root() ? editor_data.get_edited_scene_root()->get_filename() : String();
|
|
if (!edited.empty())
|
|
title += " - " + String(edited.get_file());
|
|
if (unsaved_cache)
|
|
title += " (*)";
|
|
|
|
OS::get_singleton()->set_window_title(title);
|
|
}
|
|
|
|
void EditorNode::_unhandled_input(const Ref<InputEvent> &p_event) {
|
|
|
|
if (Node::get_viewport()->get_modal_stack_top())
|
|
return; //ignore because of modal window
|
|
|
|
Ref<InputEventKey> k = p_event;
|
|
if (k.is_valid() && k->is_pressed() && !k->is_echo() && !gui_base->get_viewport()->gui_has_modal_stack()) {
|
|
|
|
EditorPlugin *old_editor = editor_plugin_screen;
|
|
|
|
if (ED_IS_SHORTCUT("editor/next_tab", p_event)) {
|
|
int next_tab = editor_data.get_edited_scene() + 1;
|
|
next_tab %= editor_data.get_edited_scene_count();
|
|
_scene_tab_changed(next_tab);
|
|
}
|
|
if (ED_IS_SHORTCUT("editor/prev_tab", p_event)) {
|
|
int next_tab = editor_data.get_edited_scene() - 1;
|
|
next_tab = next_tab >= 0 ? next_tab : editor_data.get_edited_scene_count() - 1;
|
|
_scene_tab_changed(next_tab);
|
|
}
|
|
if (ED_IS_SHORTCUT("editor/filter_files", p_event)) {
|
|
filesystem_dock->focus_on_filter();
|
|
}
|
|
|
|
if (ED_IS_SHORTCUT("editor/editor_2d", p_event)) {
|
|
_editor_select(EDITOR_2D);
|
|
} else if (ED_IS_SHORTCUT("editor/editor_3d", p_event)) {
|
|
_editor_select(EDITOR_3D);
|
|
} else if (ED_IS_SHORTCUT("editor/editor_script", p_event)) {
|
|
_editor_select(EDITOR_SCRIPT);
|
|
} else if (ED_IS_SHORTCUT("editor/editor_help", p_event)) {
|
|
emit_signal("request_help_search", "");
|
|
} else if (ED_IS_SHORTCUT("editor/editor_assetlib", p_event) && StreamPeerSSL::is_available()) {
|
|
_editor_select(EDITOR_ASSETLIB);
|
|
} else if (ED_IS_SHORTCUT("editor/editor_next", p_event)) {
|
|
_editor_select_next();
|
|
} else if (ED_IS_SHORTCUT("editor/editor_prev", p_event)) {
|
|
_editor_select_prev();
|
|
}
|
|
|
|
if (old_editor != editor_plugin_screen) {
|
|
get_tree()->set_input_as_handled();
|
|
}
|
|
}
|
|
}
|
|
|
|
void EditorNode::_notification(int p_what) {
|
|
|
|
switch (p_what) {
|
|
case NOTIFICATION_PROCESS: {
|
|
if (opening_prev && !confirmation->is_visible())
|
|
opening_prev = false;
|
|
|
|
if (unsaved_cache != (saved_version != editor_data.get_undo_redo().get_version())) {
|
|
|
|
unsaved_cache = (saved_version != editor_data.get_undo_redo().get_version());
|
|
_update_title();
|
|
}
|
|
|
|
if (last_checked_version != editor_data.get_undo_redo().get_version()) {
|
|
_update_scene_tabs();
|
|
last_checked_version = editor_data.get_undo_redo().get_version();
|
|
}
|
|
|
|
// update the animation frame of the update spinner
|
|
uint64_t frame = Engine::get_singleton()->get_frames_drawn();
|
|
uint32_t tick = OS::get_singleton()->get_ticks_msec();
|
|
|
|
if (frame != update_spinner_step_frame && (tick - update_spinner_step_msec) > (1000 / 8)) {
|
|
|
|
update_spinner_step++;
|
|
if (update_spinner_step >= 8)
|
|
update_spinner_step = 0;
|
|
|
|
update_spinner_step_msec = tick;
|
|
update_spinner_step_frame = frame + 1;
|
|
|
|
// update the icon itself only when the spinner is visible
|
|
if (EditorSettings::get_singleton()->get("interface/editor/show_update_spinner")) {
|
|
update_spinner->set_icon(gui_base->get_icon("Progress" + itos(update_spinner_step + 1), "EditorIcons"));
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
VS::DOFBokehShape dof_shape = VS::DOFBokehShape(int(GLOBAL_GET("rendering/quality/filters/depth_of_field_bokeh_shape")));
|
|
VS::get_singleton()->camera_effects_set_dof_blur_bokeh_shape(dof_shape);
|
|
VS::DOFBlurQuality dof_quality = VS::DOFBlurQuality(int(GLOBAL_GET("rendering/quality/filters/depth_of_field_bokeh_quality")));
|
|
bool dof_jitter = GLOBAL_GET("rendering/quality/filters/depth_of_field_use_jitter");
|
|
VS::get_singleton()->camera_effects_set_dof_blur_quality(dof_quality, dof_jitter);
|
|
VS::get_singleton()->environment_set_ssao_quality(VS::EnvironmentSSAOQuality(int(GLOBAL_GET("rendering/quality/ssao/quality"))), GLOBAL_GET("rendering/quality/ssao/half_size"));
|
|
VS::get_singleton()->screen_space_roughness_limiter_set_active(GLOBAL_GET("rendering/quality/filters/screen_space_roughness_limiter"), GLOBAL_GET("rendering/quality/filters/screen_space_roughness_limiter_curve"));
|
|
}
|
|
|
|
ResourceImporterTexture::get_singleton()->update_imports();
|
|
} break;
|
|
|
|
case NOTIFICATION_ENTER_TREE: {
|
|
Engine::get_singleton()->set_editor_hint(true);
|
|
|
|
OS::get_singleton()->set_low_processor_usage_mode_sleep_usec(int(EDITOR_GET("interface/editor/low_processor_mode_sleep_usec")));
|
|
get_tree()->get_root()->set_as_audio_listener(false);
|
|
get_tree()->get_root()->set_as_audio_listener_2d(false);
|
|
get_tree()->set_auto_accept_quit(false);
|
|
get_tree()->connect("files_dropped", callable_mp(this, &EditorNode::_dropped_files));
|
|
get_tree()->connect("global_menu_action", callable_mp(this, &EditorNode::_global_menu_action));
|
|
|
|
/* DO NOT LOAD SCENES HERE, WAIT FOR FILE SCANNING AND REIMPORT TO COMPLETE */
|
|
} break;
|
|
|
|
case NOTIFICATION_EXIT_TREE: {
|
|
editor_data.save_editor_external_data();
|
|
FileAccess::set_file_close_fail_notify_callback(NULL);
|
|
log->deinit(); // do not get messages anymore
|
|
editor_data.clear_edited_scenes();
|
|
} break;
|
|
|
|
case NOTIFICATION_READY: {
|
|
|
|
{
|
|
_initializing_addons = true;
|
|
Vector<String> addons;
|
|
if (ProjectSettings::get_singleton()->has_setting("editor_plugins/enabled")) {
|
|
addons = ProjectSettings::get_singleton()->get("editor_plugins/enabled");
|
|
}
|
|
|
|
for (int i = 0; i < addons.size(); i++) {
|
|
set_addon_plugin_enabled(addons[i], true);
|
|
}
|
|
_initializing_addons = false;
|
|
}
|
|
|
|
VisualServer::get_singleton()->viewport_set_hide_scenario(get_scene_root()->get_viewport_rid(), true);
|
|
VisualServer::get_singleton()->viewport_set_hide_canvas(get_scene_root()->get_viewport_rid(), true);
|
|
VisualServer::get_singleton()->viewport_set_disable_environment(get_viewport()->get_viewport_rid(), true);
|
|
|
|
feature_profile_manager->notify_changed();
|
|
|
|
if (!main_editor_buttons[EDITOR_3D]->is_visible()) { //may be hidden due to feature profile
|
|
_editor_select(EDITOR_2D);
|
|
} else {
|
|
_editor_select(EDITOR_3D);
|
|
}
|
|
|
|
/* DO NOT LOAD SCENES HERE, WAIT FOR FILE SCANNING AND REIMPORT TO COMPLETE */
|
|
} break;
|
|
|
|
case MainLoop::NOTIFICATION_WM_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();
|
|
} break;
|
|
|
|
case MainLoop::NOTIFICATION_WM_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;
|
|
|
|
case MainLoop::NOTIFICATION_WM_ABOUT: {
|
|
|
|
show_about();
|
|
} break;
|
|
|
|
case MainLoop::NOTIFICATION_WM_QUIT_REQUEST: {
|
|
|
|
_menu_option_confirm(FILE_QUIT, false);
|
|
} break;
|
|
|
|
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
|
|
scene_tabs->set_tab_close_display_policy((bool(EDITOR_GET("interface/scene_tabs/always_show_close_button")) ? Tabs::CLOSE_BUTTON_SHOW_ALWAYS : Tabs::CLOSE_BUTTON_SHOW_ACTIVE_ONLY));
|
|
theme = create_editor_theme(theme_base->get_theme());
|
|
|
|
theme_base->set_theme(theme);
|
|
gui_base->set_theme(theme);
|
|
|
|
gui_base->add_style_override("panel", gui_base->get_stylebox("Background", "EditorStyles"));
|
|
scene_root_parent->add_style_override("panel", gui_base->get_stylebox("Content", "EditorStyles"));
|
|
bottom_panel->add_style_override("panel", gui_base->get_stylebox("panel", "TabContainer"));
|
|
scene_tabs->add_style_override("tab_fg", gui_base->get_stylebox("SceneTabFG", "EditorStyles"));
|
|
scene_tabs->add_style_override("tab_bg", gui_base->get_stylebox("SceneTabBG", "EditorStyles"));
|
|
|
|
file_menu->add_style_override("hover", gui_base->get_stylebox("MenuHover", "EditorStyles"));
|
|
project_menu->add_style_override("hover", gui_base->get_stylebox("MenuHover", "EditorStyles"));
|
|
debug_menu->add_style_override("hover", gui_base->get_stylebox("MenuHover", "EditorStyles"));
|
|
settings_menu->add_style_override("hover", gui_base->get_stylebox("MenuHover", "EditorStyles"));
|
|
help_menu->add_style_override("hover", gui_base->get_stylebox("MenuHover", "EditorStyles"));
|
|
|
|
if (EDITOR_GET("interface/scene_tabs/resize_if_many_tabs")) {
|
|
scene_tabs->set_min_width(int(EDITOR_GET("interface/scene_tabs/minimum_width")) * EDSCALE);
|
|
} else {
|
|
scene_tabs->set_min_width(0);
|
|
}
|
|
_update_scene_tabs();
|
|
|
|
recent_scenes->set_as_minsize();
|
|
|
|
// debugger area
|
|
if (EditorDebuggerNode::get_singleton()->is_visible())
|
|
bottom_panel->add_style_override("panel", gui_base->get_stylebox("BottomPanelDebuggerOverride", "EditorStyles"));
|
|
|
|
// update_icons
|
|
for (int i = 0; i < singleton->main_editor_buttons.size(); i++) {
|
|
|
|
ToolButton *tb = singleton->main_editor_buttons[i];
|
|
EditorPlugin *p_editor = singleton->editor_table[i];
|
|
Ref<Texture2D> icon = p_editor->get_icon();
|
|
|
|
if (icon.is_valid()) {
|
|
tb->set_icon(icon);
|
|
} else if (singleton->gui_base->has_icon(p_editor->get_name(), "EditorIcons")) {
|
|
tb->set_icon(singleton->gui_base->get_icon(p_editor->get_name(), "EditorIcons"));
|
|
}
|
|
}
|
|
|
|
_build_icon_type_cache();
|
|
|
|
play_button->set_icon(gui_base->get_icon("MainPlay", "EditorIcons"));
|
|
play_scene_button->set_icon(gui_base->get_icon("PlayScene", "EditorIcons"));
|
|
play_custom_scene_button->set_icon(gui_base->get_icon("PlayCustom", "EditorIcons"));
|
|
pause_button->set_icon(gui_base->get_icon("Pause", "EditorIcons"));
|
|
stop_button->set_icon(gui_base->get_icon("Stop", "EditorIcons"));
|
|
|
|
prev_scene->set_icon(gui_base->get_icon("PrevScene", "EditorIcons"));
|
|
distraction_free->set_icon(gui_base->get_icon("DistractionFree", "EditorIcons"));
|
|
scene_tab_add->set_icon(gui_base->get_icon("Add", "EditorIcons"));
|
|
|
|
bottom_panel_raise->set_icon(gui_base->get_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"));
|
|
|
|
PopupMenu *p = help_menu->get_popup();
|
|
p->set_item_icon(p->get_item_index(HELP_SEARCH), gui_base->get_icon("HelpSearch", "EditorIcons"));
|
|
p->set_item_icon(p->get_item_index(HELP_DOCS), gui_base->get_icon("Instance", "EditorIcons"));
|
|
p->set_item_icon(p->get_item_index(HELP_QA), gui_base->get_icon("Instance", "EditorIcons"));
|
|
p->set_item_icon(p->get_item_index(HELP_ISSUES), gui_base->get_icon("Instance", "EditorIcons"));
|
|
p->set_item_icon(p->get_item_index(HELP_COMMUNITY), gui_base->get_icon("Instance", "EditorIcons"));
|
|
p->set_item_icon(p->get_item_index(HELP_ABOUT), gui_base->get_icon("Godot", "EditorIcons"));
|
|
|
|
_update_update_spinner();
|
|
} break;
|
|
|
|
case Control::NOTIFICATION_RESIZED: {
|
|
_update_scene_tabs();
|
|
} break;
|
|
}
|
|
}
|
|
|
|
void EditorNode::_update_update_spinner() {
|
|
update_spinner->set_visible(EditorSettings::get_singleton()->get("interface/editor/show_update_spinner"));
|
|
|
|
bool update_continuously = EditorSettings::get_singleton()->get("interface/editor/update_continuously");
|
|
PopupMenu *update_popup = update_spinner->get_popup();
|
|
update_popup->set_item_checked(update_popup->get_item_index(SETTINGS_UPDATE_CONTINUOUSLY), update_continuously);
|
|
update_popup->set_item_checked(update_popup->get_item_index(SETTINGS_UPDATE_WHEN_CHANGED), !update_continuously);
|
|
|
|
OS::get_singleton()->set_low_processor_usage_mode(!update_continuously);
|
|
}
|
|
|
|
void EditorNode::_on_plugin_ready(Object *p_script, const String &p_activate_name) {
|
|
Ref<Script> script = Object::cast_to<Script>(p_script);
|
|
if (script.is_null())
|
|
return;
|
|
if (p_activate_name.length()) {
|
|
set_addon_plugin_enabled(p_activate_name, true);
|
|
}
|
|
project_settings->update_plugins();
|
|
project_settings->hide();
|
|
push_item(script.operator->());
|
|
}
|
|
|
|
void EditorNode::_resources_changed(const Vector<String> &p_resources) {
|
|
|
|
List<Ref<Resource>> changed;
|
|
|
|
int rc = p_resources.size();
|
|
for (int i = 0; i < rc; i++) {
|
|
|
|
Ref<Resource> res(ResourceCache::get(p_resources.get(i)));
|
|
if (res.is_null()) {
|
|
continue;
|
|
}
|
|
|
|
if (!res->editor_can_reload_from_file())
|
|
continue;
|
|
if (!res->get_path().is_resource_file() && !res->get_path().is_abs_path())
|
|
continue;
|
|
if (!FileAccess::exists(res->get_path()))
|
|
continue;
|
|
|
|
if (res->get_import_path() != String()) {
|
|
//this is an imported resource, will be reloaded if reimported via the _resources_reimported() callback
|
|
continue;
|
|
}
|
|
|
|
changed.push_back(res);
|
|
}
|
|
|
|
if (changed.size()) {
|
|
for (List<Ref<Resource>>::Element *E = changed.front(); E; E = E->next()) {
|
|
E->get()->reload_from_file();
|
|
}
|
|
}
|
|
}
|
|
|
|
void EditorNode::_fs_changed() {
|
|
|
|
for (Set<FileDialog *>::Element *E = file_dialogs.front(); E; E = E->next()) {
|
|
|
|
E->get()->invalidate();
|
|
}
|
|
|
|
for (Set<EditorFileDialog *>::Element *E = editor_file_dialogs.front(); E; E = E->next()) {
|
|
|
|
E->get()->invalidate();
|
|
}
|
|
|
|
_mark_unsaved_scenes();
|
|
|
|
// FIXME: Move this to a cleaner location, it's hacky to do this is _fs_changed.
|
|
String export_error;
|
|
if (export_defer.preset != "" && !EditorFileSystem::get_singleton()->is_scanning()) {
|
|
String preset_name = export_defer.preset;
|
|
// Ensures export_project does not loop infinitely, because notifications may
|
|
// come during the export.
|
|
export_defer.preset = "";
|
|
Ref<EditorExportPreset> preset;
|
|
for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); ++i) {
|
|
preset = EditorExport::get_singleton()->get_export_preset(i);
|
|
if (preset->get_name() == preset_name) {
|
|
break;
|
|
}
|
|
preset.unref();
|
|
}
|
|
if (preset.is_null()) {
|
|
export_error = vformat("Invalid export preset name: %s.", preset_name);
|
|
} else {
|
|
Ref<EditorExportPlatform> platform = preset->get_platform();
|
|
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);
|
|
}
|
|
} else { // Normal project export.
|
|
String config_error;
|
|
bool missing_templates;
|
|
if (!platform->can_export(preset, config_error, missing_templates)) {
|
|
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);
|
|
}
|
|
}
|
|
switch (err) {
|
|
case OK:
|
|
break;
|
|
case ERR_FILE_NOT_FOUND:
|
|
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);
|
|
break;
|
|
default:
|
|
export_error = vformat("Project export failed with error code %d for preset '%s'.", (int)err, preset_name);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!export_error.empty()) {
|
|
ERR_PRINT(export_error);
|
|
OS::get_singleton()->set_exit_code(EXIT_FAILURE);
|
|
}
|
|
_exit_editor();
|
|
}
|
|
}
|
|
|
|
void EditorNode::_resources_reimported(const Vector<String> &p_resources) {
|
|
|
|
List<String> scenes; //will load later
|
|
int current_tab = scene_tabs->get_current_tab();
|
|
|
|
for (int i = 0; i < p_resources.size(); i++) {
|
|
String file_type = ResourceLoader::get_resource_type(p_resources[i]);
|
|
if (file_type == "PackedScene") {
|
|
scenes.push_back(p_resources[i]);
|
|
//reload later if needed, first go with normal resources
|
|
continue;
|
|
}
|
|
|
|
if (!ResourceCache::has(p_resources[i])) {
|
|
continue; //not loaded, no need to reload
|
|
}
|
|
//reload normally
|
|
Resource *resource = ResourceCache::get(p_resources[i]);
|
|
if (resource) {
|
|
resource->reload_from_file();
|
|
}
|
|
}
|
|
|
|
for (List<String>::Element *E = scenes.front(); E; E = E->next()) {
|
|
reload_scene(E->get());
|
|
}
|
|
|
|
scene_tabs->set_current_tab(current_tab);
|
|
}
|
|
|
|
void EditorNode::_sources_changed(bool p_exist) {
|
|
|
|
if (waiting_for_first_scan) {
|
|
waiting_for_first_scan = false;
|
|
|
|
// Start preview thread now that it's safe.
|
|
if (!singleton->cmdline_export_mode) {
|
|
EditorResourcePreview::get_singleton()->start();
|
|
}
|
|
|
|
_load_docks();
|
|
|
|
if (defer_load_scene != "") {
|
|
load_scene(defer_load_scene);
|
|
defer_load_scene = "";
|
|
}
|
|
}
|
|
}
|
|
|
|
void EditorNode::_vp_resized() {
|
|
}
|
|
|
|
void EditorNode::_node_renamed() {
|
|
|
|
if (get_inspector())
|
|
get_inspector()->update_tree();
|
|
}
|
|
|
|
void EditorNode::_editor_select_next() {
|
|
|
|
int editor = _get_current_main_editor();
|
|
|
|
do {
|
|
if (editor == editor_table.size() - 1) {
|
|
editor = 0;
|
|
} else {
|
|
editor++;
|
|
}
|
|
} while (!main_editor_buttons[editor]->is_visible());
|
|
|
|
_editor_select(editor);
|
|
}
|
|
|
|
void EditorNode::_editor_select_prev() {
|
|
|
|
int editor = _get_current_main_editor();
|
|
|
|
do {
|
|
if (editor == 0) {
|
|
editor = editor_table.size() - 1;
|
|
} else {
|
|
editor--;
|
|
}
|
|
} while (!main_editor_buttons[editor]->is_visible());
|
|
|
|
_editor_select(editor);
|
|
}
|
|
|
|
Error EditorNode::load_resource(const String &p_resource, bool p_ignore_broken_deps) {
|
|
|
|
dependency_errors.clear();
|
|
|
|
Error err;
|
|
RES res = ResourceLoader::load(p_resource, "", false, &err);
|
|
ERR_FAIL_COND_V(!res.is_valid(), ERR_CANT_OPEN);
|
|
|
|
if (!p_ignore_broken_deps && dependency_errors.has(p_resource)) {
|
|
|
|
//current_option = -1;
|
|
Vector<String> errors;
|
|
for (Set<String>::Element *E = dependency_errors[p_resource].front(); E; E = E->next()) {
|
|
|
|
errors.push_back(E->get());
|
|
}
|
|
dependency_error->show(DependencyErrorDialog::MODE_RESOURCE, p_resource, errors);
|
|
dependency_errors.erase(p_resource);
|
|
|
|
return ERR_FILE_MISSING_DEPENDENCIES;
|
|
}
|
|
|
|
inspector_dock->edit_resource(res);
|
|
return OK;
|
|
}
|
|
|
|
void EditorNode::edit_node(Node *p_node) {
|
|
|
|
push_item(p_node);
|
|
}
|
|
|
|
void EditorNode::save_resource_in_path(const Ref<Resource> &p_resource, const String &p_path) {
|
|
|
|
editor_data.apply_changes_in_editors();
|
|
int flg = 0;
|
|
if (EditorSettings::get_singleton()->get("filesystem/on_save/compress_binary_resources"))
|
|
flg |= ResourceSaver::FLAG_COMPRESS;
|
|
|
|
String path = ProjectSettings::get_singleton()->localize_path(p_path);
|
|
Error err = ResourceSaver::save(path, p_resource, flg | ResourceSaver::FLAG_REPLACE_SUBRESOURCE_PATHS);
|
|
|
|
if (err != OK) {
|
|
if (ResourceLoader::is_imported(p_resource->get_path())) {
|
|
show_accept(TTR("Imported resources can't be saved."), TTR("OK"));
|
|
} else {
|
|
show_accept(TTR("Error saving resource!"), TTR("OK"));
|
|
}
|
|
return;
|
|
}
|
|
|
|
((Resource *)p_resource.ptr())->set_path(path);
|
|
emit_signal("resource_saved", p_resource);
|
|
editor_data.notify_resource_saved(p_resource);
|
|
}
|
|
|
|
void EditorNode::save_resource(const Ref<Resource> &p_resource) {
|
|
|
|
if (p_resource->get_path().is_resource_file()) {
|
|
save_resource_in_path(p_resource, p_resource->get_path());
|
|
} else {
|
|
save_resource_as(p_resource);
|
|
}
|
|
}
|
|
|
|
void EditorNode::save_resource_as(const Ref<Resource> &p_resource, const String &p_at_path) {
|
|
|
|
{
|
|
String path = p_resource->get_path();
|
|
int srpos = path.find("::");
|
|
if (srpos != -1) {
|
|
String base = path.substr(0, srpos);
|
|
if (!get_edited_scene() || get_edited_scene()->get_filename() != base) {
|
|
show_warning(TTR("This resource can't be saved because it does not belong to the edited scene. Make it unique first."));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
file->set_mode(EditorFileDialog::MODE_SAVE_FILE);
|
|
saving_resource = p_resource;
|
|
|
|
current_option = RESOURCE_SAVE_AS;
|
|
List<String> extensions;
|
|
Ref<PackedScene> sd = memnew(PackedScene);
|
|
ResourceSaver::get_recognized_extensions(p_resource, &extensions);
|
|
file->clear_filters();
|
|
|
|
List<String> preferred;
|
|
for (int i = 0; i < extensions.size(); i++) {
|
|
|
|
if (p_resource->is_class("Script") && (extensions[i] == "tres" || extensions[i] == "res" || extensions[i] == "xml")) {
|
|
//this serves no purpose and confused people
|
|
continue;
|
|
}
|
|
file->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper());
|
|
preferred.push_back(extensions[i]);
|
|
}
|
|
|
|
if (p_at_path != String()) {
|
|
|
|
file->set_current_dir(p_at_path);
|
|
if (p_resource->get_path().is_resource_file()) {
|
|
file->set_current_file(p_resource->get_path().get_file());
|
|
} else {
|
|
if (extensions.size()) {
|
|
file->set_current_file("new_" + p_resource->get_class().to_lower() + "." + preferred.front()->get().to_lower());
|
|
} else {
|
|
file->set_current_file(String());
|
|
}
|
|
}
|
|
} else if (p_resource->get_path() != "") {
|
|
|
|
file->set_current_path(p_resource->get_path());
|
|
if (extensions.size()) {
|
|
String ext = p_resource->get_path().get_extension().to_lower();
|
|
if (extensions.find(ext) == NULL) {
|
|
file->set_current_path(p_resource->get_path().replacen("." + ext, "." + extensions.front()->get()));
|
|
}
|
|
}
|
|
} else if (preferred.size()) {
|
|
|
|
String existing;
|
|
if (extensions.size()) {
|
|
existing = "new_" + p_resource->get_class().to_lower() + "." + preferred.front()->get().to_lower();
|
|
}
|
|
file->set_current_path(existing);
|
|
}
|
|
file->popup_centered_ratio();
|
|
file->set_title(TTR("Save Resource As..."));
|
|
}
|
|
|
|
void EditorNode::_menu_option(int p_option) {
|
|
|
|
_menu_option_confirm(p_option, false);
|
|
}
|
|
|
|
void EditorNode::_menu_confirm_current() {
|
|
|
|
_menu_option_confirm(current_option, true);
|
|
}
|
|
|
|
void EditorNode::_dialog_display_save_error(String p_file, Error p_error) {
|
|
|
|
if (p_error) {
|
|
|
|
switch (p_error) {
|
|
|
|
case ERR_FILE_CANT_WRITE: {
|
|
|
|
show_accept(TTR("Can't open file for writing:") + " " + p_file.get_extension(), TTR("OK"));
|
|
} break;
|
|
case ERR_FILE_UNRECOGNIZED: {
|
|
|
|
show_accept(TTR("Requested file format unknown:") + " " + p_file.get_extension(), TTR("OK"));
|
|
} break;
|
|
default: {
|
|
|
|
show_accept(TTR("Error while saving."), TTR("OK"));
|
|
} break;
|
|
}
|
|
}
|
|
}
|
|
|
|
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"));
|
|
} break;
|
|
case ERR_PARSE_ERROR: {
|
|
|
|
show_accept(vformat(TTR("Error while parsing '%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"));
|
|
} break;
|
|
case ERR_FILE_NOT_FOUND: {
|
|
|
|
show_accept(vformat(TTR("Missing '%s' or its dependencies."), p_file.get_file()), TTR("OK"));
|
|
} break;
|
|
default: {
|
|
|
|
show_accept(vformat(TTR("Error while loading '%s'."), p_file.get_file()), TTR("OK"));
|
|
} break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void EditorNode::_get_scene_metadata(const String &p_file) {
|
|
|
|
Node *scene = editor_data.get_edited_scene_root();
|
|
|
|
if (!scene)
|
|
return;
|
|
|
|
String path = EditorSettings::get_singleton()->get_project_settings_dir().plus_file(p_file.get_file() + "-editstate-" + p_file.md5_text() + ".cfg");
|
|
|
|
Ref<ConfigFile> cf;
|
|
cf.instance();
|
|
|
|
Error err = cf->load(path);
|
|
if (err != OK || !cf->has_section("editor_states"))
|
|
return; //must not exist
|
|
|
|
List<String> esl;
|
|
cf->get_section_keys("editor_states", &esl);
|
|
|
|
Dictionary md;
|
|
for (List<String>::Element *E = esl.front(); E; E = E->next()) {
|
|
|
|
Variant st = cf->get_value("editor_states", E->get());
|
|
if (st.get_type() != Variant::NIL) {
|
|
md[E->get()] = st;
|
|
}
|
|
}
|
|
|
|
editor_data.set_editor_states(md);
|
|
}
|
|
|
|
void EditorNode::_set_scene_metadata(const String &p_file, int p_idx) {
|
|
|
|
Node *scene = editor_data.get_edited_scene_root(p_idx);
|
|
|
|
if (!scene)
|
|
return;
|
|
|
|
scene->set_meta("__editor_run_settings__", Variant()); //clear it (no point in keeping it)
|
|
scene->set_meta("__editor_plugin_states__", Variant());
|
|
|
|
String path = EditorSettings::get_singleton()->get_project_settings_dir().plus_file(p_file.get_file() + "-editstate-" + p_file.md5_text() + ".cfg");
|
|
|
|
Ref<ConfigFile> cf;
|
|
cf.instance();
|
|
|
|
Dictionary md;
|
|
|
|
if (p_idx < 0 || editor_data.get_edited_scene() == p_idx) {
|
|
md = editor_data.get_editor_states();
|
|
} else {
|
|
md = editor_data.get_scene_editor_states(p_idx);
|
|
}
|
|
|
|
List<Variant> keys;
|
|
md.get_key_list(&keys);
|
|
|
|
for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
|
|
|
|
cf->set_value("editor_states", E->get(), md[E->get()]);
|
|
}
|
|
|
|
Error err = cf->save(path);
|
|
ERR_FAIL_COND_MSG(err != OK, "Cannot save config file to '" + path + "'.");
|
|
}
|
|
|
|
bool EditorNode::_find_and_save_resource(RES p_res, Map<RES, bool> &processed, int32_t flags) {
|
|
|
|
if (p_res.is_null())
|
|
return false;
|
|
|
|
if (processed.has(p_res)) {
|
|
|
|
return processed[p_res];
|
|
}
|
|
|
|
bool changed = p_res->is_edited();
|
|
p_res->set_edited(false);
|
|
|
|
bool subchanged = _find_and_save_edited_subresources(p_res.ptr(), processed, flags);
|
|
|
|
if (p_res->get_path().is_resource_file()) {
|
|
if (changed || subchanged) {
|
|
//save
|
|
ResourceSaver::save(p_res->get_path(), p_res, flags);
|
|
}
|
|
processed[p_res] = false; //because it's a file
|
|
return false;
|
|
} else {
|
|
|
|
processed[p_res] = changed;
|
|
return changed;
|
|
}
|
|
}
|
|
|
|
bool EditorNode::_find_and_save_edited_subresources(Object *obj, Map<RES, bool> &processed, int32_t flags) {
|
|
|
|
bool ret_changed = false;
|
|
List<PropertyInfo> pi;
|
|
obj->get_property_list(&pi);
|
|
for (List<PropertyInfo>::Element *E = pi.front(); E; E = E->next()) {
|
|
|
|
if (!(E->get().usage & PROPERTY_USAGE_STORAGE))
|
|
continue;
|
|
|
|
switch (E->get().type) {
|
|
case Variant::OBJECT: {
|
|
|
|
RES res = obj->get(E->get().name);
|
|
|
|
if (_find_and_save_resource(res, processed, flags))
|
|
ret_changed = true;
|
|
|
|
} break;
|
|
case Variant::ARRAY: {
|
|
|
|
Array varray = obj->get(E->get().name);
|
|
int len = varray.size();
|
|
for (int i = 0; i < len; i++) {
|
|
|
|
const Variant &v = varray.get(i);
|
|
RES res = v;
|
|
if (_find_and_save_resource(res, processed, flags))
|
|
ret_changed = true;
|
|
}
|
|
|
|
} break;
|
|
case Variant::DICTIONARY: {
|
|
|
|
Dictionary d = obj->get(E->get().name);
|
|
List<Variant> keys;
|
|
d.get_key_list(&keys);
|
|
for (List<Variant>::Element *F = keys.front(); F; F = F->next()) {
|
|
|
|
Variant v = d[F->get()];
|
|
RES res = v;
|
|
if (_find_and_save_resource(res, processed, flags))
|
|
ret_changed = true;
|
|
}
|
|
} break;
|
|
default: {
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret_changed;
|
|
}
|
|
|
|
void EditorNode::_save_edited_subresources(Node *scene, Map<RES, bool> &processed, int32_t flags) {
|
|
|
|
_find_and_save_edited_subresources(scene, processed, flags);
|
|
|
|
for (int i = 0; i < scene->get_child_count(); i++) {
|
|
|
|
Node *n = scene->get_child(i);
|
|
if (n->get_owner() != editor_data.get_edited_scene_root())
|
|
continue;
|
|
_save_edited_subresources(n, processed, flags);
|
|
}
|
|
}
|
|
|
|
void EditorNode::_find_node_types(Node *p_node, int &count_2d, int &count_3d) {
|
|
|
|
if (p_node->is_class("Viewport") || (p_node != editor_data.get_edited_scene_root() && p_node->get_owner() != editor_data.get_edited_scene_root()))
|
|
return;
|
|
|
|
if (p_node->is_class("CanvasItem"))
|
|
count_2d++;
|
|
else if (p_node->is_class("Spatial"))
|
|
count_3d++;
|
|
|
|
for (int i = 0; i < p_node->get_child_count(); i++)
|
|
_find_node_types(p_node->get_child(i), count_2d, count_3d);
|
|
}
|
|
|
|
void EditorNode::_save_scene_with_preview(String p_file, int p_idx) {
|
|
|
|
EditorProgress save("save", TTR("Saving Scene"), 4);
|
|
|
|
if (editor_data.get_edited_scene_root() != NULL) {
|
|
save.step(TTR("Analyzing"), 0);
|
|
|
|
int c2d = 0;
|
|
int c3d = 0;
|
|
|
|
_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();
|
|
} else {
|
|
img = SpatialEditor::get_singleton()->get_editor_viewport(0)->get_viewport_node()->get_texture()->get_data();
|
|
}
|
|
|
|
if (img.is_valid()) {
|
|
|
|
img = img->duplicate();
|
|
|
|
save.step(TTR("Creating Thumbnail"), 2);
|
|
save.step(TTR("Creating Thumbnail"), 3);
|
|
|
|
int preview_size = EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size");
|
|
preview_size *= EDSCALE;
|
|
|
|
// consider a square region
|
|
int vp_size = MIN(img->get_width(), img->get_height());
|
|
int x = (img->get_width() - vp_size) / 2;
|
|
int y = (img->get_height() - vp_size) / 2;
|
|
|
|
if (vp_size < preview_size) {
|
|
// just square it.
|
|
img->crop_from_point(x, y, vp_size, vp_size);
|
|
} else {
|
|
int ratio = vp_size / preview_size;
|
|
int size = preview_size * MAX(1, ratio / 2);
|
|
|
|
x = (img->get_width() - size) / 2;
|
|
y = (img->get_height() - size) / 2;
|
|
|
|
img->crop_from_point(x, y, size, size);
|
|
img->resize(preview_size, preview_size, Image::INTERPOLATE_LANCZOS);
|
|
}
|
|
img->convert(Image::FORMAT_RGB8);
|
|
|
|
img->flip_y();
|
|
|
|
//save thumbnail directly, as thumbnailer may not update due to actual scene not changing md5
|
|
String temp_path = EditorSettings::get_singleton()->get_cache_dir();
|
|
String cache_base = ProjectSettings::get_singleton()->globalize_path(p_file).md5_text();
|
|
cache_base = temp_path.plus_file("resthumb-" + cache_base);
|
|
|
|
//does not have it, try to load a cached thumbnail
|
|
|
|
String file = cache_base + ".png";
|
|
|
|
post_process_preview(img);
|
|
img->save_png(file);
|
|
}
|
|
}
|
|
|
|
save.step(TTR("Saving Scene"), 4);
|
|
_save_scene(p_file, p_idx);
|
|
|
|
if (!singleton->cmdline_export_mode) {
|
|
EditorResourcePreview::get_singleton()->check_for_invalidation(p_file);
|
|
}
|
|
}
|
|
|
|
bool EditorNode::_validate_scene_recursive(const String &p_filename, Node *p_node) {
|
|
|
|
for (int i = 0; i < p_node->get_child_count(); i++) {
|
|
Node *child = p_node->get_child(i);
|
|
if (child->get_filename() == p_filename) {
|
|
return true;
|
|
}
|
|
|
|
if (_validate_scene_recursive(p_filename, child)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool _find_edited_resources(const Ref<Resource> &p_resource, Set<Ref<Resource>> &edited_resources) {
|
|
|
|
if (p_resource->is_edited()) {
|
|
edited_resources.insert(p_resource);
|
|
return true;
|
|
}
|
|
|
|
List<PropertyInfo> plist;
|
|
|
|
p_resource->get_property_list(&plist);
|
|
|
|
for (List<PropertyInfo>::Element *E = plist.front(); E; E = E->next()) {
|
|
if (E->get().type == Variant::OBJECT && E->get().usage & PROPERTY_USAGE_STORAGE && !(E->get().usage & PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT)) {
|
|
RES res = p_resource->get(E->get().name);
|
|
if (res.is_null()) {
|
|
continue;
|
|
}
|
|
if (res->get_path().is_resource_file()) { //not a subresource, continue
|
|
continue;
|
|
}
|
|
if (_find_edited_resources(res, edited_resources)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int EditorNode::_save_external_resources() {
|
|
//save external resources and its subresources if any was modified
|
|
|
|
int flg = 0;
|
|
if (EditorSettings::get_singleton()->get("filesystem/on_save/compress_binary_resources"))
|
|
flg |= ResourceSaver::FLAG_COMPRESS;
|
|
flg |= ResourceSaver::FLAG_REPLACE_SUBRESOURCE_PATHS;
|
|
|
|
Set<Ref<Resource>> edited_subresources;
|
|
int saved = 0;
|
|
List<Ref<Resource>> cached;
|
|
ResourceCache::get_cached_resources(&cached);
|
|
for (List<Ref<Resource>>::Element *E = cached.front(); E; E = E->next()) {
|
|
|
|
Ref<Resource> res = E->get();
|
|
if (!res->get_path().is_resource_file())
|
|
continue;
|
|
//not only check if this resourec is edited, check contained subresources too
|
|
if (_find_edited_resources(res, edited_subresources)) {
|
|
ResourceSaver::save(res->get_path(), res, flg);
|
|
saved++;
|
|
}
|
|
}
|
|
|
|
// clear later, because user may have put the same subresource in two different resources,
|
|
// which will be shared until the next reload
|
|
|
|
for (Set<Ref<Resource>>::Element *E = edited_subresources.front(); E; E = E->next()) {
|
|
Ref<Resource> res = E->get();
|
|
res->set_edited(false);
|
|
}
|
|
|
|
return saved;
|
|
}
|
|
|
|
void EditorNode::_save_scene(String p_file, int idx) {
|
|
|
|
Node *scene = editor_data.get_edited_scene_root(idx);
|
|
|
|
if (!scene) {
|
|
|
|
show_accept(TTR("This operation can't be done without a tree root."), TTR("OK"));
|
|
return;
|
|
}
|
|
|
|
if (scene->get_filename() != String() && _validate_scene_recursive(scene->get_filename(), scene)) {
|
|
show_accept(TTR("This scene can't be saved because there is a cyclic instancing inclusion.\nPlease resolve it and then attempt to save again."), TTR("OK"));
|
|
return;
|
|
}
|
|
|
|
editor_data.apply_changes_in_editors();
|
|
_save_default_environment();
|
|
|
|
_set_scene_metadata(p_file, idx);
|
|
|
|
Ref<PackedScene> sdata;
|
|
|
|
if (ResourceCache::has(p_file)) {
|
|
// something may be referencing this resource and we are good with that.
|
|
// we must update it, but also let the previous scene state go, as
|
|
// old version still work for referencing changes in instanced or inherited scenes
|
|
|
|
sdata = Ref<PackedScene>(Object::cast_to<PackedScene>(ResourceCache::get(p_file)));
|
|
if (sdata.is_valid())
|
|
sdata->recreate_state();
|
|
else
|
|
sdata.instance();
|
|
} else {
|
|
sdata.instance();
|
|
}
|
|
Error err = sdata->pack(scene);
|
|
|
|
if (err != OK) {
|
|
|
|
show_accept(TTR("Couldn't save scene. Likely dependencies (instances or inheritance) couldn't be satisfied."), TTR("OK"));
|
|
return;
|
|
}
|
|
|
|
// force creation of node path cache
|
|
// (hacky but needed for the tree to update properly)
|
|
Node *dummy_scene = sdata->instance(PackedScene::GEN_EDIT_STATE_INSTANCE);
|
|
if (!dummy_scene) {
|
|
show_accept(TTR("Couldn't save scene. Likely dependencies (instances or inheritance) couldn't be satisfied."), TTR("OK"));
|
|
return;
|
|
}
|
|
memdelete(dummy_scene);
|
|
|
|
int flg = 0;
|
|
if (EditorSettings::get_singleton()->get("filesystem/on_save/compress_binary_resources"))
|
|
flg |= ResourceSaver::FLAG_COMPRESS;
|
|
flg |= ResourceSaver::FLAG_REPLACE_SUBRESOURCE_PATHS;
|
|
|
|
err = ResourceSaver::save(p_file, sdata, flg);
|
|
|
|
_save_external_resources();
|
|
|
|
editor_data.save_editor_external_data();
|
|
if (err == OK) {
|
|
scene->set_filename(ProjectSettings::get_singleton()->localize_path(p_file));
|
|
if (idx < 0 || idx == editor_data.get_edited_scene())
|
|
set_current_version(editor_data.get_undo_redo().get_version());
|
|
else
|
|
editor_data.set_edited_scene_version(0, idx);
|
|
|
|
editor_folding.save_scene_folding(scene, p_file);
|
|
|
|
_update_title();
|
|
_update_scene_tabs();
|
|
} else {
|
|
|
|
_dialog_display_save_error(p_file, err);
|
|
}
|
|
}
|
|
|
|
void EditorNode::save_all_scenes() {
|
|
|
|
_menu_option_confirm(RUN_STOP, true);
|
|
_save_all_scenes();
|
|
}
|
|
|
|
void EditorNode::save_scene_list(Vector<String> p_scene_filenames) {
|
|
|
|
for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
|
|
Node *scene = editor_data.get_edited_scene_root(i);
|
|
|
|
if (scene && (p_scene_filenames.find(scene->get_filename()) >= 0)) {
|
|
_save_scene(scene->get_filename(), i);
|
|
}
|
|
}
|
|
}
|
|
|
|
void EditorNode::restart_editor() {
|
|
|
|
exiting = true;
|
|
|
|
String to_reopen;
|
|
if (get_tree()->get_edited_scene_root()) {
|
|
to_reopen = get_tree()->get_edited_scene_root()->get_filename();
|
|
}
|
|
|
|
_exit_editor();
|
|
|
|
List<String> args;
|
|
args.push_back("--path");
|
|
args.push_back(ProjectSettings::get_singleton()->get_resource_path());
|
|
args.push_back("-e");
|
|
if (to_reopen != String()) {
|
|
args.push_back(to_reopen);
|
|
}
|
|
|
|
OS::get_singleton()->set_restart_on_exit(true, args);
|
|
}
|
|
|
|
void EditorNode::_save_all_scenes() {
|
|
|
|
for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
|
|
Node *scene = editor_data.get_edited_scene_root(i);
|
|
if (scene && scene->get_filename() != "") {
|
|
if (i != editor_data.get_edited_scene())
|
|
_save_scene(scene->get_filename(), i);
|
|
else
|
|
_save_scene_with_preview(scene->get_filename());
|
|
} // else: ignore new scenes
|
|
}
|
|
|
|
_save_default_environment();
|
|
}
|
|
|
|
void EditorNode::_mark_unsaved_scenes() {
|
|
|
|
for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
|
|
|
|
Node *node = editor_data.get_edited_scene_root(i);
|
|
if (!node)
|
|
continue;
|
|
|
|
String path = node->get_filename();
|
|
if (!(path == String() || FileAccess::exists(path))) {
|
|
|
|
if (i == editor_data.get_edited_scene())
|
|
set_current_version(-1);
|
|
else
|
|
editor_data.set_edited_scene_version(-1, i);
|
|
}
|
|
}
|
|
|
|
_update_title();
|
|
_update_scene_tabs();
|
|
}
|
|
|
|
void EditorNode::_dialog_action(String p_file) {
|
|
|
|
switch (current_option) {
|
|
case FILE_NEW_INHERITED_SCENE: {
|
|
|
|
Node *scene = editor_data.get_edited_scene_root();
|
|
// If the previous scene is rootless, just close it in favor of the new one.
|
|
if (!scene)
|
|
_menu_option_confirm(FILE_CLOSE, true);
|
|
|
|
load_scene(p_file, false, true);
|
|
} break;
|
|
case FILE_OPEN_SCENE: {
|
|
|
|
load_scene(p_file);
|
|
} break;
|
|
case SETTINGS_PICK_MAIN_SCENE: {
|
|
|
|
ProjectSettings::get_singleton()->set("application/run/main_scene", p_file);
|
|
ProjectSettings::get_singleton()->save();
|
|
//would be nice to show the project manager opened with the highlighted field..
|
|
|
|
if (pick_main_scene->has_meta("from_native") && (bool)pick_main_scene->get_meta("from_native")) {
|
|
run_native->resume_run_native();
|
|
} else {
|
|
_run(false, ""); // automatically run the project
|
|
}
|
|
} break;
|
|
case FILE_CLOSE:
|
|
case FILE_CLOSE_ALL_AND_QUIT:
|
|
case FILE_CLOSE_ALL_AND_RUN_PROJECT_MANAGER:
|
|
case SCENE_TAB_CLOSE:
|
|
case FILE_SAVE_SCENE:
|
|
case FILE_SAVE_AS_SCENE: {
|
|
|
|
int scene_idx = (current_option == FILE_SAVE_SCENE || current_option == FILE_SAVE_AS_SCENE) ? -1 : tab_closing;
|
|
|
|
if (file->get_mode() == EditorFileDialog::MODE_SAVE_FILE) {
|
|
bool same_open_scene = false;
|
|
for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
|
|
if (editor_data.get_scene_path(i) == p_file && i != scene_idx)
|
|
same_open_scene = true;
|
|
}
|
|
|
|
if (same_open_scene) {
|
|
show_warning(TTR("Can't overwrite scene that is still open!"));
|
|
return;
|
|
}
|
|
|
|
_save_default_environment();
|
|
_save_scene_with_preview(p_file, scene_idx);
|
|
_add_to_recent_scenes(p_file);
|
|
save_layout();
|
|
|
|
if (scene_idx != -1)
|
|
_discard_changes();
|
|
}
|
|
|
|
} break;
|
|
|
|
case FILE_SAVE_AND_RUN: {
|
|
if (file->get_mode() == EditorFileDialog::MODE_SAVE_FILE) {
|
|
|
|
_save_default_environment();
|
|
_save_scene_with_preview(p_file);
|
|
_run(false, p_file);
|
|
}
|
|
} break;
|
|
|
|
case FILE_EXPORT_MESH_LIBRARY: {
|
|
|
|
Ref<MeshLibrary> ml;
|
|
if (file_export_lib_merge->is_pressed() && FileAccess::exists(p_file)) {
|
|
ml = ResourceLoader::load(p_file, "MeshLibrary");
|
|
|
|
if (ml.is_null()) {
|
|
show_accept(TTR("Can't load MeshLibrary for merging!"), TTR("OK"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (ml.is_null()) {
|
|
ml = Ref<MeshLibrary>(memnew(MeshLibrary));
|
|
}
|
|
|
|
MeshLibraryEditor::update_library_file(editor_data.get_edited_scene_root(), ml, true);
|
|
|
|
Error err = ResourceSaver::save(p_file, ml);
|
|
if (err) {
|
|
show_accept(TTR("Error saving MeshLibrary!"), TTR("OK"));
|
|
return;
|
|
}
|
|
|
|
} break;
|
|
case FILE_EXPORT_TILESET: {
|
|
|
|
Ref<TileSet> tileset;
|
|
if (FileAccess::exists(p_file) && file_export_lib_merge->is_pressed()) {
|
|
tileset = ResourceLoader::load(p_file, "TileSet");
|
|
|
|
if (tileset.is_null()) {
|
|
show_accept(TTR("Can't load TileSet for merging!"), TTR("OK"));
|
|
return;
|
|
}
|
|
|
|
} else {
|
|
tileset = Ref<TileSet>(memnew(TileSet));
|
|
}
|
|
|
|
TileSetEditor::update_library_file(editor_data.get_edited_scene_root(), tileset, true);
|
|
|
|
Error err = ResourceSaver::save(p_file, tileset);
|
|
if (err) {
|
|
|
|
show_accept(TTR("Error saving TileSet!"), TTR("OK"));
|
|
return;
|
|
}
|
|
} break;
|
|
|
|
case RESOURCE_SAVE:
|
|
case RESOURCE_SAVE_AS: {
|
|
|
|
ERR_FAIL_COND(saving_resource.is_null());
|
|
save_resource_in_path(saving_resource, p_file);
|
|
saving_resource = Ref<Resource>();
|
|
ObjectID current = editor_history.get_current();
|
|
Object *current_obj = current.is_valid() ? ObjectDB::get_instance(current) : NULL;
|
|
ERR_FAIL_COND(!current_obj);
|
|
current_obj->_change_notify();
|
|
} break;
|
|
case SETTINGS_LAYOUT_SAVE: {
|
|
|
|
if (p_file.empty())
|
|
return;
|
|
|
|
Ref<ConfigFile> config;
|
|
config.instance();
|
|
Error err = config->load(EditorSettings::get_singleton()->get_editor_layouts_config());
|
|
|
|
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!"));
|
|
return;
|
|
}
|
|
|
|
_save_docks_to_config(config, p_file);
|
|
|
|
config->save(EditorSettings::get_singleton()->get_editor_layouts_config());
|
|
|
|
layout_dialog->hide();
|
|
_update_layouts_menu();
|
|
|
|
if (p_file == "Default") {
|
|
show_warning(TTR("Default editor layout overridden."));
|
|
}
|
|
|
|
} break;
|
|
case SETTINGS_LAYOUT_DELETE: {
|
|
|
|
if (p_file.empty())
|
|
return;
|
|
|
|
Ref<ConfigFile> config;
|
|
config.instance();
|
|
Error err = config->load(EditorSettings::get_singleton()->get_editor_layouts_config());
|
|
|
|
if (err != OK || !config->has_section(p_file)) {
|
|
show_warning(TTR("Layout name not found!"));
|
|
return;
|
|
}
|
|
|
|
// erase
|
|
List<String> keys;
|
|
config->get_section_keys(p_file, &keys);
|
|
for (List<String>::Element *E = keys.front(); E; E = E->next()) {
|
|
config->set_value(p_file, E->get(), Variant());
|
|
}
|
|
|
|
config->save(EditorSettings::get_singleton()->get_editor_layouts_config());
|
|
|
|
layout_dialog->hide();
|
|
_update_layouts_menu();
|
|
|
|
if (p_file == "Default") {
|
|
show_warning(TTR("Restored default layout to base settings."));
|
|
}
|
|
|
|
} break;
|
|
default: { //save scene?
|
|
|
|
if (file->get_mode() == EditorFileDialog::MODE_SAVE_FILE) {
|
|
_save_scene_with_preview(p_file);
|
|
}
|
|
|
|
} break;
|
|
}
|
|
}
|
|
|
|
bool EditorNode::item_has_editor(Object *p_object) {
|
|
|
|
if (_is_class_editor_disabled_by_feature_profile(p_object->get_class())) {
|
|
return false;
|
|
}
|
|
|
|
return editor_data.get_subeditors(p_object).size() > 0;
|
|
}
|
|
|
|
void EditorNode::edit_item_resource(RES p_resource) {
|
|
edit_item(p_resource.ptr());
|
|
}
|
|
|
|
bool EditorNode::_is_class_editor_disabled_by_feature_profile(const StringName &p_class) {
|
|
|
|
Ref<EditorFeatureProfile> profile = EditorFeatureProfileManager::get_singleton()->get_current_profile();
|
|
if (profile.is_null()) {
|
|
return false;
|
|
}
|
|
|
|
StringName class_name = p_class;
|
|
|
|
while (class_name != StringName()) {
|
|
|
|
if (profile->is_class_disabled(class_name)) {
|
|
return true;
|
|
}
|
|
if (profile->is_class_editor_disabled(class_name)) {
|
|
return true;
|
|
}
|
|
class_name = ClassDB::get_parent_class(class_name);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void EditorNode::edit_item(Object *p_object) {
|
|
|
|
Vector<EditorPlugin *> sub_plugins;
|
|
|
|
if (p_object) {
|
|
if (_is_class_editor_disabled_by_feature_profile(p_object->get_class())) {
|
|
return;
|
|
}
|
|
sub_plugins = editor_data.get_subeditors(p_object);
|
|
}
|
|
|
|
if (!sub_plugins.empty()) {
|
|
|
|
bool same = true;
|
|
if (sub_plugins.size() == editor_plugins_over->get_plugins_list().size()) {
|
|
for (int i = 0; i < sub_plugins.size(); i++) {
|
|
if (sub_plugins[i] != editor_plugins_over->get_plugins_list()[i]) {
|
|
same = false;
|
|
}
|
|
}
|
|
} else {
|
|
same = false;
|
|
}
|
|
if (!same) {
|
|
_display_top_editors(false);
|
|
_set_top_editors(sub_plugins);
|
|
}
|
|
_set_editing_top_editors(p_object);
|
|
_display_top_editors(true);
|
|
} else {
|
|
hide_top_editors();
|
|
}
|
|
}
|
|
|
|
void EditorNode::push_item(Object *p_object, const String &p_property, bool p_inspector_only) {
|
|
|
|
if (!p_object) {
|
|
get_inspector()->edit(NULL);
|
|
node_dock->set_node(NULL);
|
|
scene_tree_dock->set_selected(NULL);
|
|
inspector_dock->update(NULL);
|
|
return;
|
|
}
|
|
|
|
ObjectID id = p_object->get_instance_id();
|
|
if (id != editor_history.get_current()) {
|
|
|
|
if (p_inspector_only) {
|
|
editor_history.add_object_inspector_only(id);
|
|
} else if (p_property == "")
|
|
editor_history.add_object(id);
|
|
else
|
|
editor_history.add_object(id, p_property);
|
|
}
|
|
|
|
_edit_current();
|
|
}
|
|
|
|
void EditorNode::_save_default_environment() {
|
|
|
|
Ref<Environment> fallback = get_tree()->get_root()->get_world()->get_fallback_environment();
|
|
|
|
if (fallback.is_valid() && fallback->get_path().is_resource_file()) {
|
|
Map<RES, bool> processed;
|
|
_find_and_save_edited_subresources(fallback.ptr(), processed, 0);
|
|
save_resource_in_path(fallback, fallback->get_path());
|
|
}
|
|
}
|
|
|
|
void EditorNode::hide_top_editors() {
|
|
|
|
_display_top_editors(false);
|
|
|
|
editor_plugins_over->clear();
|
|
}
|
|
|
|
void EditorNode::_display_top_editors(bool p_display) {
|
|
editor_plugins_over->make_visible(p_display);
|
|
}
|
|
|
|
void EditorNode::_set_top_editors(Vector<EditorPlugin *> p_editor_plugins_over) {
|
|
editor_plugins_over->set_plugins_list(p_editor_plugins_over);
|
|
}
|
|
|
|
void EditorNode::_set_editing_top_editors(Object *p_current_object) {
|
|
editor_plugins_over->edit(p_current_object);
|
|
}
|
|
|
|
static bool overrides_external_editor(Object *p_object) {
|
|
|
|
Script *script = Object::cast_to<Script>(p_object);
|
|
|
|
if (!script)
|
|
return false;
|
|
|
|
return script->get_language()->overrides_external_editor();
|
|
}
|
|
|
|
void EditorNode::_edit_current() {
|
|
|
|
ObjectID current = editor_history.get_current();
|
|
Object *current_obj = current.is_valid() ? ObjectDB::get_instance(current) : NULL;
|
|
bool inspector_only = editor_history.is_current_inspector_only();
|
|
|
|
this->current = current_obj;
|
|
|
|
if (!current_obj) {
|
|
|
|
scene_tree_dock->set_selected(NULL);
|
|
get_inspector()->edit(NULL);
|
|
node_dock->set_node(NULL);
|
|
inspector_dock->update(NULL);
|
|
|
|
_display_top_editors(false);
|
|
|
|
return;
|
|
}
|
|
|
|
Object *prev_inspected_object = get_inspector()->get_edited_object();
|
|
|
|
bool capitalize = bool(EDITOR_GET("interface/inspector/capitalize_properties"));
|
|
bool disable_folding = bool(EDITOR_GET("interface/inspector/disable_folding"));
|
|
bool is_resource = current_obj->is_class("Resource");
|
|
bool is_node = current_obj->is_class("Node");
|
|
|
|
String editable_warning; //none by default
|
|
|
|
if (is_resource) {
|
|
|
|
Resource *current_res = Object::cast_to<Resource>(current_obj);
|
|
ERR_FAIL_COND(!current_res);
|
|
get_inspector()->edit(current_res);
|
|
scene_tree_dock->set_selected(NULL);
|
|
node_dock->set_node(NULL);
|
|
inspector_dock->update(NULL);
|
|
EditorNode::get_singleton()->get_import_dock()->set_edit_path(current_res->get_path());
|
|
|
|
int subr_idx = current_res->get_path().find("::");
|
|
if (subr_idx != -1) {
|
|
String base_path = current_res->get_path().substr(0, subr_idx);
|
|
if (FileAccess::exists(base_path + ".import")) {
|
|
editable_warning = TTR("This resource belongs to a scene that was imported, so it's not editable.\nPlease read the documentation relevant to importing scenes to better understand this workflow.");
|
|
} else {
|
|
if ((!get_edited_scene() || get_edited_scene()->get_filename() != base_path) && ResourceLoader::get_resource_type(base_path) == "PackedScene") {
|
|
editable_warning = TTR("This resource belongs to a scene that was instanced or inherited.\nChanges to it won't be kept when saving the current scene.");
|
|
}
|
|
}
|
|
} else if (current_res->get_path().is_resource_file()) {
|
|
if (FileAccess::exists(current_res->get_path() + ".import")) {
|
|
editable_warning = TTR("This resource was imported, so it's not editable. Change its settings in the import panel and then re-import.");
|
|
}
|
|
}
|
|
} else if (is_node) {
|
|
|
|
Node *current_node = Object::cast_to<Node>(current_obj);
|
|
ERR_FAIL_COND(!current_node);
|
|
|
|
get_inspector()->edit(current_node);
|
|
if (current_node->is_inside_tree()) {
|
|
node_dock->set_node(current_node);
|
|
scene_tree_dock->set_selected(current_node);
|
|
inspector_dock->update(current_node);
|
|
} else {
|
|
node_dock->set_node(NULL);
|
|
scene_tree_dock->set_selected(NULL);
|
|
inspector_dock->update(NULL);
|
|
}
|
|
|
|
if (get_edited_scene() && get_edited_scene()->get_filename() != String()) {
|
|
String source_scene = get_edited_scene()->get_filename();
|
|
if (FileAccess::exists(source_scene + ".import")) {
|
|
editable_warning = TTR("This scene was imported, so changes to it won't be kept.\nInstancing it or inheriting will allow making changes to it.\nPlease read the documentation relevant to importing scenes to better understand this workflow.");
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
Node *selected_node = NULL;
|
|
|
|
if (current_obj->is_class("EditorDebuggerRemoteObject")) {
|
|
editable_warning = TTR("This is a remote object, so changes to it won't be kept.\nPlease read the documentation relevant to debugging to better understand this workflow.");
|
|
capitalize = false;
|
|
disable_folding = true;
|
|
} else if (current_obj->is_class("MultiNodeEdit")) {
|
|
Node *scene = get_edited_scene();
|
|
if (scene) {
|
|
MultiNodeEdit *multi_node_edit = Object::cast_to<MultiNodeEdit>(current_obj);
|
|
int node_count = multi_node_edit->get_node_count();
|
|
if (node_count > 0) {
|
|
List<Node *> multi_nodes;
|
|
for (int node_index = 0; node_index < node_count; ++node_index) {
|
|
Node *node = scene->get_node(multi_node_edit->get_node(node_index));
|
|
if (node) {
|
|
multi_nodes.push_back(node);
|
|
}
|
|
}
|
|
if (!multi_nodes.empty()) {
|
|
// Pick the top-most node
|
|
multi_nodes.sort_custom<Node::Comparator>();
|
|
selected_node = multi_nodes.front()->get();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
get_inspector()->edit(current_obj);
|
|
node_dock->set_node(NULL);
|
|
scene_tree_dock->set_selected(selected_node);
|
|
inspector_dock->update(NULL);
|
|
}
|
|
|
|
if (current_obj == prev_inspected_object) {
|
|
// Make sure inspected properties are restored.
|
|
get_inspector()->update_tree();
|
|
}
|
|
|
|
inspector_dock->set_warning(editable_warning);
|
|
|
|
if (get_inspector()->is_capitalize_paths_enabled() != capitalize) {
|
|
get_inspector()->set_enable_capitalize_paths(capitalize);
|
|
}
|
|
|
|
if (get_inspector()->is_using_folding() == disable_folding) {
|
|
get_inspector()->set_use_folding(!disable_folding);
|
|
}
|
|
|
|
/* Take care of PLUGIN EDITOR */
|
|
|
|
if (!inspector_only) {
|
|
|
|
EditorPlugin *main_plugin = editor_data.get_editor(current_obj);
|
|
|
|
for (int i = 0; i < editor_table.size(); i++) {
|
|
if (editor_table[i] == main_plugin && !main_editor_buttons[i]->is_visible()) {
|
|
main_plugin = NULL; //if button is not visible, then no plugin active
|
|
}
|
|
}
|
|
|
|
if (main_plugin) {
|
|
|
|
// special case if use of external editor is true
|
|
if (main_plugin->get_name() == "Script" && current_obj->get_class_name() != StringName("VisualScript") && (bool(EditorSettings::get_singleton()->get("text_editor/external/use_external_editor")) || overrides_external_editor(current_obj))) {
|
|
if (!changing_scene)
|
|
main_plugin->edit(current_obj);
|
|
}
|
|
|
|
else if (main_plugin != editor_plugin_screen && (!ScriptEditor::get_singleton() || !ScriptEditor::get_singleton()->is_visible_in_tree() || ScriptEditor::get_singleton()->can_take_away_focus())) {
|
|
// update screen main_plugin
|
|
|
|
if (!changing_scene) {
|
|
|
|
if (editor_plugin_screen)
|
|
editor_plugin_screen->make_visible(false);
|
|
editor_plugin_screen = main_plugin;
|
|
editor_plugin_screen->edit(current_obj);
|
|
|
|
editor_plugin_screen->make_visible(true);
|
|
|
|
int plugin_count = editor_data.get_editor_plugin_count();
|
|
for (int i = 0; i < plugin_count; i++) {
|
|
editor_data.get_editor_plugin(i)->notify_main_screen_changed(editor_plugin_screen->get_name());
|
|
}
|
|
|
|
for (int i = 0; i < editor_table.size(); i++) {
|
|
|
|
main_editor_buttons[i]->set_pressed(editor_table[i] == main_plugin);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
editor_plugin_screen->edit(current_obj);
|
|
}
|
|
}
|
|
|
|
Vector<EditorPlugin *> sub_plugins;
|
|
|
|
if (!_is_class_editor_disabled_by_feature_profile(current_obj->get_class())) {
|
|
sub_plugins = editor_data.get_subeditors(current_obj);
|
|
}
|
|
|
|
if (!sub_plugins.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()) {
|
|
|
|
hide_top_editors();
|
|
}
|
|
}
|
|
|
|
inspector_dock->update(current_obj);
|
|
inspector_dock->update_keying();
|
|
}
|
|
|
|
void EditorNode::_run(bool p_current, const String &p_custom) {
|
|
|
|
if (editor_run.get_status() == EditorRun::STATUS_PLAY) {
|
|
play_button->set_pressed(!_playing_edited);
|
|
play_scene_button->set_pressed(_playing_edited);
|
|
return;
|
|
}
|
|
|
|
play_button->set_pressed(false);
|
|
play_button->set_icon(gui_base->get_icon("MainPlay", "EditorIcons"));
|
|
play_scene_button->set_pressed(false);
|
|
play_scene_button->set_icon(gui_base->get_icon("PlayScene", "EditorIcons"));
|
|
play_custom_scene_button->set_pressed(false);
|
|
play_custom_scene_button->set_icon(gui_base->get_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())) {
|
|
|
|
Node *scene = editor_data.get_edited_scene_root();
|
|
|
|
if (!scene) {
|
|
show_accept(TTR("There is no defined scene to run."), TTR("OK"));
|
|
return;
|
|
}
|
|
|
|
if (scene->get_filename() == "") {
|
|
current_option = -1;
|
|
_menu_option_confirm(FILE_SAVE_BEFORE_RUN, false);
|
|
return;
|
|
}
|
|
|
|
run_filename = scene->get_filename();
|
|
} else if (p_custom != "") {
|
|
run_filename = p_custom;
|
|
}
|
|
|
|
if (run_filename == "") {
|
|
|
|
//evidently, run the scene
|
|
if (!ensure_main_scene(false)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (bool(EDITOR_GET("run/auto_save/save_before_running"))) {
|
|
|
|
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;
|
|
}
|
|
|
|
_save_scene_with_preview(scene->get_filename());
|
|
}
|
|
}
|
|
_menu_option(FILE_SAVE_ALL_SCENES);
|
|
editor_data.save_editor_external_data();
|
|
}
|
|
|
|
if (!call_build())
|
|
return;
|
|
|
|
if (bool(EDITOR_GET("run/output/always_clear_output_on_play"))) {
|
|
log->clear();
|
|
}
|
|
|
|
if (bool(EDITOR_GET("run/output/always_open_output_on_play"))) {
|
|
make_bottom_panel_item_visible(log);
|
|
}
|
|
|
|
List<String> breakpoints;
|
|
editor_data.get_editor_breakpoints(&breakpoints);
|
|
|
|
args = ProjectSettings::get_singleton()->get("editor/main_run_args");
|
|
skip_breakpoints = EditorDebuggerNode::get_singleton()->is_skip_breakpoints();
|
|
|
|
Error error = editor_run.run(run_filename, args, breakpoints, skip_breakpoints);
|
|
if (error != OK) {
|
|
|
|
show_accept(TTR("Could not start subprocess!"), TTR("OK"));
|
|
return;
|
|
}
|
|
|
|
emit_signal("play_pressed");
|
|
if (p_current) {
|
|
play_scene_button->set_pressed(true);
|
|
play_scene_button->set_icon(gui_base->get_icon("Reload", "EditorIcons"));
|
|
} else if (p_custom != "") {
|
|
run_custom_filename = p_custom;
|
|
play_custom_scene_button->set_pressed(true);
|
|
play_custom_scene_button->set_icon(gui_base->get_icon("Reload", "EditorIcons"));
|
|
} else {
|
|
play_button->set_pressed(true);
|
|
play_button->set_icon(gui_base->get_icon("Reload", "EditorIcons"));
|
|
}
|
|
stop_button->set_disabled(false);
|
|
|
|
_playing_edited = p_current;
|
|
}
|
|
|
|
void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
|
|
|
|
if (!p_confirmed) //this may be a hack..
|
|
current_option = (MenuOptions)p_option;
|
|
|
|
switch (p_option) {
|
|
case FILE_NEW_SCENE: {
|
|
|
|
new_scene();
|
|
|
|
} break;
|
|
case FILE_NEW_INHERITED_SCENE:
|
|
case FILE_OPEN_SCENE: {
|
|
|
|
file->set_mode(EditorFileDialog::MODE_OPEN_FILE);
|
|
List<String> extensions;
|
|
ResourceLoader::get_recognized_extensions_for_type("PackedScene", &extensions);
|
|
file->clear_filters();
|
|
for (int i = 0; i < extensions.size(); i++) {
|
|
|
|
file->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper());
|
|
}
|
|
|
|
Node *scene = editor_data.get_edited_scene_root();
|
|
if (scene) {
|
|
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();
|
|
|
|
} break;
|
|
case FILE_QUICK_OPEN: {
|
|
|
|
quick_open->popup_dialog("Resource", true);
|
|
quick_open->set_title(TTR("Quick Open..."));
|
|
|
|
} break;
|
|
case FILE_QUICK_OPEN_SCENE: {
|
|
|
|
quick_open->popup_dialog("PackedScene", true);
|
|
quick_open->set_title(TTR("Quick Open Scene..."));
|
|
|
|
} break;
|
|
case FILE_QUICK_OPEN_SCRIPT: {
|
|
|
|
quick_open->popup_dialog("Script", true);
|
|
quick_open->set_title(TTR("Quick Open Script..."));
|
|
|
|
} break;
|
|
case FILE_OPEN_PREV: {
|
|
|
|
if (previous_scenes.empty())
|
|
break;
|
|
opening_prev = true;
|
|
open_request(previous_scenes.back()->get());
|
|
previous_scenes.pop_back();
|
|
|
|
} break;
|
|
case FILE_CLOSE_OTHERS:
|
|
case FILE_CLOSE_RIGHT:
|
|
case FILE_CLOSE_ALL: {
|
|
if (editor_data.get_edited_scene_count() > 1 && (current_option != FILE_CLOSE_RIGHT || editor_data.get_edited_scene() < editor_data.get_edited_scene_count() - 1)) {
|
|
int next_tab = editor_data.get_edited_scene() + 1;
|
|
next_tab %= editor_data.get_edited_scene_count();
|
|
_scene_tab_closed(next_tab, current_option);
|
|
} else {
|
|
if (current_option != FILE_CLOSE_ALL)
|
|
current_option = -1;
|
|
else
|
|
_scene_tab_closed(editor_data.get_edited_scene());
|
|
}
|
|
|
|
if (p_confirmed)
|
|
_menu_option_confirm(SCENE_TAB_CLOSE, true);
|
|
|
|
} break;
|
|
case FILE_CLOSE_ALL_AND_QUIT:
|
|
case FILE_CLOSE_ALL_AND_RUN_PROJECT_MANAGER:
|
|
case FILE_CLOSE: {
|
|
|
|
if (!p_confirmed) {
|
|
tab_closing = p_option == FILE_CLOSE ? editor_data.get_edited_scene() : _next_unsaved_scene(false);
|
|
|
|
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->set_text(vformat(TTR("Save changes to '%s' before closing?"), scene_filename != "" ? scene_filename : "unsaved scene"));
|
|
save_confirmation->popup_centered_minsize();
|
|
break;
|
|
}
|
|
} else if (p_option == FILE_CLOSE) {
|
|
tab_closing = editor_data.get_edited_scene();
|
|
}
|
|
if (!editor_data.get_edited_scene_root(tab_closing)) {
|
|
// empty tab
|
|
_scene_tab_closed(tab_closing);
|
|
break;
|
|
}
|
|
|
|
[[fallthrough]];
|
|
}
|
|
case SCENE_TAB_CLOSE:
|
|
case FILE_SAVE_SCENE: {
|
|
|
|
int scene_idx = (p_option == FILE_SAVE_SCENE) ? -1 : tab_closing;
|
|
|
|
Node *scene = editor_data.get_edited_scene_root(scene_idx);
|
|
if (scene && scene->get_filename() != "") {
|
|
|
|
if (scene_idx != editor_data.get_edited_scene())
|
|
_save_scene_with_preview(scene->get_filename(), scene_idx);
|
|
else
|
|
_save_scene_with_preview(scene->get_filename());
|
|
|
|
if (scene_idx != -1)
|
|
_discard_changes();
|
|
save_layout();
|
|
|
|
break;
|
|
}
|
|
[[fallthrough]];
|
|
}
|
|
case FILE_SAVE_AS_SCENE: {
|
|
int scene_idx = (p_option == FILE_SAVE_SCENE || p_option == FILE_SAVE_AS_SCENE) ? -1 : tab_closing;
|
|
|
|
Node *scene = editor_data.get_edited_scene_root(scene_idx);
|
|
|
|
if (!scene) {
|
|
|
|
int saved = _save_external_resources();
|
|
String err_text;
|
|
if (saved > 0) {
|
|
err_text = vformat(TTR("Saved %s modified resource(s)."), itos(saved));
|
|
} else {
|
|
err_text = TTR("A root node is required to save the scene.");
|
|
}
|
|
|
|
show_accept(err_text, TTR("OK"));
|
|
break;
|
|
}
|
|
|
|
file->set_mode(EditorFileDialog::MODE_SAVE_FILE);
|
|
|
|
List<String> extensions;
|
|
Ref<PackedScene> sd = memnew(PackedScene);
|
|
ResourceSaver::get_recognized_extensions(sd, &extensions);
|
|
file->clear_filters();
|
|
for (int i = 0; i < extensions.size(); i++) {
|
|
|
|
file->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper());
|
|
}
|
|
|
|
if (scene->get_filename() != "") {
|
|
file->set_current_path(scene->get_filename());
|
|
if (extensions.size()) {
|
|
String ext = scene->get_filename().get_extension().to_lower();
|
|
if (extensions.find(ext) == NULL) {
|
|
file->set_current_path(scene->get_filename().replacen("." + ext, "." + extensions.front()->get()));
|
|
}
|
|
}
|
|
} else {
|
|
|
|
String existing;
|
|
if (extensions.size()) {
|
|
String root_name(scene->get_name());
|
|
existing = root_name + "." + extensions.front()->get().to_lower();
|
|
}
|
|
file->set_current_path(existing);
|
|
}
|
|
file->popup_centered_ratio();
|
|
file->set_title(TTR("Save Scene As..."));
|
|
|
|
} break;
|
|
|
|
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_minsize();
|
|
break;
|
|
}
|
|
|
|
_menu_option(FILE_SAVE_AS_SCENE);
|
|
_menu_option_confirm(FILE_SAVE_AND_RUN, false);
|
|
} break;
|
|
|
|
case FILE_EXPORT_PROJECT: {
|
|
|
|
project_export->popup_export();
|
|
} break;
|
|
|
|
case FILE_EXPORT_MESH_LIBRARY: {
|
|
|
|
if (!editor_data.get_edited_scene_root()) {
|
|
|
|
show_accept(TTR("This operation can't be done without a scene."), TTR("OK"));
|
|
break;
|
|
}
|
|
|
|
List<String> extensions;
|
|
Ref<MeshLibrary> ml(memnew(MeshLibrary));
|
|
ResourceSaver::get_recognized_extensions(ml, &extensions);
|
|
file_export_lib->clear_filters();
|
|
for (List<String>::Element *E = extensions.front(); E; E = E->next()) {
|
|
file_export_lib->add_filter("*." + E->get());
|
|
}
|
|
|
|
file_export_lib->popup_centered_ratio();
|
|
file_export_lib->set_title(TTR("Export Mesh Library"));
|
|
|
|
} break;
|
|
case FILE_EXPORT_TILESET: {
|
|
|
|
//Make sure that the scene has a root before trying to convert to tileset
|
|
if (!editor_data.get_edited_scene_root()) {
|
|
show_accept(TTR("This operation can't be done without a root node."), TTR("OK"));
|
|
break;
|
|
}
|
|
|
|
List<String> extensions;
|
|
Ref<TileSet> ml(memnew(TileSet));
|
|
ResourceSaver::get_recognized_extensions(ml, &extensions);
|
|
file_export_lib->clear_filters();
|
|
for (List<String>::Element *E = extensions.front(); E; E = E->next()) {
|
|
file_export_lib->add_filter("*." + E->get());
|
|
}
|
|
|
|
file_export_lib->popup_centered_ratio();
|
|
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->set_text(TTR("Current scene not saved. Open anyway?"));
|
|
confirmation->popup_centered_minsize();
|
|
break;
|
|
}
|
|
|
|
bool oprev = opening_prev;
|
|
Error err = load_scene(external_file);
|
|
if (err == OK && oprev) {
|
|
previous_scenes.pop_back();
|
|
opening_prev = false;
|
|
}
|
|
|
|
} break;
|
|
|
|
case EDIT_UNDO: {
|
|
|
|
if (Input::get_singleton()->get_mouse_button_mask() & 0x7) {
|
|
log->add_message("Can't undo while mouse buttons are pressed.", EditorLog::MSG_TYPE_EDITOR);
|
|
} else {
|
|
String action = editor_data.get_undo_redo().get_current_action_name();
|
|
|
|
if (!editor_data.get_undo_redo().undo()) {
|
|
log->add_message("Nothing to undo.", EditorLog::MSG_TYPE_EDITOR);
|
|
} else if (action != "") {
|
|
log->add_message("Undo: " + action, EditorLog::MSG_TYPE_EDITOR);
|
|
}
|
|
}
|
|
} break;
|
|
case EDIT_REDO: {
|
|
|
|
if (Input::get_singleton()->get_mouse_button_mask() & 0x7) {
|
|
log->add_message("Can't redo while mouse buttons are pressed.", EditorLog::MSG_TYPE_EDITOR);
|
|
} else {
|
|
if (!editor_data.get_undo_redo().redo()) {
|
|
log->add_message("Nothing to redo.", EditorLog::MSG_TYPE_EDITOR);
|
|
} else {
|
|
String action = editor_data.get_undo_redo().get_current_action_name();
|
|
log->add_message("Redo: " + action, EditorLog::MSG_TYPE_EDITOR);
|
|
}
|
|
}
|
|
} break;
|
|
|
|
case EDIT_REVERT: {
|
|
|
|
Node *scene = get_edited_scene();
|
|
|
|
if (!scene)
|
|
break;
|
|
|
|
String filename = scene->get_filename();
|
|
|
|
if (filename == String()) {
|
|
show_warning(TTR("Can't reload a scene that was never saved."));
|
|
break;
|
|
}
|
|
|
|
if (unsaved_cache && !p_confirmed) {
|
|
confirmation->get_ok()->set_text(TTR("Revert"));
|
|
confirmation->set_text(TTR("This action cannot be undone. Revert anyway?"));
|
|
confirmation->popup_centered_minsize();
|
|
break;
|
|
}
|
|
|
|
int cur_idx = editor_data.get_edited_scene();
|
|
_remove_edited_scene();
|
|
Error err = load_scene(filename);
|
|
if (err != OK)
|
|
ERR_PRINT("Failed to load scene");
|
|
editor_data.move_edited_scene_to_index(cur_idx);
|
|
get_undo_redo()->clear_history(false);
|
|
scene_tabs->set_current_tab(cur_idx);
|
|
|
|
} break;
|
|
case RUN_PLAY: {
|
|
_menu_option_confirm(RUN_STOP, true);
|
|
_run(false);
|
|
|
|
} break;
|
|
case RUN_PLAY_CUSTOM_SCENE: {
|
|
if (run_custom_filename.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);
|
|
}
|
|
|
|
} break;
|
|
case RUN_STOP: {
|
|
|
|
if (editor_run.get_status() == EditorRun::STATUS_STOP)
|
|
break;
|
|
|
|
editor_run.stop();
|
|
run_custom_filename.clear();
|
|
play_button->set_pressed(false);
|
|
play_button->set_icon(gui_base->get_icon("MainPlay", "EditorIcons"));
|
|
play_scene_button->set_pressed(false);
|
|
play_scene_button->set_icon(gui_base->get_icon("PlayScene", "EditorIcons"));
|
|
play_custom_scene_button->set_pressed(false);
|
|
play_custom_scene_button->set_icon(gui_base->get_icon("PlayCustom", "EditorIcons"));
|
|
stop_button->set_disabled(true);
|
|
|
|
if (bool(EDITOR_GET("run/output/always_close_output_on_stop"))) {
|
|
for (int i = 0; i < bottom_panel_items.size(); i++) {
|
|
if (bottom_panel_items[i].control == log) {
|
|
_bottom_panel_switch(false, i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
emit_signal("stop_pressed");
|
|
|
|
} break;
|
|
|
|
case FILE_SHOW_IN_FILESYSTEM: {
|
|
String path = editor_data.get_scene_path(editor_data.get_edited_scene());
|
|
if (path != String()) {
|
|
filesystem_dock->navigate_to_path(path);
|
|
}
|
|
} break;
|
|
|
|
case RUN_PLAY_SCENE: {
|
|
|
|
_save_default_environment();
|
|
_menu_option_confirm(RUN_STOP, true);
|
|
_run(true);
|
|
|
|
} break;
|
|
case RUN_PLAY_NATIVE: {
|
|
|
|
bool autosave = EDITOR_GET("run/auto_save/save_before_running");
|
|
if (autosave) {
|
|
_menu_option_confirm(FILE_SAVE_ALL_SCENES, false);
|
|
}
|
|
if (run_native->is_deploy_debug_remote_enabled()) {
|
|
_menu_option_confirm(RUN_STOP, true);
|
|
|
|
if (!call_build())
|
|
break; // build failed
|
|
|
|
emit_signal("play_pressed");
|
|
editor_run.run_native_notify();
|
|
}
|
|
} break;
|
|
case RUN_SCENE_SETTINGS: {
|
|
|
|
run_settings_dialog->popup_run_settings();
|
|
} break;
|
|
case RUN_SETTINGS: {
|
|
|
|
project_settings->popup_project_settings();
|
|
} break;
|
|
case FILE_INSTALL_ANDROID_SOURCE: {
|
|
|
|
if (p_confirmed) {
|
|
export_template_manager->install_android_template();
|
|
} else {
|
|
if (DirAccess::exists("res://android/build")) {
|
|
remove_android_build_template->popup_centered_minsize();
|
|
} else if (export_template_manager->can_install_android_template()) {
|
|
install_android_build_template->popup_centered_minsize();
|
|
} else {
|
|
custom_build_manage_templates->popup_centered_minsize();
|
|
}
|
|
}
|
|
} break;
|
|
case RUN_PROJECT_DATA_FOLDER: {
|
|
OS::get_singleton()->shell_open(String("file://") + OS::get_singleton()->get_user_data_dir());
|
|
} break;
|
|
case FILE_EXPLORE_ANDROID_BUILD_TEMPLATES: {
|
|
OS::get_singleton()->shell_open("file://" + ProjectSettings::get_singleton()->get_resource_path().plus_file("android"));
|
|
} break;
|
|
case FILE_QUIT:
|
|
case RUN_PROJECT_MANAGER: {
|
|
|
|
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_minsize();
|
|
} else {
|
|
_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);
|
|
} else {
|
|
|
|
String unsaved_scenes;
|
|
int i = _next_unsaved_scene(true, 0);
|
|
while (i != -1) {
|
|
unsaved_scenes += "\n " + editor_data.get_edited_scene_root(i)->get_filename();
|
|
i = _next_unsaved_scene(true, ++i);
|
|
}
|
|
|
|
save_confirmation->get_ok()->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_minsize();
|
|
}
|
|
}
|
|
|
|
OS::get_singleton()->request_attention();
|
|
break;
|
|
}
|
|
|
|
if (_next_unsaved_scene(true) != -1) {
|
|
_save_all_scenes();
|
|
}
|
|
_discard_changes();
|
|
} break;
|
|
case SETTINGS_UPDATE_CONTINUOUSLY: {
|
|
|
|
EditorSettings::get_singleton()->set("interface/editor/update_continuously", true);
|
|
_update_update_spinner();
|
|
show_accept(TTR("This option is deprecated. Situations where refresh must be forced are now considered a bug. Please report."), TTR("OK"));
|
|
} break;
|
|
case SETTINGS_UPDATE_WHEN_CHANGED: {
|
|
|
|
EditorSettings::get_singleton()->set("interface/editor/update_continuously", false);
|
|
_update_update_spinner();
|
|
} break;
|
|
case SETTINGS_UPDATE_SPINNER_HIDE: {
|
|
|
|
EditorSettings::get_singleton()->set("interface/editor/show_update_spinner", false);
|
|
_update_update_spinner();
|
|
} break;
|
|
case SETTINGS_PREFERENCES: {
|
|
|
|
settings_config_dialog->popup_edit_settings();
|
|
} break;
|
|
case SETTINGS_EDITOR_DATA_FOLDER: {
|
|
|
|
OS::get_singleton()->shell_open(String("file://") + EditorSettings::get_singleton()->get_data_dir());
|
|
} break;
|
|
case SETTINGS_EDITOR_CONFIG_FOLDER: {
|
|
|
|
OS::get_singleton()->shell_open(String("file://") + EditorSettings::get_singleton()->get_settings_dir());
|
|
} break;
|
|
case SETTINGS_MANAGE_EXPORT_TEMPLATES: {
|
|
|
|
export_template_manager->popup_manager();
|
|
|
|
} break;
|
|
case SETTINGS_MANAGE_FEATURE_PROFILES: {
|
|
|
|
feature_profile_manager->popup_centered_clamped(Size2(900, 800) * EDSCALE, 0.8);
|
|
} break;
|
|
case SETTINGS_TOGGLE_FULLSCREEN: {
|
|
|
|
OS::get_singleton()->set_window_fullscreen(!OS::get_singleton()->is_window_fullscreen());
|
|
|
|
} break;
|
|
case SETTINGS_TOGGLE_CONSOLE: {
|
|
|
|
bool was_visible = OS::get_singleton()->is_console_visible();
|
|
OS::get_singleton()->set_console_visible(!was_visible);
|
|
EditorSettings::get_singleton()->set_setting("interface/editor/hide_console_window", was_visible);
|
|
} break;
|
|
case EDITOR_SCREENSHOT: {
|
|
|
|
screenshot_timer->start();
|
|
} break;
|
|
case SETTINGS_PICK_MAIN_SCENE: {
|
|
|
|
file->set_mode(EditorFileDialog::MODE_OPEN_FILE);
|
|
List<String> extensions;
|
|
ResourceLoader::get_recognized_extensions_for_type("PackedScene", &extensions);
|
|
file->clear_filters();
|
|
for (int i = 0; i < extensions.size(); i++) {
|
|
|
|
file->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper());
|
|
}
|
|
|
|
Node *scene = editor_data.get_edited_scene_root();
|
|
if (scene) {
|
|
file->set_current_path(scene->get_filename());
|
|
};
|
|
file->set_title(TTR("Pick a Main Scene"));
|
|
file->popup_centered_ratio();
|
|
|
|
} break;
|
|
case HELP_SEARCH: {
|
|
emit_signal("request_help_search", "");
|
|
} break;
|
|
case HELP_DOCS: {
|
|
OS::get_singleton()->shell_open("https://docs.godotengine.org/");
|
|
} break;
|
|
case HELP_QA: {
|
|
OS::get_singleton()->shell_open("https://godotengine.org/qa/");
|
|
} break;
|
|
case HELP_ISSUES: {
|
|
OS::get_singleton()->shell_open("https://github.com/godotengine/godot/issues");
|
|
} break;
|
|
case HELP_COMMUNITY: {
|
|
OS::get_singleton()->shell_open("https://godotengine.org/community");
|
|
} break;
|
|
case HELP_ABOUT: {
|
|
about->popup_centered_minsize(Size2(780, 500) * EDSCALE);
|
|
} break;
|
|
|
|
case SET_VIDEO_DRIVER_SAVE_AND_RESTART: {
|
|
|
|
ProjectSettings::get_singleton()->set("rendering/quality/driver/driver_name", video_driver_request);
|
|
ProjectSettings::get_singleton()->save();
|
|
|
|
save_all_scenes();
|
|
restart_editor();
|
|
} break;
|
|
}
|
|
}
|
|
|
|
void EditorNode::_request_screenshot() {
|
|
_screenshot();
|
|
}
|
|
|
|
void EditorNode::_screenshot(bool p_use_utc) {
|
|
String name = "editor_screenshot_" + OS::get_singleton()->get_iso_date_time(p_use_utc).replace(":", "") + ".png";
|
|
NodePath path = String("user://") + name;
|
|
_save_screenshot(path);
|
|
if (EditorSettings::get_singleton()->get("interface/editor/automatically_open_screenshots")) {
|
|
OS::get_singleton()->shell_open(String("file://") + ProjectSettings::get_singleton()->globalize_path(path));
|
|
}
|
|
}
|
|
|
|
void EditorNode::_save_screenshot(NodePath p_path) {
|
|
|
|
Viewport *viewport = EditorInterface::get_singleton()->get_editor_viewport()->get_viewport();
|
|
viewport->set_clear_mode(Viewport::CLEAR_MODE_ONLY_NEXT_FRAME);
|
|
Ref<Image> img = viewport->get_texture()->get_data();
|
|
viewport->set_clear_mode(Viewport::CLEAR_MODE_ALWAYS);
|
|
Error error = img->save_png(p_path);
|
|
ERR_FAIL_COND_MSG(error != OK, "Cannot save screenshot to file '" + p_path + "'.");
|
|
}
|
|
|
|
void EditorNode::_tool_menu_option(int p_idx) {
|
|
switch (tool_menu->get_item_id(p_idx)) {
|
|
case TOOLS_ORPHAN_RESOURCES: {
|
|
orphan_resources->show();
|
|
} 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::CallError 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);
|
|
ERR_PRINT("Error calling function from tool menu: " + err);
|
|
}
|
|
} // else it's a submenu so don't do anything.
|
|
} break;
|
|
}
|
|
}
|
|
|
|
int EditorNode::_next_unsaved_scene(bool p_valid_filename, int p_start) {
|
|
|
|
for (int i = p_start; i < editor_data.get_edited_scene_count(); i++) {
|
|
|
|
if (!editor_data.get_edited_scene_root(i))
|
|
continue;
|
|
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;
|
|
if (unsaved) {
|
|
String scene_filename = editor_data.get_edited_scene_root(i)->get_filename();
|
|
if (p_valid_filename && scene_filename.length() == 0)
|
|
continue;
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void EditorNode::_exit_editor() {
|
|
exiting = true;
|
|
resource_preview->stop(); //stop early to avoid crashes
|
|
_save_docks();
|
|
|
|
// Dim the editor window while it's quitting to make it clearer that it's busy
|
|
dim_editor(true, true);
|
|
|
|
get_tree()->quit();
|
|
}
|
|
|
|
void EditorNode::_discard_changes(const String &p_str) {
|
|
|
|
switch (current_option) {
|
|
|
|
case FILE_CLOSE_ALL_AND_QUIT:
|
|
case FILE_CLOSE_ALL_AND_RUN_PROJECT_MANAGER:
|
|
case FILE_CLOSE:
|
|
case FILE_CLOSE_OTHERS:
|
|
case FILE_CLOSE_RIGHT:
|
|
case FILE_CLOSE_ALL:
|
|
case SCENE_TAB_CLOSE: {
|
|
|
|
Node *scene = editor_data.get_edited_scene_root(tab_closing);
|
|
if (scene != NULL) {
|
|
String scene_filename = scene->get_filename();
|
|
if (scene_filename != "") {
|
|
previous_scenes.push_back(scene_filename);
|
|
}
|
|
}
|
|
|
|
_remove_scene(tab_closing);
|
|
_update_scene_tabs();
|
|
|
|
if (current_option == FILE_CLOSE_ALL_AND_QUIT || current_option == FILE_CLOSE_ALL_AND_RUN_PROJECT_MANAGER) {
|
|
if (_next_unsaved_scene(false) == -1) {
|
|
current_option = current_option == FILE_CLOSE_ALL_AND_QUIT ? FILE_QUIT : RUN_PROJECT_MANAGER;
|
|
_discard_changes();
|
|
} else {
|
|
_menu_option_confirm(current_option, false);
|
|
}
|
|
} else if (current_option == FILE_CLOSE_OTHERS || current_option == FILE_CLOSE_RIGHT) {
|
|
if (editor_data.get_edited_scene_count() == 1 || (current_option == FILE_CLOSE_RIGHT && editor_data.get_edited_scene_count() <= editor_data.get_edited_scene() + 1)) {
|
|
current_option = -1;
|
|
save_confirmation->hide();
|
|
} else {
|
|
_menu_option_confirm(current_option, false);
|
|
}
|
|
} else if (current_option == FILE_CLOSE_ALL && editor_data.get_edited_scene_count() > 0) {
|
|
_menu_option_confirm(current_option, false);
|
|
} else {
|
|
current_option = -1;
|
|
save_confirmation->hide();
|
|
}
|
|
} break;
|
|
case FILE_QUIT: {
|
|
|
|
_menu_option_confirm(RUN_STOP, true);
|
|
_exit_editor();
|
|
|
|
} break;
|
|
case RUN_PROJECT_MANAGER: {
|
|
|
|
_menu_option_confirm(RUN_STOP, true);
|
|
_exit_editor();
|
|
String exec = OS::get_singleton()->get_executable_path();
|
|
|
|
List<String> args;
|
|
args.push_back("--path");
|
|
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);
|
|
ERR_FAIL_COND(err);
|
|
} break;
|
|
}
|
|
}
|
|
|
|
void EditorNode::_update_file_menu_opened() {
|
|
|
|
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");
|
|
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());
|
|
}
|
|
|
|
void EditorNode::_update_file_menu_closed() {
|
|
PopupMenu *pop = file_menu->get_popup();
|
|
pop->set_item_disabled(pop->get_item_index(FILE_OPEN_PREV), false);
|
|
}
|
|
|
|
Control *EditorNode::get_viewport() {
|
|
|
|
return viewport;
|
|
}
|
|
|
|
void EditorNode::_editor_select(int p_which) {
|
|
|
|
static bool selecting = false;
|
|
if (selecting || changing_scene)
|
|
return;
|
|
|
|
ERR_FAIL_INDEX(p_which, editor_table.size());
|
|
|
|
if (!main_editor_buttons[p_which]->is_visible()) //button hidden, no editor
|
|
return;
|
|
|
|
selecting = true;
|
|
|
|
for (int i = 0; i < main_editor_buttons.size(); i++) {
|
|
main_editor_buttons[i]->set_pressed(i == p_which);
|
|
}
|
|
|
|
selecting = false;
|
|
|
|
EditorPlugin *new_editor = editor_table[p_which];
|
|
ERR_FAIL_COND(!new_editor);
|
|
|
|
if (editor_plugin_screen == new_editor)
|
|
return;
|
|
|
|
if (editor_plugin_screen) {
|
|
editor_plugin_screen->make_visible(false);
|
|
}
|
|
|
|
editor_plugin_screen = new_editor;
|
|
editor_plugin_screen->make_visible(true);
|
|
editor_plugin_screen->selected_notify();
|
|
|
|
int plugin_count = editor_data.get_editor_plugin_count();
|
|
for (int i = 0; i < plugin_count; i++) {
|
|
editor_data.get_editor_plugin(i)->notify_main_screen_changed(editor_plugin_screen->get_name());
|
|
}
|
|
|
|
if (EditorSettings::get_singleton()->get("interface/editor/separate_distraction_mode")) {
|
|
if (p_which == EDITOR_SCRIPT) {
|
|
set_distraction_free_mode(script_distraction);
|
|
} else {
|
|
set_distraction_free_mode(scene_distraction);
|
|
}
|
|
}
|
|
}
|
|
|
|
void EditorNode::select_editor_by_name(const String &p_name) {
|
|
ERR_FAIL_COND(p_name == "");
|
|
|
|
for (int i = 0; i < main_editor_buttons.size(); i++) {
|
|
if (main_editor_buttons[i]->get_text() == p_name) {
|
|
_editor_select(i);
|
|
return;
|
|
}
|
|
}
|
|
|
|
ERR_FAIL_MSG("The editor name '" + p_name + "' was not found.");
|
|
}
|
|
|
|
void EditorNode::add_editor_plugin(EditorPlugin *p_editor, bool p_config_changed) {
|
|
|
|
if (p_editor->has_main_screen()) {
|
|
|
|
ToolButton *tb = memnew(ToolButton);
|
|
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());
|
|
Ref<Texture2D> icon = p_editor->get_icon();
|
|
|
|
if (icon.is_valid()) {
|
|
tb->set_icon(icon);
|
|
} else if (singleton->gui_base->has_icon(p_editor->get_name(), "EditorIcons")) {
|
|
tb->set_icon(singleton->gui_base->get_icon(p_editor->get_name(), "EditorIcons"));
|
|
}
|
|
|
|
tb->set_name(p_editor->get_name());
|
|
singleton->main_editor_buttons.push_back(tb);
|
|
singleton->main_editor_button_vb->add_child(tb);
|
|
singleton->editor_table.push_back(p_editor);
|
|
|
|
singleton->distraction_free->raise();
|
|
}
|
|
singleton->editor_data.add_editor_plugin(p_editor);
|
|
singleton->add_child(p_editor);
|
|
if (p_config_changed)
|
|
p_editor->enable_plugin();
|
|
}
|
|
|
|
void EditorNode::remove_editor_plugin(EditorPlugin *p_editor, bool p_config_changed) {
|
|
|
|
if (p_editor->has_main_screen()) {
|
|
|
|
for (int i = 0; i < singleton->main_editor_buttons.size(); i++) {
|
|
|
|
if (p_editor->get_name() == singleton->main_editor_buttons[i]->get_text()) {
|
|
|
|
if (singleton->main_editor_buttons[i]->is_pressed()) {
|
|
singleton->_editor_select(EDITOR_SCRIPT);
|
|
}
|
|
|
|
memdelete(singleton->main_editor_buttons[i]);
|
|
singleton->main_editor_buttons.remove(i);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
singleton->editor_table.erase(p_editor);
|
|
}
|
|
p_editor->make_visible(false);
|
|
p_editor->clear();
|
|
if (p_config_changed)
|
|
p_editor->disable_plugin();
|
|
singleton->editor_plugins_over->get_plugins_list().erase(p_editor);
|
|
singleton->remove_child(p_editor);
|
|
singleton->editor_data.remove_editor_plugin(p_editor);
|
|
singleton->get_editor_plugins_force_input_forwarding()->remove_plugin(p_editor);
|
|
}
|
|
|
|
void EditorNode::_update_addon_config() {
|
|
|
|
if (_initializing_addons)
|
|
return;
|
|
|
|
Vector<String> enabled_addons;
|
|
|
|
for (Map<String, EditorPlugin *>::Element *E = plugin_addons.front(); E; E = E->next()) {
|
|
enabled_addons.push_back(E->key());
|
|
}
|
|
|
|
if (enabled_addons.size() == 0) {
|
|
ProjectSettings::get_singleton()->set("editor_plugins/enabled", Variant());
|
|
} else {
|
|
ProjectSettings::get_singleton()->set("editor_plugins/enabled", enabled_addons);
|
|
}
|
|
|
|
project_settings->queue_save();
|
|
}
|
|
|
|
void EditorNode::set_addon_plugin_enabled(const String &p_addon, bool p_enabled, bool p_config_changed) {
|
|
|
|
ERR_FAIL_COND(p_enabled && plugin_addons.has(p_addon));
|
|
ERR_FAIL_COND(!p_enabled && !plugin_addons.has(p_addon));
|
|
|
|
if (!p_enabled) {
|
|
|
|
EditorPlugin *addon = plugin_addons[p_addon];
|
|
remove_editor_plugin(addon, p_config_changed);
|
|
memdelete(addon); //bye
|
|
plugin_addons.erase(p_addon);
|
|
_update_addon_config();
|
|
return;
|
|
}
|
|
|
|
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())) {
|
|
ProjectSettings *ps = ProjectSettings::get_singleton();
|
|
PackedStringArray enabled_plugins = ps->get("editor_plugins/enabled");
|
|
for (int i = 0; i < enabled_plugins.size(); ++i) {
|
|
if (enabled_plugins.get(i) == p_addon) {
|
|
enabled_plugins.remove(i);
|
|
break;
|
|
}
|
|
}
|
|
ps->set("editor_plugins/enabled", enabled_plugins);
|
|
ps->save();
|
|
WARN_PRINT("Addon '" + p_addon + "' failed to load. No directory found. Removing from enabled plugins.");
|
|
return;
|
|
}
|
|
Error err = cf->load(addon_path);
|
|
if (err != OK) {
|
|
show_warning(vformat(TTR("Unable to enable addon plugin at: '%s' parsing of config failed."), addon_path));
|
|
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));
|
|
return;
|
|
}
|
|
|
|
String script_path = cf->get_value("plugin", "script");
|
|
Ref<Script> script; // We need to save it for creating "ep" below.
|
|
|
|
// 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 = ResourceLoader::load(script_path);
|
|
|
|
if (script.is_null()) {
|
|
show_warning(vformat(TTR("Unable to load addon script from path: '%s'."), script_path));
|
|
return;
|
|
}
|
|
|
|
// Errors in the script cause the base_type to be an empty string.
|
|
if (String(script->get_instance_base_type()) == "") {
|
|
show_warning(vformat(TTR("Unable to load addon script from path: '%s' There seems to be an error in the code, please check the syntax."), script_path));
|
|
return;
|
|
}
|
|
|
|
// Plugin init scripts must inherit from EditorPlugin and be tools.
|
|
if (String(script->get_instance_base_type()) != "EditorPlugin") {
|
|
show_warning(vformat(TTR("Unable to load addon script from path: '%s' Base type is not EditorPlugin."), script_path));
|
|
return;
|
|
}
|
|
|
|
if (!script->is_tool()) {
|
|
show_warning(vformat(TTR("Unable to load addon script from path: '%s' Script is not in tool mode."), script_path));
|
|
return;
|
|
}
|
|
}
|
|
|
|
EditorPlugin *ep = memnew(EditorPlugin);
|
|
ep->set_script(script);
|
|
plugin_addons[p_addon] = ep;
|
|
add_editor_plugin(ep, p_config_changed);
|
|
|
|
_update_addon_config();
|
|
}
|
|
|
|
bool EditorNode::is_addon_plugin_enabled(const String &p_addon) const {
|
|
|
|
return plugin_addons.has(p_addon);
|
|
}
|
|
|
|
void EditorNode::_remove_edited_scene(bool p_change_tab) {
|
|
int new_index = editor_data.get_edited_scene();
|
|
int old_index = new_index;
|
|
|
|
if (new_index > 0) {
|
|
new_index = new_index - 1;
|
|
} else if (editor_data.get_edited_scene_count() > 1) {
|
|
new_index = 1;
|
|
} else {
|
|
editor_data.add_edited_scene(-1);
|
|
new_index = 1;
|
|
}
|
|
|
|
if (editor_data.get_scene_path(old_index) != String()) {
|
|
ScriptEditor::get_singleton()->close_builtin_scripts_from_scene(editor_data.get_scene_path(old_index));
|
|
}
|
|
|
|
if (p_change_tab) _scene_tab_changed(new_index);
|
|
editor_data.remove_scene(old_index);
|
|
editor_data.get_undo_redo().clear_history(false);
|
|
_update_title();
|
|
_update_scene_tabs();
|
|
}
|
|
|
|
void EditorNode::_remove_scene(int index, bool p_change_tab) {
|
|
|
|
if (editor_data.get_edited_scene() == index) {
|
|
//Scene to remove is current scene
|
|
_remove_edited_scene(p_change_tab);
|
|
} else {
|
|
//Scene to remove is not active scene
|
|
editor_data.remove_scene(index);
|
|
}
|
|
}
|
|
|
|
void EditorNode::set_edited_scene(Node *p_scene) {
|
|
|
|
if (get_editor_data().get_edited_scene_root()) {
|
|
if (get_editor_data().get_edited_scene_root()->get_parent() == scene_root)
|
|
scene_root->remove_child(get_editor_data().get_edited_scene_root());
|
|
}
|
|
get_editor_data().set_edited_scene_root(p_scene);
|
|
|
|
if (Object::cast_to<Popup>(p_scene))
|
|
Object::cast_to<Popup>(p_scene)->show(); //show popups
|
|
scene_tree_dock->set_edited_scene(p_scene);
|
|
if (get_tree())
|
|
get_tree()->set_edited_scene_root(p_scene);
|
|
|
|
if (p_scene) {
|
|
if (p_scene->get_parent() != scene_root)
|
|
scene_root->add_child(p_scene);
|
|
}
|
|
}
|
|
|
|
int EditorNode::_get_current_main_editor() {
|
|
|
|
for (int i = 0; i < editor_table.size(); i++) {
|
|
if (editor_table[i] == editor_plugin_screen)
|
|
return i;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
Dictionary EditorNode::_get_main_scene_state() {
|
|
|
|
Dictionary state;
|
|
state["main_tab"] = _get_current_main_editor();
|
|
state["scene_tree_offset"] = scene_tree_dock->get_tree_editor()->get_scene_tree()->get_vscroll_bar()->get_value();
|
|
state["property_edit_offset"] = get_inspector()->get_scroll_offset();
|
|
state["saved_version"] = saved_version;
|
|
state["node_filter"] = scene_tree_dock->get_filter();
|
|
return state;
|
|
}
|
|
|
|
void EditorNode::_set_main_scene_state(Dictionary p_state, Node *p_for_scene) {
|
|
|
|
if (get_edited_scene() != p_for_scene && p_for_scene != NULL)
|
|
return; //not for this scene
|
|
|
|
changing_scene = false;
|
|
|
|
int current = -1;
|
|
for (int i = 0; i < editor_table.size(); i++) {
|
|
if (editor_plugin_screen == editor_table[i]) {
|
|
current = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (p_state.has("editor_index")) {
|
|
int index = p_state["editor_index"];
|
|
if (current < 2) { //if currently in spatial/2d, only switch to spatial/2d. if currently in script, stay there
|
|
if (index < 2 || !get_edited_scene()) {
|
|
_editor_select(index);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (get_edited_scene()) {
|
|
if (current < 2) {
|
|
//use heuristic instead
|
|
int n2d = 0, n3d = 0;
|
|
_find_node_types(get_edited_scene(), n2d, n3d);
|
|
if (n2d > n3d) {
|
|
_editor_select(EDITOR_2D);
|
|
} else if (n3d > n2d) {
|
|
_editor_select(EDITOR_3D);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (p_state.has("scene_tree_offset"))
|
|
scene_tree_dock->get_tree_editor()->get_scene_tree()->get_vscroll_bar()->set_value(p_state["scene_tree_offset"]);
|
|
if (p_state.has("property_edit_offset"))
|
|
get_inspector()->set_scroll_offset(p_state["property_edit_offset"]);
|
|
|
|
if (p_state.has("node_filter"))
|
|
scene_tree_dock->set_filter(p_state["node_filter"]);
|
|
|
|
//this should only happen at the very end
|
|
|
|
EditorDebuggerNode::get_singleton()->update_live_edit_root();
|
|
ScriptEditor::get_singleton()->set_scene_root_script(editor_data.get_scene_root_script(editor_data.get_edited_scene()));
|
|
editor_data.notify_edited_scene_changed();
|
|
}
|
|
|
|
void EditorNode::set_current_version(uint64_t p_version) {
|
|
|
|
saved_version = p_version;
|
|
editor_data.set_edited_scene_version(p_version);
|
|
}
|
|
|
|
bool EditorNode::is_changing_scene() const {
|
|
return changing_scene;
|
|
}
|
|
|
|
void EditorNode::_clear_undo_history() {
|
|
|
|
get_undo_redo()->clear_history(false);
|
|
}
|
|
|
|
void EditorNode::set_current_scene(int p_idx) {
|
|
|
|
//Save the folding in case the scene gets reloaded.
|
|
if (editor_data.get_scene_path(p_idx) != "" && editor_data.get_edited_scene_root(p_idx))
|
|
editor_folding.save_scene_folding(editor_data.get_edited_scene_root(p_idx), editor_data.get_scene_path(p_idx));
|
|
|
|
if (editor_data.check_and_update_scene(p_idx)) {
|
|
if (editor_data.get_scene_path(p_idx) != "")
|
|
editor_folding.load_scene_folding(editor_data.get_edited_scene_root(p_idx), editor_data.get_scene_path(p_idx));
|
|
|
|
call_deferred("_clear_undo_history");
|
|
}
|
|
|
|
changing_scene = true;
|
|
editor_data.save_edited_scene_state(editor_selection, &editor_history, _get_main_scene_state());
|
|
|
|
if (get_editor_data().get_edited_scene_root()) {
|
|
if (get_editor_data().get_edited_scene_root()->get_parent() == scene_root)
|
|
scene_root->remove_child(get_editor_data().get_edited_scene_root());
|
|
}
|
|
|
|
editor_selection->clear();
|
|
editor_data.set_edited_scene(p_idx);
|
|
|
|
Node *new_scene = editor_data.get_edited_scene_root();
|
|
|
|
if (Object::cast_to<Popup>(new_scene))
|
|
Object::cast_to<Popup>(new_scene)->show(); //show popups
|
|
|
|
scene_tree_dock->set_edited_scene(new_scene);
|
|
if (get_tree())
|
|
get_tree()->set_edited_scene_root(new_scene);
|
|
|
|
if (new_scene) {
|
|
if (new_scene->get_parent() != scene_root)
|
|
scene_root->add_child(new_scene);
|
|
}
|
|
|
|
Dictionary state = editor_data.restore_edited_scene_state(editor_selection, &editor_history);
|
|
_edit_current();
|
|
|
|
_update_title();
|
|
|
|
call_deferred("_set_main_scene_state", state, get_edited_scene()); //do after everything else is done setting up
|
|
}
|
|
|
|
bool EditorNode::is_scene_open(const String &p_path) {
|
|
|
|
for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
|
|
if (editor_data.get_scene_path(i) == p_path)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void EditorNode::fix_dependencies(const String &p_for_file) {
|
|
dependency_fixer->edit(p_for_file);
|
|
}
|
|
|
|
int EditorNode::new_scene() {
|
|
int idx = editor_data.add_edited_scene(-1);
|
|
_scene_tab_changed(idx);
|
|
editor_data.clear_editor_states();
|
|
_update_scene_tabs();
|
|
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) {
|
|
|
|
if (!is_inside_tree()) {
|
|
defer_load_scene = p_scene;
|
|
return OK;
|
|
}
|
|
|
|
if (!p_set_inherited) {
|
|
|
|
for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
|
|
|
|
if (editor_data.get_scene_path(i) == p_scene) {
|
|
_scene_tab_changed(i);
|
|
return OK;
|
|
}
|
|
}
|
|
|
|
if (!p_force_open_imported && FileAccess::exists(p_scene + ".import")) {
|
|
open_imported->set_text(vformat(TTR("Scene '%s' was automatically imported, so it can't be modified.\nTo make changes to it, a new inherited scene can be created."), p_scene.get_file()));
|
|
open_imported->popup_centered_minsize();
|
|
new_inherited_button->grab_focus();
|
|
open_import_request = p_scene;
|
|
return OK;
|
|
}
|
|
}
|
|
|
|
if (p_clear_errors)
|
|
load_errors->clear();
|
|
|
|
String lpath = ProjectSettings::get_singleton()->localize_path(p_scene);
|
|
|
|
if (!lpath.begins_with("res://")) {
|
|
|
|
show_accept(TTR("Error loading scene, it must be inside the project path. Use 'Import' to open the scene, then save it inside the project path."), TTR("OK"));
|
|
opening_prev = false;
|
|
return ERR_FILE_NOT_FOUND;
|
|
}
|
|
|
|
int prev = editor_data.get_edited_scene();
|
|
int idx = editor_data.add_edited_scene(-1);
|
|
|
|
if (!editor_data.get_edited_scene_root() && editor_data.get_edited_scene_count() == 2) {
|
|
_remove_edited_scene();
|
|
} else {
|
|
_scene_tab_changed(idx);
|
|
}
|
|
|
|
dependency_errors.clear();
|
|
|
|
Error err;
|
|
Ref<PackedScene> sdata = ResourceLoader::load(lpath, "", true, &err);
|
|
if (!sdata.is_valid()) {
|
|
|
|
_dialog_display_load_error(lpath, err);
|
|
opening_prev = false;
|
|
|
|
if (prev != -1) {
|
|
set_current_scene(prev);
|
|
editor_data.remove_scene(idx);
|
|
}
|
|
return ERR_FILE_NOT_FOUND;
|
|
}
|
|
|
|
if (!p_ignore_broken_deps && dependency_errors.has(lpath)) {
|
|
|
|
current_option = -1;
|
|
Vector<String> errors;
|
|
for (Set<String>::Element *E = dependency_errors[lpath].front(); E; E = E->next()) {
|
|
|
|
errors.push_back(E->get());
|
|
}
|
|
dependency_error->show(DependencyErrorDialog::MODE_SCENE, lpath, errors);
|
|
opening_prev = false;
|
|
|
|
if (prev != -1) {
|
|
set_current_scene(prev);
|
|
editor_data.remove_scene(idx);
|
|
}
|
|
return ERR_FILE_MISSING_DEPENDENCIES;
|
|
}
|
|
|
|
dependency_errors.erase(lpath); //at least not self path
|
|
|
|
for (Map<String, Set<String>>::Element *E = dependency_errors.front(); E; E = E->next()) {
|
|
|
|
String txt = vformat(TTR("Scene '%s' has broken dependencies:"), E->key()) + "\n";
|
|
for (Set<String>::Element *F = E->get().front(); F; F = F->next()) {
|
|
txt += "\t" + F->get() + "\n";
|
|
}
|
|
add_io_error(txt);
|
|
}
|
|
|
|
if (ResourceCache::has(lpath)) {
|
|
//used from somewhere else? no problem! update state and replace sdata
|
|
Ref<PackedScene> ps = Ref<PackedScene>(Object::cast_to<PackedScene>(ResourceCache::get(lpath)));
|
|
if (ps.is_valid()) {
|
|
ps->replace_state(sdata->get_state());
|
|
ps->set_last_modified_time(sdata->get_last_modified_time());
|
|
sdata = ps;
|
|
}
|
|
|
|
} else {
|
|
sdata->set_path(lpath, true); //take over path
|
|
}
|
|
|
|
Node *new_scene = sdata->instance(PackedScene::GEN_EDIT_STATE_MAIN);
|
|
|
|
if (!new_scene) {
|
|
|
|
sdata.unref();
|
|
_dialog_display_load_error(lpath, ERR_FILE_NOT_FOUND);
|
|
opening_prev = false;
|
|
if (prev != -1) {
|
|
set_current_scene(prev);
|
|
editor_data.remove_scene(idx);
|
|
}
|
|
return ERR_FILE_NOT_FOUND;
|
|
}
|
|
|
|
if (p_set_inherited) {
|
|
Ref<SceneState> state = sdata->get_state();
|
|
state->set_path(lpath);
|
|
new_scene->set_scene_inherited_state(state);
|
|
new_scene->set_filename(String());
|
|
}
|
|
|
|
new_scene->set_scene_instance_state(Ref<SceneState>());
|
|
|
|
set_edited_scene(new_scene);
|
|
_get_scene_metadata(p_scene);
|
|
|
|
saved_version = editor_data.get_undo_redo().get_version();
|
|
_update_title();
|
|
_update_scene_tabs();
|
|
_add_to_recent_scenes(lpath);
|
|
|
|
if (editor_folding.has_folding_data(lpath)) {
|
|
editor_folding.load_scene_folding(new_scene, lpath);
|
|
} else if (EDITOR_GET("interface/inspector/auto_unfold_foreign_scenes")) {
|
|
editor_folding.unfold_scene(new_scene);
|
|
editor_folding.save_scene_folding(new_scene, lpath);
|
|
}
|
|
|
|
prev_scene->set_disabled(previous_scenes.size() == 0);
|
|
opening_prev = false;
|
|
scene_tree_dock->set_selected(new_scene);
|
|
|
|
EditorDebuggerNode::get_singleton()->update_live_edit_root();
|
|
|
|
push_item(new_scene);
|
|
|
|
if (!restoring_scenes) {
|
|
save_layout();
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
void EditorNode::open_request(const String &p_path) {
|
|
|
|
if (!opening_prev) {
|
|
List<String>::Element *prev_scene = previous_scenes.find(p_path);
|
|
if (prev_scene != NULL) {
|
|
prev_scene->erase();
|
|
}
|
|
}
|
|
|
|
load_scene(p_path); // as it will be opened in separate tab
|
|
}
|
|
|
|
void EditorNode::request_instance_scene(const String &p_path) {
|
|
|
|
scene_tree_dock->instance(p_path);
|
|
}
|
|
|
|
void EditorNode::request_instance_scenes(const Vector<String> &p_files) {
|
|
|
|
scene_tree_dock->instance_scenes(p_files);
|
|
}
|
|
|
|
ImportDock *EditorNode::get_import_dock() {
|
|
return import_dock;
|
|
}
|
|
|
|
FileSystemDock *EditorNode::get_filesystem_dock() {
|
|
|
|
return filesystem_dock;
|
|
}
|
|
SceneTreeDock *EditorNode::get_scene_tree_dock() {
|
|
|
|
return scene_tree_dock;
|
|
}
|
|
InspectorDock *EditorNode::get_inspector_dock() {
|
|
|
|
return inspector_dock;
|
|
}
|
|
|
|
void EditorNode::_inherit_request(String p_file) {
|
|
|
|
current_option = FILE_NEW_INHERITED_SCENE;
|
|
_dialog_action(p_file);
|
|
}
|
|
|
|
void EditorNode::_instance_request(const Vector<String> &p_files) {
|
|
|
|
request_instance_scenes(p_files);
|
|
}
|
|
|
|
void EditorNode::_close_messages() {
|
|
|
|
old_split_ofs = center_split->get_split_offset();
|
|
center_split->set_split_offset(0);
|
|
}
|
|
|
|
void EditorNode::_show_messages() {
|
|
|
|
center_split->set_split_offset(old_split_ofs);
|
|
}
|
|
|
|
void EditorNode::_add_to_recent_scenes(const String &p_scene) {
|
|
|
|
Array rc = EditorSettings::get_singleton()->get_project_metadata("recent_files", "scenes", Array());
|
|
if (rc.find(p_scene) != -1)
|
|
rc.erase(p_scene);
|
|
rc.push_front(p_scene);
|
|
if (rc.size() > 10)
|
|
rc.resize(10);
|
|
|
|
EditorSettings::get_singleton()->set_project_metadata("recent_files", "scenes", rc);
|
|
_update_recent_scenes();
|
|
}
|
|
|
|
void EditorNode::_open_recent_scene(int p_idx) {
|
|
|
|
if (p_idx == recent_scenes->get_item_count() - 1) {
|
|
|
|
EditorSettings::get_singleton()->set_project_metadata("recent_files", "scenes", Array());
|
|
call_deferred("_update_recent_scenes");
|
|
} else {
|
|
|
|
Array rc = EditorSettings::get_singleton()->get_project_metadata("recent_files", "scenes", Array());
|
|
ERR_FAIL_INDEX(p_idx, rc.size());
|
|
|
|
if (load_scene(rc[p_idx]) != OK) {
|
|
|
|
rc.remove(p_idx);
|
|
EditorSettings::get_singleton()->set_project_metadata("recent_files", "scenes", rc);
|
|
_update_recent_scenes();
|
|
}
|
|
}
|
|
}
|
|
|
|
void EditorNode::_update_recent_scenes() {
|
|
|
|
Array rc = EditorSettings::get_singleton()->get_project_metadata("recent_files", "scenes", Array());
|
|
recent_scenes->clear();
|
|
|
|
String path;
|
|
for (int i = 0; i < rc.size(); i++) {
|
|
|
|
path = rc[i];
|
|
recent_scenes->add_item(path.replace("res://", ""), i);
|
|
}
|
|
|
|
recent_scenes->add_separator();
|
|
recent_scenes->add_shortcut(ED_SHORTCUT("editor/clear_recent", TTR("Clear Recent Scenes")));
|
|
recent_scenes->set_as_minsize();
|
|
}
|
|
|
|
void EditorNode::_quick_opened() {
|
|
|
|
Vector<String> files = quick_open->get_selected_files();
|
|
|
|
for (int i = 0; i < files.size(); i++) {
|
|
String res_path = files[i];
|
|
|
|
if (quick_open->get_base_type() == "PackedScene") {
|
|
open_request(res_path);
|
|
} else {
|
|
load_resource(res_path);
|
|
}
|
|
}
|
|
}
|
|
|
|
void EditorNode::_quick_run() {
|
|
|
|
_run(false, quick_run->get_selected());
|
|
}
|
|
|
|
void EditorNode::notify_all_debug_sessions_exited() {
|
|
|
|
_menu_option_confirm(RUN_STOP, false);
|
|
stop_button->set_pressed(false);
|
|
editor_run.stop();
|
|
}
|
|
|
|
void EditorNode::add_io_error(const String &p_error) {
|
|
_load_error_notify(singleton, p_error);
|
|
}
|
|
|
|
void EditorNode::_load_error_notify(void *p_ud, const String &p_text) {
|
|
|
|
EditorNode *en = (EditorNode *)p_ud;
|
|
en->load_errors->add_image(en->gui_base->get_icon("Error", "EditorIcons"));
|
|
en->load_errors->add_text(p_text + "\n");
|
|
en->load_error_dialog->popup_centered_ratio(0.5);
|
|
}
|
|
|
|
bool EditorNode::_find_scene_in_use(Node *p_node, const String &p_path) const {
|
|
|
|
if (p_node->get_filename() == p_path) {
|
|
return true;
|
|
}
|
|
|
|
for (int i = 0; i < p_node->get_child_count(); i++) {
|
|
|
|
if (_find_scene_in_use(p_node->get_child(i), p_path)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool EditorNode::is_scene_in_use(const String &p_path) {
|
|
|
|
Node *es = get_edited_scene();
|
|
if (es)
|
|
return _find_scene_in_use(es, p_path);
|
|
return false;
|
|
}
|
|
|
|
void EditorNode::register_editor_types() {
|
|
|
|
ResourceLoader::set_timestamp_on_load(true);
|
|
ResourceSaver::set_timestamp_on_save(true);
|
|
|
|
ClassDB::register_class<EditorPlugin>();
|
|
ClassDB::register_class<EditorImportPlugin>();
|
|
ClassDB::register_class<EditorScript>();
|
|
ClassDB::register_class<EditorSelection>();
|
|
ClassDB::register_class<EditorFileDialog>();
|
|
ClassDB::register_virtual_class<EditorSettings>();
|
|
ClassDB::register_class<EditorSpatialGizmo>();
|
|
ClassDB::register_class<EditorSpatialGizmoPlugin>();
|
|
ClassDB::register_virtual_class<EditorResourcePreview>();
|
|
ClassDB::register_class<EditorResourcePreviewGenerator>();
|
|
ClassDB::register_virtual_class<EditorFileSystem>();
|
|
ClassDB::register_class<EditorFileSystemDirectory>();
|
|
ClassDB::register_class<EditorVCSInterface>();
|
|
ClassDB::register_virtual_class<ScriptEditor>();
|
|
ClassDB::register_virtual_class<EditorInterface>();
|
|
ClassDB::register_class<EditorExportPlugin>();
|
|
ClassDB::register_class<EditorResourceConversionPlugin>();
|
|
ClassDB::register_class<EditorSceneImporter>();
|
|
ClassDB::register_class<EditorInspector>();
|
|
ClassDB::register_class<EditorInspectorPlugin>();
|
|
ClassDB::register_class<EditorProperty>();
|
|
ClassDB::register_class<AnimationTrackEditPlugin>();
|
|
ClassDB::register_class<ScriptCreateDialog>();
|
|
ClassDB::register_class<EditorFeatureProfile>();
|
|
ClassDB::register_class<EditorSpinSlider>();
|
|
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>();
|
|
}
|
|
|
|
void EditorNode::unregister_editor_types() {
|
|
|
|
_init_callbacks.clear();
|
|
}
|
|
|
|
void EditorNode::stop_child_process(OS::ProcessID p_pid) {
|
|
|
|
if (has_child_process(p_pid)) {
|
|
editor_run.stop_child_process(p_pid);
|
|
if (!editor_run.get_child_process_count()) // All children stopped. Closing.
|
|
_menu_option_confirm(RUN_STOP, false);
|
|
}
|
|
}
|
|
|
|
Ref<Script> EditorNode::get_object_custom_type_base(const Object *p_object) const {
|
|
ERR_FAIL_COND_V(!p_object, NULL);
|
|
|
|
Ref<Script> script = p_object->get_script();
|
|
|
|
if (script.is_valid()) {
|
|
// Uncommenting would break things! Consider adding a parameter if you need it.
|
|
// StringName name = EditorNode::get_editor_data().script_class_get_name(base_script->get_path());
|
|
// if (name != StringName())
|
|
// return name;
|
|
|
|
// should probably be deprecated in 4.x
|
|
StringName base = script->get_instance_base_type();
|
|
if (base != StringName() && EditorNode::get_editor_data().get_custom_types().has(base)) {
|
|
const Vector<EditorData::CustomType> &types = EditorNode::get_editor_data().get_custom_types()[base];
|
|
|
|
Ref<Script> base_script = script;
|
|
while (base_script.is_valid()) {
|
|
for (int i = 0; i < types.size(); ++i) {
|
|
if (types[i].script == base_script) {
|
|
return types[i].script;
|
|
}
|
|
}
|
|
base_script = base_script->get_base_script();
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
StringName EditorNode::get_object_custom_type_name(const Object *p_object) const {
|
|
ERR_FAIL_COND_V(!p_object, StringName());
|
|
|
|
Ref<Script> script = p_object->get_script();
|
|
if (script.is_null() && p_object->is_class("Script")) {
|
|
script = p_object;
|
|
}
|
|
|
|
if (script.is_valid()) {
|
|
Ref<Script> base_script = script;
|
|
while (base_script.is_valid()) {
|
|
StringName name = EditorNode::get_editor_data().script_class_get_name(base_script->get_path());
|
|
if (name != StringName())
|
|
return name;
|
|
|
|
// should probably be deprecated in 4.x
|
|
StringName base = base_script->get_instance_base_type();
|
|
if (base != StringName() && EditorNode::get_editor_data().get_custom_types().has(base)) {
|
|
const Vector<EditorData::CustomType> &types = EditorNode::get_editor_data().get_custom_types()[base];
|
|
for (int i = 0; i < types.size(); ++i) {
|
|
if (types[i].script == base_script) {
|
|
return types[i].name;
|
|
}
|
|
}
|
|
}
|
|
base_script = base_script->get_base_script();
|
|
}
|
|
}
|
|
|
|
return StringName();
|
|
}
|
|
|
|
Ref<ImageTexture> EditorNode::_load_custom_class_icon(const String &p_path) const {
|
|
if (p_path.length()) {
|
|
Ref<Image> img = memnew(Image);
|
|
Error err = ImageLoader::load_image(p_path, img);
|
|
if (err == OK) {
|
|
Ref<ImageTexture> icon = memnew(ImageTexture);
|
|
img->resize(16 * EDSCALE, 16 * EDSCALE, Image::INTERPOLATE_LANCZOS);
|
|
icon->create_from_image(img);
|
|
return icon;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
Ref<Texture2D> EditorNode::get_object_icon(const Object *p_object, const String &p_fallback) const {
|
|
ERR_FAIL_COND_V(!p_object || !gui_base, NULL);
|
|
|
|
Ref<Script> script = p_object->get_script();
|
|
if (script.is_null() && p_object->is_class("Script")) {
|
|
script = p_object;
|
|
}
|
|
|
|
if (script.is_valid()) {
|
|
Ref<Script> base_script = script;
|
|
while (base_script.is_valid()) {
|
|
StringName name = EditorNode::get_editor_data().script_class_get_name(base_script->get_path());
|
|
String icon_path = EditorNode::get_editor_data().script_class_get_icon_path(name);
|
|
Ref<ImageTexture> icon = _load_custom_class_icon(icon_path);
|
|
if (icon.is_valid()) {
|
|
return icon;
|
|
}
|
|
|
|
// should probably be deprecated in 4.x
|
|
StringName base = base_script->get_instance_base_type();
|
|
if (base != StringName() && EditorNode::get_editor_data().get_custom_types().has(base)) {
|
|
const Vector<EditorData::CustomType> &types = EditorNode::get_editor_data().get_custom_types()[base];
|
|
for (int i = 0; i < types.size(); ++i) {
|
|
if (types[i].script == base_script && types[i].icon.is_valid()) {
|
|
return types[i].icon;
|
|
}
|
|
}
|
|
}
|
|
base_script = base_script->get_base_script();
|
|
}
|
|
}
|
|
|
|
// should probably be deprecated in 4.x
|
|
if (p_object->has_meta("_editor_icon"))
|
|
return p_object->get_meta("_editor_icon");
|
|
|
|
if (gui_base->has_icon(p_object->get_class(), "EditorIcons"))
|
|
return gui_base->get_icon(p_object->get_class(), "EditorIcons");
|
|
|
|
if (p_fallback.length())
|
|
return gui_base->get_icon(p_fallback, "EditorIcons");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
Ref<Texture2D> EditorNode::get_class_icon(const String &p_class, const String &p_fallback) const {
|
|
ERR_FAIL_COND_V_MSG(p_class.empty(), NULL, "Class name cannot be empty.");
|
|
|
|
if (gui_base->has_icon(p_class, "EditorIcons")) {
|
|
return gui_base->get_icon(p_class, "EditorIcons");
|
|
}
|
|
|
|
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");
|
|
|
|
while (script.is_valid()) {
|
|
String current_icon_path;
|
|
script->get_language()->get_global_class_name(script->get_path(), NULL, ¤t_icon_path);
|
|
icon = _load_custom_class_icon(current_icon_path);
|
|
if (icon.is_valid()) {
|
|
return icon;
|
|
}
|
|
script = script->get_base_script();
|
|
}
|
|
|
|
if (icon.is_null()) {
|
|
icon = gui_base->get_icon(ScriptServer::get_global_class_base(p_class), "EditorIcons");
|
|
}
|
|
|
|
return icon;
|
|
}
|
|
|
|
const Map<String, Vector<EditorData::CustomType>> &p_map = EditorNode::get_editor_data().get_custom_types();
|
|
for (const Map<String, Vector<EditorData::CustomType>>::Element *E = p_map.front(); E; E = E->next()) {
|
|
const Vector<EditorData::CustomType> &ct = E->value();
|
|
for (int i = 0; i < ct.size(); ++i) {
|
|
if (ct[i].name == p_class) {
|
|
if (ct[i].icon.is_valid()) {
|
|
return ct[i].icon;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (p_fallback.length() && gui_base->has_icon(p_fallback, "EditorIcons"))
|
|
return gui_base->get_icon(p_fallback, "EditorIcons");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void EditorNode::progress_add_task(const String &p_task, const String &p_label, int p_steps, bool p_can_cancel) {
|
|
|
|
if (singleton->cmdline_export_mode) {
|
|
print_line(p_task + ": begin: " + p_label + " steps: " + itos(p_steps));
|
|
} else {
|
|
singleton->progress_dialog->add_task(p_task, p_label, p_steps, p_can_cancel);
|
|
}
|
|
}
|
|
|
|
bool EditorNode::progress_task_step(const String &p_task, const String &p_state, int p_step, bool p_force_refresh) {
|
|
|
|
if (singleton->cmdline_export_mode) {
|
|
print_line("\t" + p_task + ": step " + itos(p_step) + ": " + p_state);
|
|
return false;
|
|
} else {
|
|
|
|
return singleton->progress_dialog->task_step(p_task, p_state, p_step, p_force_refresh);
|
|
}
|
|
}
|
|
|
|
void EditorNode::progress_end_task(const String &p_task) {
|
|
|
|
if (singleton->cmdline_export_mode) {
|
|
print_line(p_task + ": end");
|
|
} else {
|
|
singleton->progress_dialog->end_task(p_task);
|
|
}
|
|
}
|
|
|
|
void EditorNode::progress_add_task_bg(const String &p_task, const String &p_label, int p_steps) {
|
|
|
|
singleton->progress_hb->add_task(p_task, p_label, p_steps);
|
|
}
|
|
|
|
void EditorNode::progress_task_step_bg(const String &p_task, int p_step) {
|
|
|
|
singleton->progress_hb->task_step(p_task, p_step);
|
|
}
|
|
|
|
void EditorNode::progress_end_task_bg(const String &p_task) {
|
|
|
|
singleton->progress_hb->end_task(p_task);
|
|
}
|
|
|
|
Ref<Texture2D> EditorNode::_file_dialog_get_icon(const String &p_path) {
|
|
|
|
EditorFileSystemDirectory *efsd = EditorFileSystem::get_singleton()->get_filesystem_path(p_path.get_base_dir());
|
|
if (efsd) {
|
|
|
|
String file = p_path.get_file();
|
|
for (int i = 0; i < efsd->get_file_count(); i++) {
|
|
if (efsd->get_file(i) == file) {
|
|
|
|
String type = efsd->get_file_type(i);
|
|
|
|
if (singleton->icon_type_cache.has(type)) {
|
|
return singleton->icon_type_cache[type];
|
|
} else {
|
|
return singleton->icon_type_cache["Object"];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return singleton->icon_type_cache["Object"];
|
|
}
|
|
|
|
void EditorNode::_build_icon_type_cache() {
|
|
|
|
List<StringName> tl;
|
|
StringName ei = "EditorIcons";
|
|
theme_base->get_theme()->get_icon_list(ei, &tl);
|
|
for (List<StringName>::Element *E = tl.front(); E; E = E->next()) {
|
|
|
|
if (!ClassDB::class_exists(E->get()))
|
|
continue;
|
|
icon_type_cache[E->get()] = theme_base->get_theme()->get_icon(E->get(), ei);
|
|
}
|
|
}
|
|
|
|
void EditorNode::_file_dialog_register(FileDialog *p_dialog) {
|
|
|
|
singleton->file_dialogs.insert(p_dialog);
|
|
}
|
|
|
|
void EditorNode::_file_dialog_unregister(FileDialog *p_dialog) {
|
|
|
|
singleton->file_dialogs.erase(p_dialog);
|
|
}
|
|
|
|
void EditorNode::_editor_file_dialog_register(EditorFileDialog *p_dialog) {
|
|
|
|
singleton->editor_file_dialogs.insert(p_dialog);
|
|
}
|
|
|
|
void EditorNode::_editor_file_dialog_unregister(EditorFileDialog *p_dialog) {
|
|
|
|
singleton->editor_file_dialogs.erase(p_dialog);
|
|
}
|
|
|
|
Vector<EditorNodeInitCallback> EditorNode::_init_callbacks;
|
|
|
|
Error EditorNode::export_preset(const String &p_preset, const String &p_path, bool p_debug, bool p_pack_only) {
|
|
|
|
export_defer.preset = p_preset;
|
|
export_defer.path = p_path;
|
|
export_defer.debug = p_debug;
|
|
export_defer.pack_only = p_pack_only;
|
|
cmdline_export_mode = true;
|
|
return OK;
|
|
}
|
|
|
|
void EditorNode::show_accept(const String &p_text, const String &p_title) {
|
|
current_option = -1;
|
|
accept->get_ok()->set_text(p_title);
|
|
accept->set_text(p_text);
|
|
accept->popup_centered_minsize();
|
|
}
|
|
|
|
void EditorNode::show_warning(const String &p_text, const String &p_title) {
|
|
|
|
if (warning->is_inside_tree()) {
|
|
warning->set_text(p_text);
|
|
warning->set_title(p_title);
|
|
warning->popup_centered_minsize();
|
|
} else {
|
|
WARN_PRINT(p_title + " " + p_text);
|
|
}
|
|
}
|
|
|
|
void EditorNode::_copy_warning(const String &p_str) {
|
|
|
|
OS::get_singleton()->set_clipboard(warning->get_text());
|
|
}
|
|
|
|
void EditorNode::_dock_select_input(const Ref<InputEvent> &p_input) {
|
|
|
|
Ref<InputEventMouse> me = p_input;
|
|
|
|
if (me.is_valid()) {
|
|
|
|
Vector2 point = me->get_position();
|
|
|
|
int nrect = -1;
|
|
for (int i = 0; i < DOCK_SLOT_MAX; i++) {
|
|
if (dock_select_rect[i].has_point(point)) {
|
|
nrect = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (nrect != dock_select_rect_over) {
|
|
dock_select->update();
|
|
dock_select_rect_over = nrect;
|
|
}
|
|
|
|
if (nrect == -1)
|
|
return;
|
|
|
|
Ref<InputEventMouseButton> mb = me;
|
|
|
|
if (mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed() && dock_popup_selected != nrect) {
|
|
Control *dock = dock_slot[dock_popup_selected]->get_current_tab_control();
|
|
if (dock) {
|
|
dock_slot[dock_popup_selected]->remove_child(dock);
|
|
}
|
|
if (dock_slot[dock_popup_selected]->get_tab_count() == 0) {
|
|
dock_slot[dock_popup_selected]->hide();
|
|
|
|
} else {
|
|
|
|
dock_slot[dock_popup_selected]->set_current_tab(0);
|
|
}
|
|
|
|
dock_slot[nrect]->add_child(dock);
|
|
dock_popup_selected = nrect;
|
|
dock_slot[nrect]->set_current_tab(dock_slot[nrect]->get_tab_count() - 1);
|
|
dock_slot[nrect]->show();
|
|
dock_select->update();
|
|
|
|
for (int i = 0; i < vsplits.size(); i++) {
|
|
bool in_use = dock_slot[i * 2 + 0]->get_tab_count() || dock_slot[i * 2 + 1]->get_tab_count();
|
|
if (in_use)
|
|
vsplits[i]->show();
|
|
else
|
|
vsplits[i]->hide();
|
|
}
|
|
|
|
if (right_l_vsplit->is_visible() || right_r_vsplit->is_visible())
|
|
right_hsplit->show();
|
|
else
|
|
right_hsplit->hide();
|
|
|
|
_edit_current();
|
|
_save_docks();
|
|
}
|
|
}
|
|
}
|
|
|
|
void EditorNode::_dock_popup_exit() {
|
|
|
|
dock_select_rect_over = -1;
|
|
dock_select->update();
|
|
}
|
|
|
|
void EditorNode::_dock_pre_popup(int p_which) {
|
|
|
|
dock_popup_selected = p_which;
|
|
}
|
|
|
|
void EditorNode::_dock_move_left() {
|
|
|
|
if (dock_popup_selected < 0 || dock_popup_selected >= DOCK_SLOT_MAX)
|
|
return;
|
|
Control *current = dock_slot[dock_popup_selected]->get_tab_control(dock_slot[dock_popup_selected]->get_current_tab());
|
|
Control *prev = dock_slot[dock_popup_selected]->get_tab_control(dock_slot[dock_popup_selected]->get_current_tab() - 1);
|
|
if (!current || !prev)
|
|
return;
|
|
dock_slot[dock_popup_selected]->move_child(current, prev->get_index());
|
|
dock_slot[dock_popup_selected]->set_current_tab(dock_slot[dock_popup_selected]->get_current_tab() - 1);
|
|
dock_select->update();
|
|
_edit_current();
|
|
_save_docks();
|
|
}
|
|
|
|
void EditorNode::_dock_move_right() {
|
|
|
|
Control *current = dock_slot[dock_popup_selected]->get_tab_control(dock_slot[dock_popup_selected]->get_current_tab());
|
|
Control *next = dock_slot[dock_popup_selected]->get_tab_control(dock_slot[dock_popup_selected]->get_current_tab() + 1);
|
|
if (!current || !next)
|
|
return;
|
|
dock_slot[dock_popup_selected]->move_child(next, current->get_index());
|
|
dock_slot[dock_popup_selected]->set_current_tab(dock_slot[dock_popup_selected]->get_current_tab() + 1);
|
|
dock_select->update();
|
|
_edit_current();
|
|
_save_docks();
|
|
}
|
|
|
|
void EditorNode::_dock_select_draw() {
|
|
Size2 s = dock_select->get_size();
|
|
s.y /= 2.0;
|
|
s.x /= 6.0;
|
|
|
|
Color used = Color(0.6, 0.6, 0.6, 0.8);
|
|
Color used_selected = Color(0.8, 0.8, 0.8, 0.8);
|
|
Color tab_selected = theme_base->get_color("mono_color", "Editor");
|
|
Color unused = used;
|
|
unused.a = 0.4;
|
|
Color unusable = unused;
|
|
unusable.a = 0.1;
|
|
|
|
Rect2 unr(s.x * 2, 0, s.x * 2, s.y * 2);
|
|
unr.position += Vector2(2, 5);
|
|
unr.size -= Vector2(4, 7);
|
|
|
|
dock_select->draw_rect(unr, unusable);
|
|
|
|
dock_tab_move_left->set_disabled(true);
|
|
dock_tab_move_right->set_disabled(true);
|
|
|
|
if (dock_popup_selected != -1 && dock_slot[dock_popup_selected]->get_tab_count()) {
|
|
|
|
dock_tab_move_left->set_disabled(dock_slot[dock_popup_selected]->get_current_tab() == 0);
|
|
dock_tab_move_right->set_disabled(dock_slot[dock_popup_selected]->get_current_tab() >= dock_slot[dock_popup_selected]->get_tab_count() - 1);
|
|
}
|
|
|
|
for (int i = 0; i < DOCK_SLOT_MAX; i++) {
|
|
|
|
Vector2 ofs;
|
|
|
|
switch (i) {
|
|
case DOCK_SLOT_LEFT_UL: {
|
|
|
|
} break;
|
|
case DOCK_SLOT_LEFT_BL: {
|
|
ofs.y += s.y;
|
|
} break;
|
|
case DOCK_SLOT_LEFT_UR: {
|
|
ofs.x += s.x;
|
|
} break;
|
|
case DOCK_SLOT_LEFT_BR: {
|
|
ofs += s;
|
|
} break;
|
|
case DOCK_SLOT_RIGHT_UL: {
|
|
ofs.x += s.x * 4;
|
|
} break;
|
|
case DOCK_SLOT_RIGHT_BL: {
|
|
ofs.x += s.x * 4;
|
|
ofs.y += s.y;
|
|
|
|
} break;
|
|
case DOCK_SLOT_RIGHT_UR: {
|
|
ofs.x += s.x * 4;
|
|
ofs.x += s.x;
|
|
|
|
} break;
|
|
case DOCK_SLOT_RIGHT_BR: {
|
|
ofs.x += s.x * 4;
|
|
ofs += s;
|
|
|
|
} break;
|
|
}
|
|
|
|
Rect2 r(ofs, s);
|
|
dock_select_rect[i] = r;
|
|
r.position += Vector2(2, 5);
|
|
r.size -= Vector2(4, 7);
|
|
|
|
if (i == dock_select_rect_over) {
|
|
dock_select->draw_rect(r, used_selected);
|
|
} else if (dock_slot[i]->get_child_count() == 0) {
|
|
dock_select->draw_rect(r, unused);
|
|
} else {
|
|
|
|
dock_select->draw_rect(r, used);
|
|
}
|
|
|
|
for (int j = 0; j < MIN(3, dock_slot[i]->get_child_count()); j++) {
|
|
int xofs = (r.size.width / 3) * j;
|
|
Color c = used;
|
|
if (i == dock_popup_selected && (dock_slot[i]->get_current_tab() > 3 || dock_slot[i]->get_current_tab() == j))
|
|
c = tab_selected;
|
|
dock_select->draw_rect(Rect2(2 + ofs.x + xofs, ofs.y, r.size.width / 3 - 1, 3), c);
|
|
}
|
|
}
|
|
}
|
|
|
|
void EditorNode::_save_docks() {
|
|
|
|
if (waiting_for_first_scan) {
|
|
return; //scanning, do not touch docks
|
|
}
|
|
Ref<ConfigFile> config;
|
|
config.instance();
|
|
|
|
_save_docks_to_config(config, "docks");
|
|
_save_open_scenes_to_config(config, "EditorNode");
|
|
editor_data.get_plugin_window_layout(config);
|
|
|
|
config->save(EditorSettings::get_singleton()->get_project_settings_dir().plus_file("editor_layout.cfg"));
|
|
}
|
|
|
|
void EditorNode::_save_docks_to_config(Ref<ConfigFile> p_layout, const String &p_section) {
|
|
|
|
for (int i = 0; i < DOCK_SLOT_MAX; i++) {
|
|
String names;
|
|
for (int j = 0; j < dock_slot[i]->get_tab_count(); j++) {
|
|
String name = dock_slot[i]->get_tab_control(j)->get_name();
|
|
if (names != "")
|
|
names += ",";
|
|
names += name;
|
|
}
|
|
|
|
if (names != "") {
|
|
p_layout->set_value(p_section, "dock_" + itos(i + 1), names);
|
|
}
|
|
}
|
|
|
|
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_list_display_mode", filesystem_dock->get_file_list_display_mode());
|
|
|
|
for (int i = 0; i < vsplits.size(); i++) {
|
|
|
|
if (vsplits[i]->is_visible_in_tree()) {
|
|
p_layout->set_value(p_section, "dock_split_" + itos(i + 1), vsplits[i]->get_split_offset());
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < hsplits.size(); i++) {
|
|
|
|
p_layout->set_value(p_section, "dock_hsplit_" + itos(i + 1), hsplits[i]->get_split_offset());
|
|
}
|
|
}
|
|
|
|
void EditorNode::_save_open_scenes_to_config(Ref<ConfigFile> p_layout, const String &p_section) {
|
|
Array scenes;
|
|
for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
|
|
String path = editor_data.get_scene_path(i);
|
|
if (path == "") {
|
|
continue;
|
|
}
|
|
scenes.push_back(path);
|
|
}
|
|
p_layout->set_value(p_section, "open_scenes", scenes);
|
|
}
|
|
|
|
void EditorNode::save_layout() {
|
|
|
|
dock_drag_timer->start();
|
|
}
|
|
|
|
void EditorNode::_dock_split_dragged(int ofs) {
|
|
|
|
dock_drag_timer->start();
|
|
}
|
|
|
|
void EditorNode::_load_docks() {
|
|
|
|
Ref<ConfigFile> config;
|
|
config.instance();
|
|
Error err = config->load(EditorSettings::get_singleton()->get_project_settings_dir().plus_file("editor_layout.cfg"));
|
|
if (err != OK) {
|
|
//no config
|
|
if (overridden_default_layout >= 0) {
|
|
_layout_menu_option(overridden_default_layout);
|
|
}
|
|
return;
|
|
}
|
|
|
|
_load_docks_from_config(config, "docks");
|
|
_load_open_scenes_from_config(config, "EditorNode");
|
|
|
|
editor_data.set_plugin_window_layout(config);
|
|
}
|
|
|
|
void EditorNode::_update_dock_slots_visibility() {
|
|
|
|
if (!docks_visible) {
|
|
|
|
for (int i = 0; i < DOCK_SLOT_MAX; i++) {
|
|
dock_slot[i]->hide();
|
|
}
|
|
|
|
for (int i = 0; i < vsplits.size(); i++) {
|
|
vsplits[i]->hide();
|
|
}
|
|
|
|
right_hsplit->hide();
|
|
bottom_panel->hide();
|
|
} else {
|
|
for (int i = 0; i < DOCK_SLOT_MAX; i++) {
|
|
|
|
int tabs_visible = 0;
|
|
for (int j = 0; j < dock_slot[i]->get_tab_count(); j++) {
|
|
if (!dock_slot[i]->get_tab_hidden(j)) {
|
|
tabs_visible++;
|
|
}
|
|
}
|
|
if (tabs_visible)
|
|
dock_slot[i]->show();
|
|
else
|
|
dock_slot[i]->hide();
|
|
}
|
|
|
|
for (int i = 0; i < vsplits.size(); i++) {
|
|
bool in_use = dock_slot[i * 2 + 0]->get_tab_count() || dock_slot[i * 2 + 1]->get_tab_count();
|
|
if (in_use)
|
|
vsplits[i]->show();
|
|
else
|
|
vsplits[i]->hide();
|
|
}
|
|
|
|
for (int i = 0; i < DOCK_SLOT_MAX; i++) {
|
|
|
|
if (dock_slot[i]->is_visible() && dock_slot[i]->get_tab_count()) {
|
|
dock_slot[i]->set_current_tab(0);
|
|
}
|
|
}
|
|
bottom_panel->show();
|
|
|
|
if (right_l_vsplit->is_visible() || right_r_vsplit->is_visible())
|
|
right_hsplit->show();
|
|
else
|
|
right_hsplit->hide();
|
|
}
|
|
}
|
|
|
|
void EditorNode::_dock_tab_changed(int p_tab) {
|
|
|
|
// update visibility but don't set current tab
|
|
|
|
if (!docks_visible) {
|
|
|
|
for (int i = 0; i < DOCK_SLOT_MAX; i++) {
|
|
dock_slot[i]->hide();
|
|
}
|
|
|
|
for (int i = 0; i < vsplits.size(); i++) {
|
|
vsplits[i]->hide();
|
|
}
|
|
|
|
right_hsplit->hide();
|
|
bottom_panel->hide();
|
|
} else {
|
|
for (int i = 0; i < DOCK_SLOT_MAX; i++) {
|
|
|
|
if (dock_slot[i]->get_tab_count())
|
|
dock_slot[i]->show();
|
|
else
|
|
dock_slot[i]->hide();
|
|
}
|
|
|
|
for (int i = 0; i < vsplits.size(); i++) {
|
|
bool in_use = dock_slot[i * 2 + 0]->get_tab_count() || dock_slot[i * 2 + 1]->get_tab_count();
|
|
if (in_use)
|
|
vsplits[i]->show();
|
|
else
|
|
vsplits[i]->hide();
|
|
}
|
|
bottom_panel->show();
|
|
|
|
if (right_l_vsplit->is_visible() || right_r_vsplit->is_visible())
|
|
right_hsplit->show();
|
|
else
|
|
right_hsplit->hide();
|
|
}
|
|
}
|
|
|
|
void EditorNode::_load_docks_from_config(Ref<ConfigFile> p_layout, const String &p_section) {
|
|
|
|
for (int i = 0; i < DOCK_SLOT_MAX; i++) {
|
|
|
|
if (!p_layout->has_section_key(p_section, "dock_" + itos(i + 1)))
|
|
continue;
|
|
|
|
Vector<String> names = String(p_layout->get_value(p_section, "dock_" + itos(i + 1))).split(",");
|
|
|
|
for (int j = 0; j < names.size(); j++) {
|
|
|
|
String name = names[j];
|
|
//find it, in a horribly inefficient way
|
|
int atidx = -1;
|
|
Control *node = NULL;
|
|
for (int k = 0; k < DOCK_SLOT_MAX; k++) {
|
|
if (!dock_slot[k]->has_node(name))
|
|
continue;
|
|
node = Object::cast_to<Control>(dock_slot[k]->get_node(name));
|
|
if (!node)
|
|
continue;
|
|
atidx = k;
|
|
break;
|
|
}
|
|
if (atidx == -1) //well, it's not anywhere
|
|
continue;
|
|
|
|
if (atidx == i) {
|
|
node->raise();
|
|
continue;
|
|
}
|
|
|
|
dock_slot[atidx]->remove_child(node);
|
|
|
|
if (dock_slot[atidx]->get_tab_count() == 0) {
|
|
dock_slot[atidx]->hide();
|
|
}
|
|
dock_slot[i]->add_child(node);
|
|
dock_slot[i]->show();
|
|
}
|
|
}
|
|
|
|
if (p_layout->has_section_key(p_section, "dock_filesystem_split")) {
|
|
int fs_split_ofs = p_layout->get_value(p_section, "dock_filesystem_split");
|
|
filesystem_dock->set_split_offset(fs_split_ofs);
|
|
}
|
|
|
|
if (p_layout->has_section_key(p_section, "dock_filesystem_display_mode")) {
|
|
FileSystemDock::DisplayMode dock_filesystem_display_mode = FileSystemDock::DisplayMode(int(p_layout->get_value(p_section, "dock_filesystem_display_mode")));
|
|
filesystem_dock->set_display_mode(dock_filesystem_display_mode);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
for (int i = 0; i < vsplits.size(); i++) {
|
|
|
|
if (!p_layout->has_section_key(p_section, "dock_split_" + itos(i + 1)))
|
|
continue;
|
|
|
|
int ofs = p_layout->get_value(p_section, "dock_split_" + itos(i + 1));
|
|
vsplits[i]->set_split_offset(ofs);
|
|
}
|
|
|
|
for (int i = 0; i < hsplits.size(); i++) {
|
|
if (!p_layout->has_section_key(p_section, "dock_hsplit_" + itos(i + 1)))
|
|
continue;
|
|
int ofs = p_layout->get_value(p_section, "dock_hsplit_" + itos(i + 1));
|
|
hsplits[i]->set_split_offset(ofs);
|
|
}
|
|
|
|
for (int i = 0; i < vsplits.size(); i++) {
|
|
bool in_use = dock_slot[i * 2 + 0]->get_tab_count() || dock_slot[i * 2 + 1]->get_tab_count();
|
|
if (in_use)
|
|
vsplits[i]->show();
|
|
else
|
|
vsplits[i]->hide();
|
|
}
|
|
|
|
if (right_l_vsplit->is_visible() || right_r_vsplit->is_visible())
|
|
right_hsplit->show();
|
|
else
|
|
right_hsplit->hide();
|
|
|
|
for (int i = 0; i < DOCK_SLOT_MAX; i++) {
|
|
|
|
if (dock_slot[i]->is_visible() && dock_slot[i]->get_tab_count()) {
|
|
dock_slot[i]->set_current_tab(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void EditorNode::_load_open_scenes_from_config(Ref<ConfigFile> p_layout, const String &p_section) {
|
|
if (!bool(EDITOR_GET("interface/scene_tabs/restore_scenes_on_load"))) {
|
|
return;
|
|
}
|
|
|
|
if (!p_layout->has_section(p_section) || !p_layout->has_section_key(p_section, "open_scenes")) {
|
|
return;
|
|
}
|
|
|
|
restoring_scenes = true;
|
|
|
|
Array scenes = p_layout->get_value(p_section, "open_scenes");
|
|
for (int i = 0; i < scenes.size(); i++) {
|
|
load_scene(scenes[i]);
|
|
}
|
|
save_layout();
|
|
|
|
restoring_scenes = false;
|
|
}
|
|
|
|
bool EditorNode::has_scenes_in_session() {
|
|
if (!bool(EDITOR_GET("interface/scene_tabs/restore_scenes_on_load"))) {
|
|
return false;
|
|
}
|
|
Ref<ConfigFile> config;
|
|
config.instance();
|
|
Error err = config->load(EditorSettings::get_singleton()->get_project_settings_dir().plus_file("editor_layout.cfg"));
|
|
if (err != OK) {
|
|
return false;
|
|
}
|
|
if (!config->has_section("EditorNode") || !config->has_section_key("EditorNode", "open_scenes")) {
|
|
return false;
|
|
}
|
|
Array scenes = config->get_value("EditorNode", "open_scenes");
|
|
return !scenes.empty();
|
|
}
|
|
|
|
bool EditorNode::ensure_main_scene(bool p_from_native) {
|
|
pick_main_scene->set_meta("from_native", p_from_native); //whether from play button or native run
|
|
String main_scene = GLOBAL_DEF("application/run/main_scene", "");
|
|
|
|
if (main_scene == "") {
|
|
|
|
current_option = -1;
|
|
pick_main_scene->set_text(TTR("No main scene has ever been defined, select one?\nYou can change it later in \"Project Settings\" under the 'application' category."));
|
|
pick_main_scene->popup_centered_minsize();
|
|
return false;
|
|
}
|
|
|
|
if (!FileAccess::exists(main_scene)) {
|
|
|
|
current_option = -1;
|
|
pick_main_scene->set_text(vformat(TTR("Selected scene '%s' does not exist, select a valid one?\nYou can change it later in \"Project Settings\" under the 'application' category."), main_scene));
|
|
pick_main_scene->popup_centered_minsize();
|
|
return false;
|
|
}
|
|
|
|
if (ResourceLoader::get_resource_type(main_scene) != "PackedScene") {
|
|
|
|
current_option = -1;
|
|
pick_main_scene->set_text(vformat(TTR("Selected scene '%s' is not a scene file, select a valid one?\nYou can change it later in \"Project Settings\" under the 'application' category."), main_scene));
|
|
pick_main_scene->popup_centered_minsize();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void EditorNode::run_play() {
|
|
_menu_option_confirm(RUN_STOP, true);
|
|
_run(false);
|
|
}
|
|
|
|
void EditorNode::run_stop() {
|
|
_menu_option_confirm(RUN_STOP, false);
|
|
}
|
|
|
|
int EditorNode::get_current_tab() {
|
|
return scene_tabs->get_current_tab();
|
|
}
|
|
|
|
void EditorNode::set_current_tab(int p_tab) {
|
|
scene_tabs->set_current_tab(p_tab);
|
|
}
|
|
|
|
void EditorNode::_update_layouts_menu() {
|
|
|
|
editor_layouts->clear();
|
|
overridden_default_layout = -1;
|
|
|
|
editor_layouts->set_size(Vector2());
|
|
editor_layouts->add_shortcut(ED_SHORTCUT("layout/save", TTR("Save Layout")), SETTINGS_LAYOUT_SAVE);
|
|
editor_layouts->add_shortcut(ED_SHORTCUT("layout/delete", TTR("Delete Layout")), SETTINGS_LAYOUT_DELETE);
|
|
editor_layouts->add_separator();
|
|
editor_layouts->add_shortcut(ED_SHORTCUT("layout/default", TTR("Default")), SETTINGS_LAYOUT_DEFAULT);
|
|
|
|
Ref<ConfigFile> config;
|
|
config.instance();
|
|
Error err = config->load(EditorSettings::get_singleton()->get_editor_layouts_config());
|
|
if (err != OK) {
|
|
return; //no config
|
|
}
|
|
|
|
List<String> layouts;
|
|
config.ptr()->get_sections(&layouts);
|
|
|
|
for (List<String>::Element *E = layouts.front(); E; E = E->next()) {
|
|
|
|
String layout = E->get();
|
|
|
|
if (layout == TTR("Default")) {
|
|
editor_layouts->remove_item(editor_layouts->get_item_index(SETTINGS_LAYOUT_DEFAULT));
|
|
overridden_default_layout = editor_layouts->get_item_count();
|
|
}
|
|
|
|
editor_layouts->add_item(layout);
|
|
}
|
|
}
|
|
|
|
void EditorNode::_layout_menu_option(int p_id) {
|
|
|
|
switch (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->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->popup_centered();
|
|
layout_dialog->set_name_line_enabled(false);
|
|
} break;
|
|
case SETTINGS_LAYOUT_DEFAULT: {
|
|
|
|
_load_docks_from_config(default_layout, "docks");
|
|
_save_docks();
|
|
} break;
|
|
default: {
|
|
|
|
Ref<ConfigFile> config;
|
|
config.instance();
|
|
Error err = config->load(EditorSettings::get_singleton()->get_editor_layouts_config());
|
|
if (err != OK) {
|
|
return; //no config
|
|
}
|
|
|
|
_load_docks_from_config(config, editor_layouts->get_item_text(p_id));
|
|
_save_docks();
|
|
}
|
|
}
|
|
}
|
|
|
|
void EditorNode::_scene_tab_script_edited(int p_tab) {
|
|
|
|
Ref<Script> script = editor_data.get_scene_root_script(p_tab);
|
|
if (script.is_valid())
|
|
inspector_dock->edit_resource(script);
|
|
}
|
|
|
|
void EditorNode::_scene_tab_closed(int p_tab, int option) {
|
|
current_option = option;
|
|
tab_closing = p_tab;
|
|
Node *scene = editor_data.get_edited_scene_root(p_tab);
|
|
if (!scene) {
|
|
_discard_changes();
|
|
return;
|
|
}
|
|
|
|
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;
|
|
if (unsaved) {
|
|
save_confirmation->get_ok()->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_minsize();
|
|
} else {
|
|
_discard_changes();
|
|
}
|
|
|
|
save_layout();
|
|
_update_scene_tabs();
|
|
}
|
|
|
|
void EditorNode::_scene_tab_hover(int p_tab) {
|
|
if (!bool(EDITOR_GET("interface/scene_tabs/show_thumbnail_on_hover"))) {
|
|
return;
|
|
}
|
|
int current_tab = scene_tabs->get_current_tab();
|
|
|
|
if (p_tab == current_tab || p_tab < 0) {
|
|
tab_preview_panel->hide();
|
|
} else {
|
|
String path = editor_data.get_scene_path(p_tab);
|
|
if (path != String()) {
|
|
EditorResourcePreview::get_singleton()->queue_resource_preview(path, this, "_thumbnail_done", p_tab);
|
|
}
|
|
}
|
|
}
|
|
|
|
void EditorNode::_scene_tab_exit() {
|
|
tab_preview_panel->hide();
|
|
}
|
|
|
|
void EditorNode::_scene_tab_input(const Ref<InputEvent> &p_input) {
|
|
Ref<InputEventMouseButton> mb = p_input;
|
|
|
|
if (mb.is_valid()) {
|
|
|
|
if (scene_tabs->get_hovered_tab() >= 0) {
|
|
if (mb->get_button_index() == BUTTON_MIDDLE && mb->is_pressed()) {
|
|
_scene_tab_closed(scene_tabs->get_hovered_tab());
|
|
}
|
|
} else {
|
|
if ((mb->get_button_index() == BUTTON_LEFT && mb->is_doubleclick()) || (mb->get_button_index() == BUTTON_MIDDLE && mb->is_pressed())) {
|
|
_menu_option_confirm(FILE_NEW_SCENE, true);
|
|
}
|
|
}
|
|
if (mb->get_button_index() == BUTTON_RIGHT && mb->is_pressed()) {
|
|
|
|
// context menu
|
|
scene_tabs_context_menu->clear();
|
|
scene_tabs_context_menu->set_size(Size2(1, 1));
|
|
|
|
scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/new_scene"), FILE_NEW_SCENE);
|
|
if (scene_tabs->get_hovered_tab() >= 0) {
|
|
scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/save_scene"), FILE_SAVE_SCENE);
|
|
scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/save_scene_as"), FILE_SAVE_AS_SCENE);
|
|
}
|
|
scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/save_all_scenes"), FILE_SAVE_ALL_SCENES);
|
|
if (scene_tabs->get_hovered_tab() >= 0) {
|
|
scene_tabs_context_menu->add_separator();
|
|
scene_tabs_context_menu->add_item(TTR("Show in FileSystem"), FILE_SHOW_IN_FILESYSTEM);
|
|
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");
|
|
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");
|
|
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()) {
|
|
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);
|
|
scene_tabs_context_menu->add_item(TTR("Close Tabs to the Right"), FILE_CLOSE_RIGHT);
|
|
scene_tabs_context_menu->add_item(TTR("Close All Tabs"), FILE_CLOSE_ALL);
|
|
}
|
|
scene_tabs_context_menu->set_position(mb->get_global_position());
|
|
scene_tabs_context_menu->popup();
|
|
}
|
|
}
|
|
}
|
|
|
|
void EditorNode::_reposition_active_tab(int idx_to) {
|
|
editor_data.move_edited_scene_to_index(idx_to);
|
|
_update_scene_tabs();
|
|
}
|
|
|
|
void EditorNode::_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_udata) {
|
|
int p_tab = p_udata.operator signed int();
|
|
if (p_preview.is_valid()) {
|
|
Rect2 rect = scene_tabs->get_tab_rect(p_tab);
|
|
rect.position += scene_tabs->get_global_position();
|
|
tab_preview->set_texture(p_preview);
|
|
tab_preview_panel->set_position(rect.position + Vector2(0, rect.size.height));
|
|
tab_preview_panel->show();
|
|
}
|
|
}
|
|
|
|
void EditorNode::_scene_tab_changed(int p_tab) {
|
|
tab_preview_panel->hide();
|
|
|
|
bool unsaved = (saved_version != editor_data.get_undo_redo().get_version());
|
|
|
|
if (p_tab == editor_data.get_edited_scene())
|
|
return; //pointless
|
|
|
|
uint64_t next_scene_version = editor_data.get_scene_version(p_tab);
|
|
|
|
editor_data.get_undo_redo().create_action(TTR("Switch Scene Tab"));
|
|
editor_data.get_undo_redo().add_do_method(this, "set_current_version", unsaved ? saved_version : 0);
|
|
editor_data.get_undo_redo().add_do_method(this, "set_current_scene", p_tab);
|
|
editor_data.get_undo_redo().add_do_method(this, "set_current_version", next_scene_version == 0 ? editor_data.get_undo_redo().get_version() + 1 : next_scene_version);
|
|
|
|
editor_data.get_undo_redo().add_undo_method(this, "set_current_version", next_scene_version);
|
|
editor_data.get_undo_redo().add_undo_method(this, "set_current_scene", editor_data.get_edited_scene());
|
|
editor_data.get_undo_redo().add_undo_method(this, "set_current_version", saved_version);
|
|
editor_data.get_undo_redo().commit_action();
|
|
}
|
|
|
|
ToolButton *EditorNode::add_bottom_panel_item(String p_text, Control *p_item) {
|
|
|
|
ToolButton *tb = memnew(ToolButton);
|
|
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);
|
|
tb->set_focus_mode(Control::FOCUS_NONE);
|
|
bottom_panel_vb->add_child(p_item);
|
|
bottom_panel_hb->raise();
|
|
bottom_panel_hb_editors->add_child(tb);
|
|
p_item->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
|
p_item->hide();
|
|
BottomPanelItem bpi;
|
|
bpi.button = tb;
|
|
bpi.control = p_item;
|
|
bpi.name = p_text;
|
|
bottom_panel_items.push_back(bpi);
|
|
|
|
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()) {
|
|
_bottom_panel_switch(false, i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void EditorNode::make_bottom_panel_item_visible(Control *p_item) {
|
|
|
|
for (int i = 0; i < bottom_panel_items.size(); i++) {
|
|
|
|
if (bottom_panel_items[i].control == p_item) {
|
|
_bottom_panel_switch(true, i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void EditorNode::raise_bottom_panel_item(Control *p_item) {
|
|
|
|
for (int i = 0; i < bottom_panel_items.size(); i++) {
|
|
|
|
if (bottom_panel_items[i].control == p_item) {
|
|
bottom_panel_items[i].button->raise();
|
|
SWAP(bottom_panel_items.write[i], bottom_panel_items.write[bottom_panel_items.size() - 1]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < bottom_panel_items.size(); i++) {
|
|
bottom_panel_items[i].button->disconnect("toggled", callable_mp(this, &EditorNode::_bottom_panel_switch));
|
|
bottom_panel_items[i].button->connect("toggled", callable_mp(this, &EditorNode::_bottom_panel_switch), varray(i));
|
|
}
|
|
}
|
|
|
|
void EditorNode::remove_bottom_panel_item(Control *p_item) {
|
|
|
|
for (int i = 0; i < bottom_panel_items.size(); i++) {
|
|
|
|
if (bottom_panel_items[i].control == p_item) {
|
|
if (p_item->is_visible_in_tree()) {
|
|
_bottom_panel_switch(false, i);
|
|
}
|
|
bottom_panel_vb->remove_child(bottom_panel_items[i].control);
|
|
bottom_panel_hb_editors->remove_child(bottom_panel_items[i].button);
|
|
memdelete(bottom_panel_items[i].button);
|
|
bottom_panel_items.remove(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < bottom_panel_items.size(); i++) {
|
|
bottom_panel_items[i].button->disconnect("toggled", callable_mp(this, &EditorNode::_bottom_panel_switch));
|
|
bottom_panel_items[i].button->connect("toggled", callable_mp(this, &EditorNode::_bottom_panel_switch), varray(i));
|
|
}
|
|
}
|
|
|
|
void EditorNode::_bottom_panel_switch(bool p_enable, int p_idx) {
|
|
|
|
ERR_FAIL_INDEX(p_idx, bottom_panel_items.size());
|
|
|
|
if (bottom_panel_items[p_idx].control->is_visible() == p_enable) {
|
|
return;
|
|
}
|
|
|
|
if (p_enable) {
|
|
for (int i = 0; i < bottom_panel_items.size(); i++) {
|
|
|
|
bottom_panel_items[i].button->set_pressed(i == p_idx);
|
|
bottom_panel_items[i].control->set_visible(i == p_idx);
|
|
}
|
|
if (EditorDebuggerNode::get_singleton() == bottom_panel_items[p_idx].control) { // this is the debug panel which uses tabs, so the top section should be smaller
|
|
bottom_panel->add_style_override("panel", gui_base->get_stylebox("BottomPanelDebuggerOverride", "EditorStyles"));
|
|
} else {
|
|
bottom_panel->add_style_override("panel", gui_base->get_stylebox("panel", "TabContainer"));
|
|
}
|
|
center_split->set_dragger_visibility(SplitContainer::DRAGGER_VISIBLE);
|
|
center_split->set_collapsed(false);
|
|
if (bottom_panel_raise->is_pressed()) {
|
|
top_split->hide();
|
|
}
|
|
bottom_panel_raise->show();
|
|
|
|
} else {
|
|
bottom_panel->add_style_override("panel", gui_base->get_stylebox("panel", "TabContainer"));
|
|
bottom_panel_items[p_idx].button->set_pressed(false);
|
|
bottom_panel_items[p_idx].control->set_visible(false);
|
|
center_split->set_dragger_visibility(SplitContainer::DRAGGER_HIDDEN);
|
|
center_split->set_collapsed(true);
|
|
bottom_panel_raise->hide();
|
|
if (bottom_panel_raise->is_pressed()) {
|
|
top_split->show();
|
|
}
|
|
}
|
|
}
|
|
|
|
void EditorNode::set_docks_visible(bool p_show) {
|
|
docks_visible = p_show;
|
|
_update_dock_slots_visibility();
|
|
}
|
|
|
|
bool EditorNode::get_docks_visible() const {
|
|
return docks_visible;
|
|
}
|
|
|
|
void EditorNode::_toggle_distraction_free_mode() {
|
|
|
|
if (EditorSettings::get_singleton()->get("interface/editor/separate_distraction_mode")) {
|
|
int screen = -1;
|
|
for (int i = 0; i < editor_table.size(); i++) {
|
|
if (editor_plugin_screen == editor_table[i]) {
|
|
screen = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (screen == EDITOR_SCRIPT) {
|
|
script_distraction = !script_distraction;
|
|
set_distraction_free_mode(script_distraction);
|
|
} else {
|
|
scene_distraction = !scene_distraction;
|
|
set_distraction_free_mode(scene_distraction);
|
|
}
|
|
} else {
|
|
set_distraction_free_mode(distraction_free->is_pressed());
|
|
}
|
|
}
|
|
|
|
void EditorNode::set_distraction_free_mode(bool p_enter) {
|
|
|
|
distraction_free->set_pressed(p_enter);
|
|
|
|
if (p_enter) {
|
|
if (docks_visible) {
|
|
set_docks_visible(false);
|
|
}
|
|
} else {
|
|
set_docks_visible(true);
|
|
}
|
|
}
|
|
|
|
bool EditorNode::get_distraction_free_mode() const {
|
|
return distraction_free->is_pressed();
|
|
}
|
|
|
|
void EditorNode::add_control_to_dock(DockSlot p_slot, Control *p_control) {
|
|
ERR_FAIL_INDEX(p_slot, DOCK_SLOT_MAX);
|
|
dock_slot[p_slot]->add_child(p_control);
|
|
_update_dock_slots_visibility();
|
|
}
|
|
|
|
void EditorNode::remove_control_from_dock(Control *p_control) {
|
|
|
|
Control *dock = NULL;
|
|
for (int i = 0; i < DOCK_SLOT_MAX; i++) {
|
|
if (p_control->get_parent() == dock_slot[i]) {
|
|
dock = dock_slot[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
ERR_FAIL_COND_MSG(!dock, "Control was not in dock.");
|
|
|
|
dock->remove_child(p_control);
|
|
_update_dock_slots_visibility();
|
|
}
|
|
|
|
Variant EditorNode::drag_resource(const Ref<Resource> &p_res, Control *p_from) {
|
|
|
|
Control *drag_control = memnew(Control);
|
|
TextureRect *drag_preview = memnew(TextureRect);
|
|
Label *label = memnew(Label);
|
|
|
|
Ref<Texture2D> preview;
|
|
|
|
{
|
|
//todo make proper previews
|
|
Ref<ImageTexture> pic = gui_base->get_icon("FileBigThumb", "EditorIcons");
|
|
Ref<Image> img = pic->get_data();
|
|
img = img->duplicate();
|
|
img->resize(48, 48); //meh
|
|
Ref<ImageTexture> resized_pic = Ref<ImageTexture>(memnew(ImageTexture));
|
|
resized_pic->create_from_image(img);
|
|
preview = resized_pic;
|
|
}
|
|
|
|
drag_preview->set_texture(preview);
|
|
drag_control->add_child(drag_preview);
|
|
if (p_res->get_path().is_resource_file()) {
|
|
label->set_text(p_res->get_path().get_file());
|
|
} else if (p_res->get_name() != "") {
|
|
label->set_text(p_res->get_name());
|
|
} else {
|
|
label->set_text(p_res->get_class());
|
|
}
|
|
|
|
drag_control->add_child(label);
|
|
|
|
p_from->set_drag_preview(drag_control); //wait until it enters scene
|
|
|
|
label->set_position(Point2((preview->get_width() - label->get_minimum_size().width) / 2, preview->get_height()));
|
|
|
|
Dictionary drag_data;
|
|
drag_data["type"] = "resource";
|
|
drag_data["resource"] = p_res;
|
|
drag_data["from"] = p_from;
|
|
|
|
return drag_data;
|
|
}
|
|
|
|
Variant EditorNode::drag_files_and_dirs(const Vector<String> &p_paths, Control *p_from) {
|
|
bool has_folder = false;
|
|
bool has_file = false;
|
|
for (int i = 0; i < p_paths.size(); i++) {
|
|
bool is_folder = p_paths[i].ends_with("/");
|
|
has_folder |= is_folder;
|
|
has_file |= !is_folder;
|
|
}
|
|
|
|
int max_rows = 6;
|
|
int num_rows = p_paths.size() > max_rows ? max_rows - 1 : p_paths.size(); // Don't waste a row to say "1 more file" - list it instead.
|
|
VBoxContainer *vbox = memnew(VBoxContainer);
|
|
for (int i = 0; i < num_rows; i++) {
|
|
HBoxContainer *hbox = memnew(HBoxContainer);
|
|
TextureRect *icon = memnew(TextureRect);
|
|
Label *label = memnew(Label);
|
|
|
|
if (p_paths[i].ends_with("/")) {
|
|
label->set_text(p_paths[i].substr(0, p_paths[i].length() - 1).get_file());
|
|
icon->set_texture(gui_base->get_icon("Folder", "EditorIcons"));
|
|
} else {
|
|
label->set_text(p_paths[i].get_file());
|
|
icon->set_texture(gui_base->get_icon("File", "EditorIcons"));
|
|
}
|
|
icon->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
|
|
icon->set_size(Size2(16, 16));
|
|
hbox->add_child(icon);
|
|
hbox->add_child(label);
|
|
vbox->add_child(hbox);
|
|
}
|
|
|
|
if (p_paths.size() > num_rows) {
|
|
Label *label = memnew(Label);
|
|
if (has_file && has_folder) {
|
|
label->set_text(vformat(TTR("%d more files or folders"), p_paths.size() - num_rows));
|
|
} else if (has_folder) {
|
|
label->set_text(vformat(TTR("%d more folders"), p_paths.size() - num_rows));
|
|
} else {
|
|
label->set_text(vformat(TTR("%d more files"), p_paths.size() - num_rows));
|
|
}
|
|
vbox->add_child(label);
|
|
}
|
|
p_from->set_drag_preview(vbox); //wait until it enters scene
|
|
|
|
Dictionary drag_data;
|
|
drag_data["type"] = has_folder ? "files_and_dirs" : "files";
|
|
drag_data["files"] = p_paths;
|
|
drag_data["from"] = p_from;
|
|
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);
|
|
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);
|
|
}
|
|
|
|
void EditorNode::add_tool_submenu_item(const String &p_name, PopupMenu *p_submenu) {
|
|
ERR_FAIL_NULL(p_submenu);
|
|
ERR_FAIL_COND(p_submenu->get_parent() != NULL);
|
|
|
|
tool_menu->add_child(p_submenu);
|
|
tool_menu->add_submenu_item(p_name, p_submenu->get_name(), TOOLS_CUSTOM);
|
|
}
|
|
|
|
void EditorNode::remove_tool_menu_item(const String &p_name) {
|
|
for (int i = 0; i < tool_menu->get_item_count(); i++) {
|
|
if (tool_menu->get_item_id(i) != TOOLS_CUSTOM)
|
|
continue;
|
|
|
|
if (tool_menu->get_item_text(i) == p_name) {
|
|
if (tool_menu->get_item_submenu(i) != "") {
|
|
Node *n = tool_menu->get_node(tool_menu->get_item_submenu(i));
|
|
tool_menu->remove_child(n);
|
|
memdelete(n);
|
|
}
|
|
tool_menu->remove_item(i);
|
|
tool_menu->set_as_minsize();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void EditorNode::_global_menu_action(const Variant &p_id, const Variant &p_meta) {
|
|
|
|
int id = (int)p_id;
|
|
if (id == GLOBAL_NEW_WINDOW) {
|
|
if (OS::get_singleton()->get_main_loop()) {
|
|
List<String> args;
|
|
args.push_back("-e");
|
|
String exec = OS::get_singleton()->get_executable_path();
|
|
|
|
OS::ProcessID pid = 0;
|
|
OS::get_singleton()->execute(exec, args, false, &pid);
|
|
}
|
|
} else if (id == GLOBAL_SCENE) {
|
|
int idx = (int)p_meta;
|
|
scene_tabs->set_current_tab(idx);
|
|
}
|
|
}
|
|
|
|
void EditorNode::_dropped_files(const Vector<String> &p_files, int p_screen) {
|
|
|
|
String to_path = ProjectSettings::get_singleton()->globalize_path(get_filesystem_dock()->get_selected_path());
|
|
|
|
_add_dropped_files_recursive(p_files, to_path);
|
|
|
|
EditorFileSystem::get_singleton()->scan_changes();
|
|
}
|
|
|
|
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];
|
|
String to = to_path.plus_file(from.get_file());
|
|
|
|
if (dir->dir_exists(from)) {
|
|
|
|
Vector<String> sub_files;
|
|
|
|
DirAccessRef sub_dir = DirAccess::open(from);
|
|
sub_dir->list_dir_begin();
|
|
|
|
String next_file = sub_dir->get_next();
|
|
while (next_file != "") {
|
|
if (next_file == "." || next_file == "..") {
|
|
next_file = sub_dir->get_next();
|
|
continue;
|
|
}
|
|
|
|
sub_files.push_back(from.plus_file(next_file));
|
|
next_file = sub_dir->get_next();
|
|
}
|
|
|
|
if (!sub_files.empty()) {
|
|
dir->make_dir(to);
|
|
_add_dropped_files_recursive(sub_files, to);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (!ResourceFormatImporter::get_singleton()->can_be_imported(from) && (just_copy.find(from.get_extension().to_lower()) == -1)) {
|
|
continue;
|
|
}
|
|
dir->copy(from, to);
|
|
}
|
|
}
|
|
|
|
void EditorNode::_file_access_close_error_notify(const String &p_str) {
|
|
|
|
add_io_error("Unable to write to file '" + p_str + "', file in use, locked or lacking permissions.");
|
|
}
|
|
|
|
void EditorNode::reload_scene(const String &p_path) {
|
|
|
|
//first of all, reload internal textures, materials, meshes, etc. as they might have changed on disk
|
|
|
|
List<Ref<Resource>> cached;
|
|
ResourceCache::get_cached_resources(&cached);
|
|
List<Ref<Resource>> to_clear; //clear internal resources from previous scene from being used
|
|
for (List<Ref<Resource>>::Element *E = cached.front(); E; E = E->next()) {
|
|
|
|
if (E->get()->get_path().begins_with(p_path + "::")) { //subresources of existing scene
|
|
to_clear.push_back(E->get());
|
|
}
|
|
}
|
|
|
|
//so reload reloads everything, clear subresources of previous scene
|
|
while (to_clear.front()) {
|
|
to_clear.front()->get()->set_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) {
|
|
scene_idx = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
int current_tab = editor_data.get_edited_scene();
|
|
|
|
if (scene_idx == -1) {
|
|
if (get_edited_scene()) {
|
|
//scene is not open, so at it might be instanced. We'll refresh the whole scene later.
|
|
editor_data.get_undo_redo().clear_history();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (current_tab == scene_idx) {
|
|
editor_data.apply_changes_in_editors();
|
|
_set_scene_metadata(p_path);
|
|
}
|
|
|
|
//remove scene
|
|
_remove_scene(scene_idx, false);
|
|
|
|
//reload scene
|
|
load_scene(p_path, true, false, true, true);
|
|
|
|
//adjust index so tab is back a the previous position
|
|
editor_data.move_edited_scene_to_index(scene_idx);
|
|
get_undo_redo()->clear_history();
|
|
|
|
//recover the tab
|
|
scene_tabs->set_current_tab(current_tab);
|
|
}
|
|
|
|
int EditorNode::plugin_init_callback_count = 0;
|
|
|
|
void EditorNode::add_plugin_init_callback(EditorPluginInitializeCallback p_callback) {
|
|
|
|
ERR_FAIL_COND(plugin_init_callback_count == MAX_INIT_CALLBACKS);
|
|
|
|
plugin_init_callbacks[plugin_init_callback_count++] = p_callback;
|
|
}
|
|
|
|
EditorPluginInitializeCallback EditorNode::plugin_init_callbacks[EditorNode::MAX_INIT_CALLBACKS];
|
|
|
|
int EditorNode::build_callback_count = 0;
|
|
|
|
void EditorNode::add_build_callback(EditorBuildCallback p_callback) {
|
|
|
|
ERR_FAIL_COND(build_callback_count == MAX_INIT_CALLBACKS);
|
|
|
|
build_callbacks[build_callback_count++] = p_callback;
|
|
}
|
|
|
|
EditorBuildCallback EditorNode::build_callbacks[EditorNode::MAX_BUILD_CALLBACKS];
|
|
|
|
bool EditorNode::call_build() {
|
|
|
|
bool builds_successful = true;
|
|
|
|
for (int i = 0; i < build_callback_count && builds_successful; i++) {
|
|
if (!build_callbacks[i]()) {
|
|
ERR_PRINT("A Godot Engine build callback failed.");
|
|
builds_successful = false;
|
|
}
|
|
}
|
|
|
|
if (builds_successful && !editor_data.call_build()) {
|
|
ERR_PRINT("An EditorPlugin build callback failed.");
|
|
builds_successful = false;
|
|
}
|
|
|
|
return builds_successful;
|
|
}
|
|
|
|
void EditorNode::_inherit_imported(const String &p_action) {
|
|
|
|
open_imported->hide();
|
|
load_scene(open_import_request, true, true);
|
|
}
|
|
|
|
void EditorNode::_open_imported() {
|
|
|
|
load_scene(open_import_request, true, false, true, true);
|
|
}
|
|
|
|
void EditorNode::dim_editor(bool p_dimming, bool p_force_dim) {
|
|
// Dimming can be forced regardless of the editor setting, which is useful when quitting the editor.
|
|
if ((p_force_dim || EditorSettings::get_singleton()->get("interface/editor/dim_editor_on_dialog_popup")) && p_dimming) {
|
|
dimmed = true;
|
|
gui_base->set_modulate(Color(0.5, 0.5, 0.5));
|
|
} else {
|
|
dimmed = false;
|
|
gui_base->set_modulate(Color(1, 1, 1));
|
|
}
|
|
}
|
|
|
|
bool EditorNode::is_editor_dimmed() const {
|
|
return dimmed;
|
|
}
|
|
|
|
void EditorNode::open_export_template_manager() {
|
|
|
|
export_template_manager->popup_manager();
|
|
}
|
|
|
|
void EditorNode::add_resource_conversion_plugin(const Ref<EditorResourceConversionPlugin> &p_plugin) {
|
|
resource_conversion_plugins.push_back(p_plugin);
|
|
}
|
|
|
|
void EditorNode::remove_resource_conversion_plugin(const Ref<EditorResourceConversionPlugin> &p_plugin) {
|
|
resource_conversion_plugins.erase(p_plugin);
|
|
}
|
|
|
|
Vector<Ref<EditorResourceConversionPlugin>> EditorNode::find_resource_conversion_plugin(const Ref<Resource> &p_for_resource) {
|
|
|
|
Vector<Ref<EditorResourceConversionPlugin>> ret;
|
|
|
|
for (int i = 0; i < resource_conversion_plugins.size(); i++) {
|
|
if (resource_conversion_plugins[i].is_valid() && resource_conversion_plugins[i]->handles(p_for_resource)) {
|
|
ret.push_back(resource_conversion_plugins[i]);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void EditorNode::_bottom_panel_raise_toggled(bool p_pressed) {
|
|
|
|
top_split->set_visible(!p_pressed);
|
|
}
|
|
|
|
void EditorNode::_update_video_driver_color() {
|
|
|
|
// TODO: Probably should de-hardcode this and add to editor settings.
|
|
if (video_driver->get_text() == "GLES2") {
|
|
video_driver->add_color_override("font_color", Color::hex(0x5586a4ff));
|
|
} else if (video_driver->get_text() == "Vulkan") {
|
|
video_driver->add_color_override("font_color", theme_base->get_color("vulkan_color", "Editor"));
|
|
}
|
|
}
|
|
|
|
void EditorNode::_video_driver_selected(int p_which) {
|
|
|
|
String driver = video_driver->get_item_metadata(p_which);
|
|
|
|
String current = OS::get_singleton()->get_video_driver_name(OS::get_singleton()->get_current_video_driver());
|
|
|
|
if (driver == current) {
|
|
return;
|
|
}
|
|
|
|
video_driver_request = driver;
|
|
video_restart_dialog->popup_centered_minsize();
|
|
video_driver->select(video_driver_current);
|
|
_update_video_driver_color();
|
|
}
|
|
|
|
void EditorNode::_resource_saved(RES p_resource, const String &p_path) {
|
|
if (EditorFileSystem::get_singleton()) {
|
|
EditorFileSystem::get_singleton()->update_file(p_path);
|
|
}
|
|
|
|
singleton->editor_folding.save_resource_folding(p_resource, p_path);
|
|
}
|
|
|
|
void EditorNode::_resource_loaded(RES p_resource, const String &p_path) {
|
|
|
|
singleton->editor_folding.load_resource_folding(p_resource, p_path);
|
|
}
|
|
|
|
void EditorNode::_feature_profile_changed() {
|
|
|
|
Ref<EditorFeatureProfile> profile = feature_profile_manager->get_current_profile();
|
|
TabContainer *import_tabs = cast_to<TabContainer>(import_dock->get_parent());
|
|
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));
|
|
|
|
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));
|
|
if (StreamPeerSSL::is_available())
|
|
main_editor_buttons[EDITOR_ASSETLIB]->set_visible(!profile->is_feature_disabled(EditorFeatureProfile::FEATURE_ASSET_LIB));
|
|
if ((profile->is_feature_disabled(EditorFeatureProfile::FEATURE_3D) && singleton->main_editor_buttons[EDITOR_3D]->is_pressed()) ||
|
|
(profile->is_feature_disabled(EditorFeatureProfile::FEATURE_SCRIPT) && singleton->main_editor_buttons[EDITOR_SCRIPT]->is_pressed()) ||
|
|
(StreamPeerSSL::is_available() && profile->is_feature_disabled(EditorFeatureProfile::FEATURE_ASSET_LIB) && singleton->main_editor_buttons[EDITOR_ASSETLIB]->is_pressed())) {
|
|
_editor_select(EDITOR_2D);
|
|
}
|
|
} else {
|
|
|
|
import_tabs->set_tab_hidden(import_dock->get_index(), false);
|
|
node_tabs->set_tab_hidden(node_dock->get_index(), false);
|
|
fs_tabs->set_tab_hidden(filesystem_dock->get_index(), false);
|
|
import_dock->set_visible(true);
|
|
node_dock->set_visible(true);
|
|
filesystem_dock->set_visible(true);
|
|
main_editor_buttons[EDITOR_3D]->set_visible(true);
|
|
main_editor_buttons[EDITOR_SCRIPT]->set_visible(true);
|
|
if (StreamPeerSSL::is_available())
|
|
main_editor_buttons[EDITOR_ASSETLIB]->set_visible(true);
|
|
}
|
|
|
|
_update_dock_slots_visibility();
|
|
}
|
|
|
|
void EditorNode::_bind_methods() {
|
|
|
|
ClassDB::bind_method("_editor_select", &EditorNode::_editor_select);
|
|
ClassDB::bind_method("_node_renamed", &EditorNode::_node_renamed);
|
|
ClassDB::bind_method("edit_node", &EditorNode::edit_node);
|
|
ClassDB::bind_method("_unhandled_input", &EditorNode::_unhandled_input);
|
|
|
|
ClassDB::bind_method(D_METHOD("push_item", "object", "property", "inspector_only"), &EditorNode::push_item, DEFVAL(""), DEFVAL(false));
|
|
|
|
ClassDB::bind_method("_get_scene_metadata", &EditorNode::_get_scene_metadata);
|
|
ClassDB::bind_method("set_edited_scene", &EditorNode::set_edited_scene);
|
|
ClassDB::bind_method("open_request", &EditorNode::open_request);
|
|
ClassDB::bind_method("_close_messages", &EditorNode::_close_messages);
|
|
ClassDB::bind_method("_show_messages", &EditorNode::_show_messages);
|
|
|
|
ClassDB::bind_method("stop_child_process", &EditorNode::stop_child_process);
|
|
|
|
ClassDB::bind_method("get_script_create_dialog", &EditorNode::get_script_create_dialog);
|
|
|
|
ClassDB::bind_method("set_current_scene", &EditorNode::set_current_scene);
|
|
ClassDB::bind_method("set_current_version", &EditorNode::set_current_version);
|
|
ClassDB::bind_method("_thumbnail_done", &EditorNode::_thumbnail_done);
|
|
ClassDB::bind_method("_set_main_scene_state", &EditorNode::_set_main_scene_state);
|
|
ClassDB::bind_method("_update_recent_scenes", &EditorNode::_update_recent_scenes);
|
|
|
|
ClassDB::bind_method("_clear_undo_history", &EditorNode::_clear_undo_history);
|
|
ClassDB::bind_method("edit_item_resource", &EditorNode::edit_item_resource);
|
|
|
|
ClassDB::bind_method(D_METHOD("get_gui_base"), &EditorNode::get_gui_base);
|
|
|
|
ClassDB::bind_method(D_METHOD("_on_plugin_ready"), &EditorNode::_on_plugin_ready); // Still used by some connect_compat.
|
|
|
|
ClassDB::bind_method("_screenshot", &EditorNode::_screenshot);
|
|
ClassDB::bind_method("_save_screenshot", &EditorNode::_save_screenshot);
|
|
|
|
ADD_SIGNAL(MethodInfo("play_pressed"));
|
|
ADD_SIGNAL(MethodInfo("pause_pressed"));
|
|
ADD_SIGNAL(MethodInfo("stop_pressed"));
|
|
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")));
|
|
}
|
|
|
|
static Node *_resource_get_edited_scene() {
|
|
|
|
return EditorNode::get_singleton()->get_edited_scene();
|
|
}
|
|
|
|
void EditorNode::_print_handler(void *p_this, const String &p_string, bool p_error) {
|
|
EditorNode *en = (EditorNode *)p_this;
|
|
en->log->add_message(p_string, p_error ? EditorLog::MSG_TYPE_ERROR : EditorLog::MSG_TYPE_STD);
|
|
}
|
|
|
|
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, NULL, &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;
|
|
}
|
|
|
|
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_outputs->clear();
|
|
execute_outputs->set_scroll_follow(true);
|
|
execute_output_dialog->popup_centered_ratio();
|
|
|
|
ExecuteThreadArgs eta;
|
|
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);
|
|
|
|
ERR_FAIL_COND_V(!eta.execute_output_thread, 0);
|
|
|
|
while (!eta.done) {
|
|
{
|
|
MutexLock lock(eta.execute_output_mutex);
|
|
if (prev_len != eta.output.length()) {
|
|
String to_add = eta.output.substr(prev_len, eta.output.length());
|
|
prev_len = eta.output.length();
|
|
execute_outputs->add_text(to_add);
|
|
Main::iteration();
|
|
}
|
|
}
|
|
OS::get_singleton()->delay_usec(1000);
|
|
}
|
|
|
|
Thread::wait_to_finish(eta.execute_output_thread);
|
|
memdelete(eta.execute_output_thread);
|
|
execute_outputs->add_text("\nExit Code: " + itos(eta.exitcode));
|
|
|
|
if (p_close_on_errors && eta.exitcode != 0) {
|
|
execute_output_dialog->hide();
|
|
}
|
|
if (p_close_on_ok && eta.exitcode == 0) {
|
|
execute_output_dialog->hide();
|
|
}
|
|
|
|
execute_output_dialog->get_ok()->set_disabled(false);
|
|
|
|
return eta.exitcode;
|
|
}
|
|
|
|
EditorNode::EditorNode() {
|
|
|
|
Input::get_singleton()->set_use_accumulated_input(true);
|
|
Resource::_get_local_scene_func = _resource_get_edited_scene;
|
|
|
|
VisualServer::get_singleton()->set_debug_generate_wireframes(true);
|
|
|
|
NavigationServer::get_singleton()->set_active(false); // no nav by default if editor
|
|
|
|
PhysicsServer::get_singleton()->set_active(false); // no physics by default if editor
|
|
Physics2DServer::get_singleton()->set_active(false); // no physics by default if editor
|
|
ScriptServer::set_scripting_enabled(false); // no scripting by default if editor
|
|
|
|
EditorHelp::generate_doc(); //before any editor classes are created
|
|
SceneState::set_disable_placeholders(true);
|
|
ResourceLoader::clear_translation_remaps(); //no remaps using during editor
|
|
ResourceLoader::clear_path_remaps();
|
|
|
|
InputDefault *id = Object::cast_to<InputDefault>(Input::get_singleton());
|
|
|
|
if (id) {
|
|
|
|
if (!OS::get_singleton()->has_touchscreen_ui_hint() && Input::get_singleton()) {
|
|
//only if no touchscreen ui hint, set emulation
|
|
id->set_emulate_touch_from_mouse(false); //just disable just in case
|
|
}
|
|
id->set_custom_mouse_cursor(RES());
|
|
}
|
|
|
|
singleton = this;
|
|
exiting = false;
|
|
dimmed = false;
|
|
last_checked_version = 0;
|
|
changing_scene = false;
|
|
_initializing_addons = false;
|
|
docks_visible = true;
|
|
restoring_scenes = false;
|
|
cmdline_export_mode = false;
|
|
scene_distraction = false;
|
|
script_distraction = false;
|
|
|
|
TranslationServer::get_singleton()->set_enabled(false);
|
|
// load settings
|
|
if (!EditorSettings::get_singleton())
|
|
EditorSettings::create();
|
|
|
|
FileAccess::set_backup_save(EDITOR_GET("filesystem/on_save/safe_save_on_backup_then_rename"));
|
|
|
|
{
|
|
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 = OS::get_singleton()->get_current_screen();
|
|
editor_set_scale(OS::get_singleton()->get_screen_dpi(screen) >= 192 && OS::get_singleton()->get_screen_size(screen).x > 2000 ? 2.0 : 1.0);
|
|
} break;
|
|
|
|
case 1: {
|
|
editor_set_scale(0.75);
|
|
} break;
|
|
|
|
case 2: {
|
|
editor_set_scale(1.0);
|
|
} break;
|
|
|
|
case 3: {
|
|
editor_set_scale(1.25);
|
|
} break;
|
|
|
|
case 4: {
|
|
editor_set_scale(1.5);
|
|
} break;
|
|
|
|
case 5: {
|
|
editor_set_scale(1.75);
|
|
} break;
|
|
|
|
case 6: {
|
|
editor_set_scale(2.0);
|
|
} break;
|
|
|
|
default: {
|
|
editor_set_scale(custom_display_scale);
|
|
} break;
|
|
}
|
|
}
|
|
|
|
// Define a minimum window size to prevent UI elements from overlapping or being cut off
|
|
OS::get_singleton()->set_min_window_size(Size2(1024, 600) * EDSCALE);
|
|
|
|
ResourceLoader::set_abort_on_missing_resources(false);
|
|
FileDialog::set_default_show_hidden_files(EditorSettings::get_singleton()->get("filesystem/file_dialog/show_hidden_files"));
|
|
EditorFileDialog::set_default_show_hidden_files(EditorSettings::get_singleton()->get("filesystem/file_dialog/show_hidden_files"));
|
|
EditorFileDialog::set_default_display_mode((EditorFileDialog::DisplayMode)EditorSettings::get_singleton()->get("filesystem/file_dialog/display_mode").operator int());
|
|
ResourceLoader::set_error_notify_func(this, _load_error_notify);
|
|
ResourceLoader::set_dependency_error_notify_func(this, _dependency_error_report);
|
|
|
|
{ //register importers at the beginning, so dialogs are created with the right extensions
|
|
Ref<ResourceImporterTexture> import_texture;
|
|
import_texture.instance();
|
|
ResourceFormatImporter::get_singleton()->add_importer(import_texture);
|
|
|
|
/* Ref<ResourceImporterLayeredTexture> import_cubemap;
|
|
import_cubemap.instance();
|
|
import_cubemap->set_mode(ResourceImporterLayeredTexture::MODE_CUBEMAP);
|
|
ResourceFormatImporter::get_singleton()->add_importer(import_cubemap);
|
|
|
|
Ref<ResourceImporterLayeredTexture> import_array;
|
|
import_array.instance();
|
|
import_array->set_mode(ResourceImporterLayeredTexture::MODE_2D_ARRAY);
|
|
ResourceFormatImporter::get_singleton()->add_importer(import_array);
|
|
|
|
Ref<ResourceImporterLayeredTexture> import_cubemap_array;
|
|
import_cubemap_array.instance();
|
|
import_cubemap_array->set_mode(ResourceImporterLayeredTexture::MODE_CUBEMAP_ARRAY);
|
|
ResourceFormatImporter::get_singleton()->add_importer(import_cubemap_array);
|
|
*/
|
|
Ref<ResourceImporterImage> import_image;
|
|
import_image.instance();
|
|
ResourceFormatImporter::get_singleton()->add_importer(import_image);
|
|
|
|
Ref<ResourceImporterTextureAtlas> import_texture_atlas;
|
|
import_texture_atlas.instance();
|
|
ResourceFormatImporter::get_singleton()->add_importer(import_texture_atlas);
|
|
|
|
Ref<ResourceImporterCSVTranslation> import_csv_translation;
|
|
import_csv_translation.instance();
|
|
ResourceFormatImporter::get_singleton()->add_importer(import_csv_translation);
|
|
|
|
Ref<ResourceImporterCSV> import_csv;
|
|
import_csv.instance();
|
|
ResourceFormatImporter::get_singleton()->add_importer(import_csv);
|
|
|
|
Ref<ResourceImporterWAV> import_wav;
|
|
import_wav.instance();
|
|
ResourceFormatImporter::get_singleton()->add_importer(import_wav);
|
|
|
|
Ref<ResourceImporterOBJ> import_obj;
|
|
import_obj.instance();
|
|
ResourceFormatImporter::get_singleton()->add_importer(import_obj);
|
|
|
|
Ref<ResourceImporterScene> import_scene;
|
|
import_scene.instance();
|
|
ResourceFormatImporter::get_singleton()->add_importer(import_scene);
|
|
|
|
{
|
|
Ref<EditorSceneImporterCollada> import_collada;
|
|
import_collada.instance();
|
|
import_scene->add_importer(import_collada);
|
|
|
|
Ref<EditorOBJImporter> import_obj2;
|
|
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);
|
|
}
|
|
|
|
Ref<ResourceImporterBitMap> import_bitmap;
|
|
import_bitmap.instance();
|
|
ResourceFormatImporter::get_singleton()->add_importer(import_bitmap);
|
|
}
|
|
|
|
{
|
|
Ref<EditorInspectorDefaultPlugin> eidp;
|
|
eidp.instance();
|
|
EditorInspector::add_inspector_plugin(eidp);
|
|
|
|
Ref<EditorInspectorRootMotionPlugin> rmp;
|
|
rmp.instance();
|
|
EditorInspector::add_inspector_plugin(rmp);
|
|
|
|
Ref<EditorInspectorShaderModePlugin> smp;
|
|
smp.instance();
|
|
EditorInspector::add_inspector_plugin(smp);
|
|
}
|
|
|
|
_pvrtc_register_compressors();
|
|
|
|
editor_selection = memnew(EditorSelection);
|
|
|
|
EditorFileSystem *efs = memnew(EditorFileSystem);
|
|
add_child(efs);
|
|
|
|
//used for previews
|
|
FileDialog::get_icon_func = _file_dialog_get_icon;
|
|
FileDialog::register_func = _file_dialog_register;
|
|
FileDialog::unregister_func = _file_dialog_unregister;
|
|
|
|
EditorFileDialog::get_icon_func = _file_dialog_get_icon;
|
|
EditorFileDialog::register_func = _editor_file_dialog_register;
|
|
EditorFileDialog::unregister_func = _editor_file_dialog_unregister;
|
|
|
|
editor_export = memnew(EditorExport);
|
|
add_child(editor_export);
|
|
|
|
// Exporters might need the theme
|
|
theme = create_custom_theme();
|
|
|
|
register_exporters();
|
|
|
|
GLOBAL_DEF("editor/main_run_args", "");
|
|
|
|
ClassDB::set_class_enabled("RootMotionView", true);
|
|
|
|
//defs here, use EDITOR_GET in logic
|
|
EDITOR_DEF_RST("interface/scene_tabs/always_show_close_button", false);
|
|
EDITOR_DEF_RST("interface/scene_tabs/resize_if_many_tabs", true);
|
|
EDITOR_DEF_RST("interface/scene_tabs/minimum_width", 50);
|
|
EDITOR_DEF("run/output/always_clear_output_on_play", true);
|
|
EDITOR_DEF("run/output/always_open_output_on_play", true);
|
|
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);
|
|
EDITOR_DEF_RST("interface/scene_tabs/show_thumbnail_on_hover", true);
|
|
EDITOR_DEF_RST("interface/inspector/capitalize_properties", true);
|
|
EDITOR_DEF_RST("interface/inspector/default_float_step", 0.001);
|
|
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::FLOAT, "interface/inspector/default_float_step", PROPERTY_HINT_RANGE, "0,1,0"));
|
|
EDITOR_DEF_RST("interface/inspector/disable_folding", false);
|
|
EDITOR_DEF_RST("interface/inspector/auto_unfold_foreign_scenes", true);
|
|
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/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);
|
|
|
|
gui_base = memnew(Panel);
|
|
theme_base->add_child(gui_base);
|
|
gui_base->set_anchors_and_margins_preset(Control::PRESET_WIDE);
|
|
|
|
theme_base->set_theme(theme);
|
|
gui_base->set_theme(theme);
|
|
gui_base->add_style_override("panel", gui_base->get_stylebox("Background", "EditorStyles"));
|
|
|
|
resource_preview = memnew(EditorResourcePreview);
|
|
add_child(resource_preview);
|
|
progress_dialog = memnew(ProgressDialog);
|
|
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_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->add_constant_override("separation", 8 * EDSCALE);
|
|
|
|
menu_hb = memnew(HBoxContainer);
|
|
main_vbox->add_child(menu_hb);
|
|
|
|
left_l_hsplit = memnew(HSplitContainer);
|
|
main_vbox->add_child(left_l_hsplit);
|
|
|
|
left_l_hsplit->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
|
|
|
left_l_vsplit = memnew(VSplitContainer);
|
|
left_l_hsplit->add_child(left_l_vsplit);
|
|
dock_slot[DOCK_SLOT_LEFT_UL] = memnew(TabContainer);
|
|
left_l_vsplit->add_child(dock_slot[DOCK_SLOT_LEFT_UL]);
|
|
dock_slot[DOCK_SLOT_LEFT_BL] = memnew(TabContainer);
|
|
left_l_vsplit->add_child(dock_slot[DOCK_SLOT_LEFT_BL]);
|
|
|
|
left_r_hsplit = memnew(HSplitContainer);
|
|
left_l_hsplit->add_child(left_r_hsplit);
|
|
left_r_vsplit = memnew(VSplitContainer);
|
|
left_r_hsplit->add_child(left_r_vsplit);
|
|
dock_slot[DOCK_SLOT_LEFT_UR] = memnew(TabContainer);
|
|
left_r_vsplit->add_child(dock_slot[DOCK_SLOT_LEFT_UR]);
|
|
dock_slot[DOCK_SLOT_LEFT_BR] = memnew(TabContainer);
|
|
left_r_vsplit->add_child(dock_slot[DOCK_SLOT_LEFT_BR]);
|
|
|
|
main_hsplit = memnew(HSplitContainer);
|
|
left_r_hsplit->add_child(main_hsplit);
|
|
VBoxContainer *center_vb = memnew(VBoxContainer);
|
|
main_hsplit->add_child(center_vb);
|
|
center_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
|
|
|
center_split = memnew(VSplitContainer);
|
|
center_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
|
center_split->set_collapsed(false);
|
|
center_vb->add_child(center_split);
|
|
|
|
right_hsplit = memnew(HSplitContainer);
|
|
main_hsplit->add_child(right_hsplit);
|
|
|
|
right_l_vsplit = memnew(VSplitContainer);
|
|
right_hsplit->add_child(right_l_vsplit);
|
|
dock_slot[DOCK_SLOT_RIGHT_UL] = memnew(TabContainer);
|
|
right_l_vsplit->add_child(dock_slot[DOCK_SLOT_RIGHT_UL]);
|
|
dock_slot[DOCK_SLOT_RIGHT_BL] = memnew(TabContainer);
|
|
right_l_vsplit->add_child(dock_slot[DOCK_SLOT_RIGHT_BL]);
|
|
|
|
right_r_vsplit = memnew(VSplitContainer);
|
|
right_hsplit->add_child(right_r_vsplit);
|
|
dock_slot[DOCK_SLOT_RIGHT_UR] = memnew(TabContainer);
|
|
right_r_vsplit->add_child(dock_slot[DOCK_SLOT_RIGHT_UR]);
|
|
dock_slot[DOCK_SLOT_RIGHT_BR] = memnew(TabContainer);
|
|
right_r_vsplit->add_child(dock_slot[DOCK_SLOT_RIGHT_BR]);
|
|
|
|
// Store them for easier access
|
|
vsplits.push_back(left_l_vsplit);
|
|
vsplits.push_back(left_r_vsplit);
|
|
vsplits.push_back(right_l_vsplit);
|
|
vsplits.push_back(right_r_vsplit);
|
|
|
|
hsplits.push_back(left_l_hsplit);
|
|
hsplits.push_back(left_r_hsplit);
|
|
hsplits.push_back(main_hsplit);
|
|
hsplits.push_back(right_hsplit);
|
|
|
|
for (int i = 0; i < vsplits.size(); i++) {
|
|
vsplits[i]->connect("dragged", callable_mp(this, &EditorNode::_dock_split_dragged));
|
|
hsplits[i]->connect("dragged", callable_mp(this, &EditorNode::_dock_split_dragged));
|
|
}
|
|
|
|
dock_select_popup = memnew(PopupPanel);
|
|
gui_base->add_child(dock_select_popup);
|
|
VBoxContainer *dock_vb = memnew(VBoxContainer);
|
|
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->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);
|
|
|
|
Label *dock_label = memnew(Label);
|
|
dock_label->set_text(TTR("Dock Position"));
|
|
dock_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
|
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->set_focus_mode(Control::FOCUS_NONE);
|
|
dock_tab_move_right->connect("pressed", callable_mp(this, &EditorNode::_dock_move_right));
|
|
|
|
dock_hb->add_child(dock_tab_move_right);
|
|
dock_vb->add_child(dock_hb);
|
|
|
|
dock_select = memnew(Control);
|
|
dock_select->set_custom_minimum_size(Size2(128, 64) * EDSCALE);
|
|
dock_select->connect("gui_input", callable_mp(this, &EditorNode::_dock_select_input));
|
|
dock_select->connect("draw", callable_mp(this, &EditorNode::_dock_select_draw));
|
|
dock_select->connect("mouse_exited", callable_mp(this, &EditorNode::_dock_popup_exit));
|
|
dock_select->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
|
dock_vb->add_child(dock_select);
|
|
|
|
dock_select_popup->set_as_minsize();
|
|
dock_select_rect_over = -1;
|
|
dock_popup_selected = -1;
|
|
for (int i = 0; i < DOCK_SLOT_MAX; i++) {
|
|
dock_slot[i]->set_custom_minimum_size(Size2(170, 0) * EDSCALE);
|
|
dock_slot[i]->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
|
dock_slot[i]->set_popup(dock_select_popup);
|
|
dock_slot[i]->connect("pre_popup_pressed", callable_mp(this, &EditorNode::_dock_pre_popup), varray(i));
|
|
dock_slot[i]->set_tab_align(TabContainer::ALIGN_LEFT);
|
|
dock_slot[i]->set_drag_to_rearrange_enabled(true);
|
|
dock_slot[i]->set_tabs_rearrange_group(1);
|
|
dock_slot[i]->connect("tab_changed", callable_mp(this, &EditorNode::_dock_tab_changed));
|
|
dock_slot[i]->set_use_hidden_tabs_for_min_size(true);
|
|
}
|
|
|
|
dock_drag_timer = memnew(Timer);
|
|
add_child(dock_drag_timer);
|
|
dock_drag_timer->set_wait_time(0.5);
|
|
dock_drag_timer->set_one_shot(true);
|
|
dock_drag_timer->connect("timeout", callable_mp(this, &EditorNode::_save_docks));
|
|
|
|
top_split = memnew(VSplitContainer);
|
|
center_split->add_child(top_split);
|
|
top_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
|
top_split->set_collapsed(true);
|
|
|
|
VBoxContainer *srt = memnew(VBoxContainer);
|
|
srt->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
|
top_split->add_child(srt);
|
|
srt->add_constant_override("separation", 0);
|
|
|
|
tab_preview_panel = memnew(Panel);
|
|
tab_preview_panel->set_size(Size2(100, 100) * EDSCALE);
|
|
tab_preview_panel->hide();
|
|
tab_preview_panel->set_self_modulate(Color(1, 1, 1, 0.7));
|
|
gui_base->add_child(tab_preview_panel);
|
|
|
|
tab_preview = memnew(TextureRect);
|
|
tab_preview->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
|
|
tab_preview->set_size(Size2(96, 96) * EDSCALE);
|
|
tab_preview->set_position(Point2(2, 2) * EDSCALE);
|
|
tab_preview_panel->add_child(tab_preview);
|
|
|
|
scene_tabs = memnew(Tabs);
|
|
scene_tabs->add_style_override("tab_fg", gui_base->get_stylebox("SceneTabFG", "EditorStyles"));
|
|
scene_tabs->add_style_override("tab_bg", gui_base->get_stylebox("SceneTabBG", "EditorStyles"));
|
|
scene_tabs->set_select_with_rmb(true);
|
|
scene_tabs->add_tab("unsaved");
|
|
scene_tabs->set_tab_align(Tabs::ALIGN_LEFT);
|
|
scene_tabs->set_tab_close_display_policy((bool(EDITOR_DEF("interface/scene_tabs/always_show_close_button", false)) ? Tabs::CLOSE_BUTTON_SHOW_ALWAYS : Tabs::CLOSE_BUTTON_SHOW_ACTIVE_ONLY));
|
|
scene_tabs->set_min_width(int(EDITOR_DEF("interface/scene_tabs/minimum_width", 50)) * EDSCALE);
|
|
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("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));
|
|
scene_tabs->connect("resized", callable_mp(this, &EditorNode::_update_scene_tabs));
|
|
|
|
tabbar_container = memnew(HBoxContainer);
|
|
scene_tabs->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
|
|
|
scene_tabs_context_menu = memnew(PopupMenu);
|
|
tabbar_container->add_child(scene_tabs_context_menu);
|
|
scene_tabs_context_menu->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
|
|
scene_tabs_context_menu->set_hide_on_window_lose_focus(true);
|
|
|
|
srt->add_child(tabbar_container);
|
|
tabbar_container->add_child(scene_tabs);
|
|
distraction_free = memnew(ToolButton);
|
|
#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
|
|
distraction_free->set_shortcut(ED_SHORTCUT("editor/distraction_free_mode", TTR("Distraction Free Mode"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_F11));
|
|
#endif
|
|
distraction_free->set_tooltip(TTR("Toggle distraction-free mode."));
|
|
distraction_free->connect("pressed", callable_mp(this, &EditorNode::_toggle_distraction_free_mode));
|
|
distraction_free->set_icon(gui_base->get_icon("DistractionFree", "EditorIcons"));
|
|
distraction_free->set_toggle_mode(true);
|
|
|
|
scene_tab_add = memnew(ToolButton);
|
|
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_icon("Add", "EditorIcons"));
|
|
scene_tab_add->add_color_override("icon_color_normal", 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);
|
|
scene_root_parent->set_custom_minimum_size(Size2(0, 80) * EDSCALE);
|
|
scene_root_parent->add_style_override("panel", gui_base->get_stylebox("Content", "EditorStyles"));
|
|
scene_root_parent->set_draw_behind_parent(true);
|
|
srt->add_child(scene_root_parent);
|
|
scene_root_parent->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
|
|
|
scene_root = memnew(Viewport);
|
|
//scene_root->set_usage(Viewport::USAGE_2D); canvas BG mode prevents usage of this as 2D
|
|
|
|
VisualServer::get_singleton()->viewport_set_hide_scenario(scene_root->get_viewport_rid(), true);
|
|
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_constant_override("separation", 0);
|
|
scene_root_parent->add_child(viewport);
|
|
|
|
HBoxContainer *left_menu_hb = memnew(HBoxContainer);
|
|
menu_hb->add_child(left_menu_hb);
|
|
|
|
file_menu = memnew(MenuButton);
|
|
file_menu->set_flat(false);
|
|
file_menu->set_switch_on_hover(true);
|
|
file_menu->set_text(TTR("Scene"));
|
|
file_menu->add_style_override("hover", gui_base->get_stylebox("MenuHover", "EditorStyles"));
|
|
left_menu_hb->add_child(file_menu);
|
|
|
|
prev_scene = memnew(ToolButton);
|
|
prev_scene->set_icon(gui_base->get_icon("PrevScene", "EditorIcons"));
|
|
prev_scene->set_tooltip(TTR("Go to previously opened scene."));
|
|
prev_scene->set_disabled(true);
|
|
prev_scene->connect("pressed", callable_mp(this, &EditorNode::_menu_option), make_binds(FILE_OPEN_PREV));
|
|
gui_base->add_child(prev_scene);
|
|
prev_scene->set_position(Point2(3, 24));
|
|
prev_scene->hide();
|
|
|
|
accept = memnew(AcceptDialog);
|
|
gui_base->add_child(accept);
|
|
accept->connect("confirmed", callable_mp(this, &EditorNode::_menu_confirm_current));
|
|
|
|
project_export = memnew(ProjectExportDialog);
|
|
gui_base->add_child(project_export);
|
|
|
|
dependency_error = memnew(DependencyErrorDialog);
|
|
gui_base->add_child(dependency_error);
|
|
|
|
dependency_fixer = memnew(DependencyEditor);
|
|
gui_base->add_child(dependency_fixer);
|
|
|
|
settings_config_dialog = memnew(EditorSettingsDialog);
|
|
gui_base->add_child(settings_config_dialog);
|
|
|
|
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);
|
|
|
|
feature_profile_manager = memnew(EditorFeatureProfileManager);
|
|
gui_base->add_child(feature_profile_manager);
|
|
about = memnew(EditorAbout);
|
|
gui_base->add_child(about);
|
|
feature_profile_manager->connect("current_feature_profile_changed", callable_mp(this, &EditorNode::_feature_profile_changed));
|
|
|
|
warning = memnew(AcceptDialog);
|
|
warning->add_button(TTR("Copy Text"), true, "copy");
|
|
gui_base->add_child(warning);
|
|
warning->connect("custom_action", callable_mp(this, &EditorNode::_copy_warning));
|
|
|
|
ED_SHORTCUT("editor/next_tab", TTR("Next tab"), KEY_MASK_CMD + KEY_TAB);
|
|
ED_SHORTCUT("editor/prev_tab", TTR("Previous tab"), KEY_MASK_CMD + KEY_MASK_SHIFT + KEY_TAB);
|
|
ED_SHORTCUT("editor/filter_files", TTR("Filter Files..."), KEY_MASK_CMD + KEY_MASK_ALT + KEY_P);
|
|
PopupMenu *p;
|
|
|
|
file_menu->set_tooltip(TTR("Operations with scene files."));
|
|
|
|
p = file_menu->get_popup();
|
|
p->set_hide_on_window_lose_focus(true);
|
|
p->add_shortcut(ED_SHORTCUT("editor/new_scene", TTR("New Scene")), FILE_NEW_SCENE);
|
|
p->add_shortcut(ED_SHORTCUT("editor/new_inherited_scene", TTR("New Inherited Scene...")), FILE_NEW_INHERITED_SCENE);
|
|
p->add_shortcut(ED_SHORTCUT("editor/open_scene", TTR("Open Scene..."), KEY_MASK_CMD + KEY_O), FILE_OPEN_SCENE);
|
|
p->add_shortcut(ED_SHORTCUT("editor/reopen_closed_scene", TTR("Reopen Closed Scene"), KEY_MASK_CMD + KEY_MASK_SHIFT + KEY_T), FILE_OPEN_PREV);
|
|
p->add_submenu_item(TTR("Open Recent"), "RecentScenes", FILE_OPEN_RECENT);
|
|
|
|
p->add_separator();
|
|
p->add_shortcut(ED_SHORTCUT("editor/save_scene", TTR("Save Scene"), KEY_MASK_CMD + KEY_S), FILE_SAVE_SCENE);
|
|
p->add_shortcut(ED_SHORTCUT("editor/save_scene_as", TTR("Save Scene As..."), KEY_MASK_CMD + KEY_MASK_SHIFT + KEY_S), FILE_SAVE_AS_SCENE);
|
|
p->add_shortcut(ED_SHORTCUT("editor/save_all_scenes", TTR("Save All Scenes"), KEY_MASK_CMD + KEY_MASK_SHIFT + KEY_MASK_ALT + KEY_S), FILE_SAVE_ALL_SCENES);
|
|
|
|
p->add_separator();
|
|
|
|
p->add_shortcut(ED_SHORTCUT("editor/quick_open", TTR("Quick Open..."), KEY_MASK_SHIFT + KEY_MASK_ALT + KEY_O), FILE_QUICK_OPEN);
|
|
p->add_shortcut(ED_SHORTCUT("editor/quick_open_scene", TTR("Quick Open Scene..."), KEY_MASK_CMD + KEY_MASK_SHIFT + KEY_O), FILE_QUICK_OPEN_SCENE);
|
|
p->add_shortcut(ED_SHORTCUT("editor/quick_open_script", TTR("Quick Open Script..."), KEY_MASK_CMD + KEY_MASK_ALT + KEY_O), FILE_QUICK_OPEN_SCRIPT);
|
|
|
|
p->add_separator();
|
|
PopupMenu *pm_export = memnew(PopupMenu);
|
|
pm_export->set_name("Export");
|
|
p->add_child(pm_export);
|
|
p->add_submenu_item(TTR("Convert To..."), "Export");
|
|
pm_export->add_shortcut(ED_SHORTCUT("editor/convert_to_MeshLibrary", TTR("MeshLibrary...")), FILE_EXPORT_MESH_LIBRARY);
|
|
pm_export->add_shortcut(ED_SHORTCUT("editor/convert_to_TileSet", TTR("TileSet...")), FILE_EXPORT_TILESET);
|
|
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_separator();
|
|
p->add_shortcut(ED_SHORTCUT("editor/revert_scene", TTR("Revert Scene")), EDIT_REVERT);
|
|
p->add_shortcut(ED_SHORTCUT("editor/close_scene", TTR("Close Scene"), KEY_MASK_CMD + KEY_MASK_SHIFT + KEY_W), FILE_CLOSE);
|
|
|
|
recent_scenes = memnew(PopupMenu);
|
|
recent_scenes->set_name("RecentScenes");
|
|
p->add_child(recent_scenes);
|
|
recent_scenes->connect("id_pressed", callable_mp(this, &EditorNode::_open_recent_scene));
|
|
|
|
p->add_separator();
|
|
p->add_shortcut(ED_SHORTCUT("editor/file_quit", TTR("Quit"), KEY_MASK_CMD + KEY_Q), FILE_QUIT, true);
|
|
|
|
project_menu = memnew(MenuButton);
|
|
project_menu->set_flat(false);
|
|
project_menu->set_switch_on_hover(true);
|
|
project_menu->set_tooltip(TTR("Miscellaneous project or scene-wide tools."));
|
|
project_menu->set_text(TTR("Project"));
|
|
project_menu->add_style_override("hover", gui_base->get_stylebox("MenuHover", "EditorStyles"));
|
|
left_menu_hb->add_child(project_menu);
|
|
|
|
p = project_menu->get_popup();
|
|
p->set_hide_on_window_lose_focus(true);
|
|
p->add_shortcut(ED_SHORTCUT("editor/project_settings", TTR("Project Settings...")), RUN_SETTINGS);
|
|
p->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
|
|
|
|
vcs_actions_menu = VersionControlEditorPlugin::get_singleton()->get_version_control_actions_panel();
|
|
vcs_actions_menu->set_name("Version Control");
|
|
vcs_actions_menu->connect("index_pressed", callable_mp(this, &EditorNode::_version_control_menu_option));
|
|
p->add_separator();
|
|
p->add_child(vcs_actions_menu);
|
|
p->add_submenu_item(TTR("Version Control"), "Version Control");
|
|
vcs_actions_menu->add_item(TTR("Set Up Version Control"), RUN_VCS_SETTINGS);
|
|
vcs_actions_menu->add_item(TTR("Shut Down Version Control"), RUN_VCS_SHUT_DOWN);
|
|
|
|
p->add_separator();
|
|
p->add_shortcut(ED_SHORTCUT("editor/export", TTR("Export...")), FILE_EXPORT_PROJECT);
|
|
p->add_item(TTR("Install Android Build Template..."), FILE_INSTALL_ANDROID_SOURCE);
|
|
p->add_item(TTR("Open Project Data Folder"), RUN_PROJECT_DATA_FOLDER);
|
|
|
|
plugin_config_dialog = memnew(PluginConfigDialog);
|
|
plugin_config_dialog->connect("plugin_ready", callable_mp(this, &EditorNode::_on_plugin_ready));
|
|
gui_base->add_child(plugin_config_dialog);
|
|
|
|
tool_menu = memnew(PopupMenu);
|
|
tool_menu->set_name("Tools");
|
|
tool_menu->connect("index_pressed", callable_mp(this, &EditorNode::_tool_menu_option));
|
|
p->add_child(tool_menu);
|
|
p->add_submenu_item(TTR("Tools"), "Tools");
|
|
tool_menu->add_item(TTR("Orphan Resource Explorer..."), TOOLS_ORPHAN_RESOURCES);
|
|
|
|
p->add_separator();
|
|
#ifdef OSX_ENABLED
|
|
p->add_shortcut(ED_SHORTCUT("editor/quit_to_project_list", TTR("Quit to Project List"), KEY_MASK_SHIFT + KEY_MASK_ALT + KEY_Q), RUN_PROJECT_MANAGER, true);
|
|
#else
|
|
p->add_shortcut(ED_SHORTCUT("editor/quit_to_project_list", TTR("Quit to Project List"), KEY_MASK_CMD + KEY_MASK_SHIFT + KEY_Q), RUN_PROJECT_MANAGER, true);
|
|
#endif
|
|
|
|
menu_hb->add_spacer();
|
|
|
|
main_editor_button_vb = memnew(HBoxContainer);
|
|
menu_hb->add_child(main_editor_button_vb);
|
|
|
|
// Options are added and handled by DebuggerEditorPlugin
|
|
debug_menu = memnew(MenuButton);
|
|
debug_menu->set_flat(false);
|
|
debug_menu->set_switch_on_hover(true);
|
|
debug_menu->set_text(TTR("Debug"));
|
|
debug_menu->add_style_override("hover", gui_base->get_stylebox("MenuHover", "EditorStyles"));
|
|
left_menu_hb->add_child(debug_menu);
|
|
menu_hb->add_spacer();
|
|
|
|
settings_menu = memnew(MenuButton);
|
|
settings_menu->set_flat(false);
|
|
settings_menu->set_switch_on_hover(true);
|
|
settings_menu->set_text(TTR("Editor"));
|
|
settings_menu->add_style_override("hover", gui_base->get_stylebox("MenuHover", "EditorStyles"));
|
|
left_menu_hb->add_child(settings_menu);
|
|
|
|
p = settings_menu->get_popup();
|
|
p->set_hide_on_window_lose_focus(true);
|
|
p->add_shortcut(ED_SHORTCUT("editor/editor_settings", TTR("Editor Settings...")), SETTINGS_PREFERENCES);
|
|
p->add_separator();
|
|
|
|
editor_layouts = memnew(PopupMenu);
|
|
editor_layouts->set_name("Layouts");
|
|
p->add_child(editor_layouts);
|
|
editor_layouts->connect("id_pressed", callable_mp(this, &EditorNode::_layout_menu_option));
|
|
p->add_submenu_item(TTR("Editor Layout"), "Layouts");
|
|
p->add_separator();
|
|
#ifdef OSX_ENABLED
|
|
p->add_shortcut(ED_SHORTCUT("editor/take_screenshot", TTR("Take Screenshot"), KEY_MASK_CMD | KEY_F12), EDITOR_SCREENSHOT);
|
|
#else
|
|
p->add_shortcut(ED_SHORTCUT("editor/take_screenshot", TTR("Take Screenshot"), KEY_MASK_CTRL | KEY_F12), EDITOR_SCREENSHOT);
|
|
#endif
|
|
p->set_item_tooltip(p->get_item_count() - 1, TTR("Screenshots are stored in the Editor Data/Settings Folder."));
|
|
#ifdef OSX_ENABLED
|
|
p->add_shortcut(ED_SHORTCUT("editor/fullscreen_mode", TTR("Toggle Fullscreen"), KEY_MASK_CMD | KEY_MASK_CTRL | KEY_F), SETTINGS_TOGGLE_FULLSCREEN);
|
|
#else
|
|
p->add_shortcut(ED_SHORTCUT("editor/fullscreen_mode", TTR("Toggle Fullscreen"), KEY_MASK_SHIFT | KEY_F11), SETTINGS_TOGGLE_FULLSCREEN);
|
|
#endif
|
|
#ifdef WINDOWS_ENABLED
|
|
p->add_item(TTR("Toggle System Console"), SETTINGS_TOGGLE_CONSOLE);
|
|
#endif
|
|
p->add_separator();
|
|
|
|
if (OS::get_singleton()->get_data_path() == OS::get_singleton()->get_config_path()) {
|
|
// Configuration and data folders are located in the same place (Windows/macOS)
|
|
p->add_item(TTR("Open Editor Data/Settings Folder"), SETTINGS_EDITOR_DATA_FOLDER);
|
|
} else {
|
|
// Separate configuration and data folders (Linux)
|
|
p->add_item(TTR("Open Editor Data Folder"), SETTINGS_EDITOR_DATA_FOLDER);
|
|
p->add_item(TTR("Open Editor Settings Folder"), SETTINGS_EDITOR_CONFIG_FOLDER);
|
|
}
|
|
p->add_separator();
|
|
|
|
p->add_item(TTR("Manage Editor Features..."), SETTINGS_MANAGE_FEATURE_PROFILES);
|
|
p->add_item(TTR("Manage Export Templates..."), SETTINGS_MANAGE_EXPORT_TEMPLATES);
|
|
|
|
// Help Menu
|
|
help_menu = memnew(MenuButton);
|
|
help_menu->set_flat(false);
|
|
help_menu->set_switch_on_hover(true);
|
|
help_menu->set_text(TTR("Help"));
|
|
help_menu->add_style_override("hover", gui_base->get_stylebox("MenuHover", "EditorStyles"));
|
|
left_menu_hb->add_child(help_menu);
|
|
|
|
p = help_menu->get_popup();
|
|
p->set_hide_on_window_lose_focus(true);
|
|
p->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
|
|
p->add_icon_shortcut(gui_base->get_icon("HelpSearch", "EditorIcons"), ED_SHORTCUT("editor/editor_help", TTR("Search"), KEY_MASK_SHIFT | KEY_F1), HELP_SEARCH);
|
|
p->add_separator();
|
|
p->add_icon_shortcut(gui_base->get_icon("Instance", "EditorIcons"), ED_SHORTCUT("editor/online_docs", TTR("Online Docs")), HELP_DOCS);
|
|
p->add_icon_shortcut(gui_base->get_icon("Instance", "EditorIcons"), ED_SHORTCUT("editor/q&a", TTR("Q&A")), HELP_QA);
|
|
p->add_icon_shortcut(gui_base->get_icon("Instance", "EditorIcons"), ED_SHORTCUT("editor/issue_tracker", TTR("Issue Tracker")), HELP_ISSUES);
|
|
p->add_icon_shortcut(gui_base->get_icon("Instance", "EditorIcons"), ED_SHORTCUT("editor/community", TTR("Community")), HELP_COMMUNITY);
|
|
p->add_separator();
|
|
p->add_icon_shortcut(gui_base->get_icon("Godot", "EditorIcons"), ED_SHORTCUT("editor/about", TTR("About")), HELP_ABOUT);
|
|
|
|
HBoxContainer *play_hb = memnew(HBoxContainer);
|
|
menu_hb->add_child(play_hb);
|
|
|
|
play_button = memnew(ToolButton);
|
|
play_hb->add_child(play_button);
|
|
play_button->set_toggle_mode(true);
|
|
play_button->set_icon(gui_base->get_icon("MainPlay", "EditorIcons"));
|
|
play_button->set_focus_mode(Control::FOCUS_NONE);
|
|
play_button->connect("pressed", callable_mp(this, &EditorNode::_menu_option), make_binds(RUN_PLAY));
|
|
play_button->set_tooltip(TTR("Play the project."));
|
|
#ifdef OSX_ENABLED
|
|
play_button->set_shortcut(ED_SHORTCUT("editor/play", TTR("Play"), KEY_MASK_CMD | KEY_B));
|
|
#else
|
|
play_button->set_shortcut(ED_SHORTCUT("editor/play", TTR("Play"), KEY_F5));
|
|
#endif
|
|
|
|
pause_button = memnew(ToolButton);
|
|
pause_button->set_toggle_mode(true);
|
|
pause_button->set_icon(gui_base->get_icon("Pause", "EditorIcons"));
|
|
pause_button->set_focus_mode(Control::FOCUS_NONE);
|
|
pause_button->set_tooltip(TTR("Pause the scene execution for debugging."));
|
|
pause_button->set_disabled(true);
|
|
play_hb->add_child(pause_button);
|
|
#ifdef OSX_ENABLED
|
|
pause_button->set_shortcut(ED_SHORTCUT("editor/pause_scene", TTR("Pause Scene"), KEY_MASK_CMD | KEY_MASK_CTRL | KEY_Y));
|
|
#else
|
|
pause_button->set_shortcut(ED_SHORTCUT("editor/pause_scene", TTR("Pause Scene"), KEY_F7));
|
|
#endif
|
|
|
|
stop_button = memnew(ToolButton);
|
|
play_hb->add_child(stop_button);
|
|
stop_button->set_focus_mode(Control::FOCUS_NONE);
|
|
stop_button->set_icon(gui_base->get_icon("Stop", "EditorIcons"));
|
|
stop_button->connect("pressed", callable_mp(this, &EditorNode::_menu_option), make_binds(RUN_STOP));
|
|
stop_button->set_tooltip(TTR("Stop the scene."));
|
|
stop_button->set_disabled(true);
|
|
#ifdef OSX_ENABLED
|
|
stop_button->set_shortcut(ED_SHORTCUT("editor/stop", TTR("Stop"), KEY_MASK_CMD | KEY_PERIOD));
|
|
#else
|
|
stop_button->set_shortcut(ED_SHORTCUT("editor/stop", TTR("Stop"), KEY_F8));
|
|
#endif
|
|
|
|
run_native = memnew(EditorRunNative);
|
|
play_hb->add_child(run_native);
|
|
run_native->connect("native_run", callable_mp(this, &EditorNode::_menu_option), varray(RUN_PLAY_NATIVE));
|
|
|
|
play_scene_button = memnew(ToolButton);
|
|
play_hb->add_child(play_scene_button);
|
|
play_scene_button->set_toggle_mode(true);
|
|
play_scene_button->set_focus_mode(Control::FOCUS_NONE);
|
|
play_scene_button->set_icon(gui_base->get_icon("PlayScene", "EditorIcons"));
|
|
play_scene_button->connect("pressed", callable_mp(this, &EditorNode::_menu_option), make_binds(RUN_PLAY_SCENE));
|
|
play_scene_button->set_tooltip(TTR("Play the edited scene."));
|
|
#ifdef OSX_ENABLED
|
|
play_scene_button->set_shortcut(ED_SHORTCUT("editor/play_scene", TTR("Play Scene"), KEY_MASK_CMD | KEY_R));
|
|
#else
|
|
play_scene_button->set_shortcut(ED_SHORTCUT("editor/play_scene", TTR("Play Scene"), KEY_F6));
|
|
#endif
|
|
|
|
play_custom_scene_button = memnew(ToolButton);
|
|
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);
|
|
play_custom_scene_button->set_icon(gui_base->get_icon("PlayCustom", "EditorIcons"));
|
|
play_custom_scene_button->connect("pressed", callable_mp(this, &EditorNode::_menu_option), make_binds(RUN_PLAY_CUSTOM_SCENE));
|
|
play_custom_scene_button->set_tooltip(TTR("Play custom scene"));
|
|
#ifdef OSX_ENABLED
|
|
play_custom_scene_button->set_shortcut(ED_SHORTCUT("editor/play_custom_scene", TTR("Play Custom Scene"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_R));
|
|
#else
|
|
play_custom_scene_button->set_shortcut(ED_SHORTCUT("editor/play_custom_scene", TTR("Play Custom Scene"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_F5));
|
|
#endif
|
|
|
|
HBoxContainer *right_menu_hb = memnew(HBoxContainer);
|
|
menu_hb->add_child(right_menu_hb);
|
|
|
|
// Toggle for video driver
|
|
video_driver = memnew(OptionButton);
|
|
video_driver->set_flat(true);
|
|
video_driver->set_focus_mode(Control::FOCUS_NONE);
|
|
video_driver->connect("item_selected", callable_mp(this, &EditorNode::_video_driver_selected));
|
|
video_driver->add_font_override("font", gui_base->get_font("bold", "EditorFonts"));
|
|
// TODO re-enable when GLES2 is ported
|
|
video_driver->set_disabled(true);
|
|
right_menu_hb->add_child(video_driver);
|
|
|
|
String video_drivers = ProjectSettings::get_singleton()->get_custom_property_info()["rendering/quality/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++) {
|
|
String driver = video_drivers.get_slice(",", i);
|
|
video_driver->add_item(driver);
|
|
video_driver->set_item_metadata(i, driver);
|
|
|
|
if (current_video_driver == driver) {
|
|
video_driver->select(i);
|
|
video_driver_current = i;
|
|
}
|
|
}
|
|
|
|
_update_video_driver_color();
|
|
|
|
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->connect("confirmed", callable_mp(this, &EditorNode::_menu_option), varray(SET_VIDEO_DRIVER_SAVE_AND_RESTART));
|
|
gui_base->add_child(video_restart_dialog);
|
|
|
|
progress_hb = memnew(BackgroundProgress);
|
|
|
|
layout_dialog = memnew(EditorLayoutsDialog);
|
|
gui_base->add_child(layout_dialog);
|
|
layout_dialog->set_hide_on_ok(false);
|
|
layout_dialog->set_size(Size2(225, 270) * EDSCALE);
|
|
layout_dialog->connect("name_confirmed", callable_mp(this, &EditorNode::_dialog_action));
|
|
|
|
update_spinner = memnew(MenuButton);
|
|
update_spinner->set_tooltip(TTR("Spins when the editor window redraws."));
|
|
right_menu_hb->add_child(update_spinner);
|
|
update_spinner->set_icon(gui_base->get_icon("Progress1", "EditorIcons"));
|
|
update_spinner->get_popup()->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
|
|
p = update_spinner->get_popup();
|
|
p->add_radio_check_item(TTR("Update Continuously"), SETTINGS_UPDATE_CONTINUOUSLY);
|
|
p->add_radio_check_item(TTR("Update When Changed"), SETTINGS_UPDATE_WHEN_CHANGED);
|
|
p->add_separator();
|
|
p->add_item(TTR("Hide Update Spinner"), SETTINGS_UPDATE_SPINNER_HIDE);
|
|
_update_update_spinner();
|
|
|
|
// Instantiate and place editor docks
|
|
|
|
scene_tree_dock = memnew(SceneTreeDock(this, scene_root, editor_selection, editor_data));
|
|
inspector_dock = memnew(InspectorDock(this, editor_data));
|
|
import_dock = memnew(ImportDock);
|
|
node_dock = memnew(NodeDock);
|
|
|
|
filesystem_dock = memnew(FileSystemDock(this));
|
|
filesystem_dock->connect("inherit", callable_mp(this, &EditorNode::_inherit_request));
|
|
filesystem_dock->connect("instance", callable_mp(this, &EditorNode::_instance_request));
|
|
filesystem_dock->connect("display_mode_changed", callable_mp(this, &EditorNode::_save_docks));
|
|
|
|
// Scene: Top left
|
|
dock_slot[DOCK_SLOT_LEFT_UR]->add_child(scene_tree_dock);
|
|
dock_slot[DOCK_SLOT_LEFT_UR]->set_tab_title(scene_tree_dock->get_index(), TTR("Scene"));
|
|
|
|
// Import: Top left, behind Scene
|
|
dock_slot[DOCK_SLOT_LEFT_UR]->add_child(import_dock);
|
|
dock_slot[DOCK_SLOT_LEFT_UR]->set_tab_title(import_dock->get_index(), TTR("Import"));
|
|
|
|
// FileSystem: Bottom left
|
|
dock_slot[DOCK_SLOT_LEFT_BR]->add_child(filesystem_dock);
|
|
dock_slot[DOCK_SLOT_LEFT_BR]->set_tab_title(filesystem_dock->get_index(), TTR("FileSystem"));
|
|
|
|
// Inspector: Full height right
|
|
dock_slot[DOCK_SLOT_RIGHT_UL]->add_child(inspector_dock);
|
|
dock_slot[DOCK_SLOT_RIGHT_UL]->set_tab_title(inspector_dock->get_index(), TTR("Inspector"));
|
|
|
|
// Node: Full height right, behind Inspector
|
|
dock_slot[DOCK_SLOT_RIGHT_UL]->add_child(node_dock);
|
|
dock_slot[DOCK_SLOT_RIGHT_UL]->set_tab_title(node_dock->get_index(), TTR("Node"));
|
|
|
|
// Hide unused dock slots and vsplits
|
|
dock_slot[DOCK_SLOT_LEFT_UL]->hide();
|
|
dock_slot[DOCK_SLOT_LEFT_BL]->hide();
|
|
dock_slot[DOCK_SLOT_RIGHT_BL]->hide();
|
|
dock_slot[DOCK_SLOT_RIGHT_UR]->hide();
|
|
dock_slot[DOCK_SLOT_RIGHT_BR]->hide();
|
|
left_l_vsplit->hide();
|
|
right_r_vsplit->hide();
|
|
|
|
// Add some offsets to left_r and main hsplits to make LEFT_R and RIGHT_L docks wider than minsize
|
|
left_r_hsplit->set_split_offset(70 * EDSCALE);
|
|
main_hsplit->set_split_offset(-70 * EDSCALE);
|
|
|
|
// Define corresponding default layout
|
|
|
|
const String docks_section = "docks";
|
|
overridden_default_layout = -1;
|
|
default_layout.instance();
|
|
// Dock numbers are based on DockSlot enum value + 1
|
|
default_layout->set_value(docks_section, "dock_3", "Scene,Import");
|
|
default_layout->set_value(docks_section, "dock_4", "FileSystem");
|
|
default_layout->set_value(docks_section, "dock_5", "Inspector,Node");
|
|
|
|
for (int i = 0; i < vsplits.size(); i++)
|
|
default_layout->set_value(docks_section, "dock_split_" + itos(i + 1), 0);
|
|
default_layout->set_value(docks_section, "dock_hsplit_1", 0);
|
|
default_layout->set_value(docks_section, "dock_hsplit_2", 70 * EDSCALE);
|
|
default_layout->set_value(docks_section, "dock_hsplit_3", -70 * EDSCALE);
|
|
default_layout->set_value(docks_section, "dock_hsplit_4", 0);
|
|
|
|
_update_layouts_menu();
|
|
|
|
// Bottom panels
|
|
|
|
bottom_panel = memnew(PanelContainer);
|
|
bottom_panel->add_style_override("panel", gui_base->get_stylebox("panel", "TabContainer"));
|
|
center_split->add_child(bottom_panel);
|
|
center_split->set_dragger_visibility(SplitContainer::DRAGGER_HIDDEN);
|
|
|
|
bottom_panel_vb = memnew(VBoxContainer);
|
|
bottom_panel->add_child(bottom_panel_vb);
|
|
|
|
bottom_panel_hb = memnew(HBoxContainer);
|
|
bottom_panel_hb->set_custom_minimum_size(Size2(0, 24 * EDSCALE)); // Adjust for the height of the "Expand Bottom Dock" icon.
|
|
bottom_panel_vb->add_child(bottom_panel_hb);
|
|
|
|
bottom_panel_hb_editors = memnew(HBoxContainer);
|
|
bottom_panel_hb_editors->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
|
bottom_panel_hb->add_child(bottom_panel_hb_editors);
|
|
|
|
version_label = memnew(Label);
|
|
version_label->set_text(VERSION_FULL_CONFIG);
|
|
// Fade out the version label to be less prominent, but still readable
|
|
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->set_icon(gui_base->get_icon("ExpandBottomDock", "EditorIcons"));
|
|
|
|
bottom_panel_raise->set_shortcut(ED_SHORTCUT("editor/bottom_panel_expand", TTR("Expand Bottom Panel"), KEY_MASK_SHIFT | KEY_F12));
|
|
|
|
bottom_panel_hb->add_child(bottom_panel_raise);
|
|
bottom_panel_raise->hide();
|
|
bottom_panel_raise->set_toggle_mode(true);
|
|
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);
|
|
log->set_tool_button(output_button);
|
|
|
|
old_split_ofs = 0;
|
|
|
|
center_split->connect("resized", callable_mp(this, &EditorNode::_vp_resized));
|
|
|
|
orphan_resources = memnew(OrphanResourcesDialog);
|
|
gui_base->add_child(orphan_resources);
|
|
|
|
confirmation = memnew(ConfirmationDialog);
|
|
gui_base->add_child(confirmation);
|
|
confirmation->connect("confirmed", callable_mp(this, &EditorNode::_menu_confirm_current));
|
|
|
|
save_confirmation = memnew(ConfirmationDialog);
|
|
save_confirmation->add_button(TTR("Don't Save"), OS::get_singleton()->get_swap_ok_cancel(), "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->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->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->connect("confirmed", callable_mp(this, &EditorNode::_menu_option), varray(FILE_EXPLORE_ANDROID_BUILD_TEMPLATES));
|
|
gui_base->add_child(remove_android_build_template);
|
|
|
|
file_templates = memnew(EditorFileDialog);
|
|
file_templates->set_title(TTR("Import Templates From ZIP File"));
|
|
|
|
gui_base->add_child(file_templates);
|
|
file_templates->set_mode(EditorFileDialog::MODE_OPEN_FILE);
|
|
file_templates->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
|
|
file_templates->clear_filters();
|
|
file_templates->add_filter("*.tpz ; " + TTR("Template Package"));
|
|
|
|
file = memnew(EditorFileDialog);
|
|
gui_base->add_child(file);
|
|
file->set_current_dir("res://");
|
|
|
|
file_export_lib = memnew(EditorFileDialog);
|
|
file_export_lib->set_title(TTR("Export Library"));
|
|
file_export_lib->set_mode(EditorFileDialog::MODE_SAVE_FILE);
|
|
file_export_lib->connect("file_selected", callable_mp(this, &EditorNode::_dialog_action));
|
|
file_export_lib_merge = memnew(CheckBox);
|
|
file_export_lib_merge->set_text(TTR("Merge With Existing"));
|
|
file_export_lib_merge->set_pressed(true);
|
|
file_export_lib->get_vbox()->add_child(file_export_lib_merge);
|
|
gui_base->add_child(file_export_lib);
|
|
|
|
file_script = memnew(EditorFileDialog);
|
|
file_script->set_title(TTR("Open & Run a Script"));
|
|
file_script->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
|
|
file_script->set_mode(EditorFileDialog::MODE_OPEN_FILE);
|
|
List<String> sexts;
|
|
ResourceLoader::get_recognized_extensions_for_type("Script", &sexts);
|
|
for (List<String>::Element *E = sexts.front(); E; E = E->next()) {
|
|
file_script->add_filter("*." + E->get());
|
|
}
|
|
gui_base->add_child(file_script);
|
|
file_script->connect("file_selected", callable_mp(this, &EditorNode::_dialog_action));
|
|
|
|
file_menu->get_popup()->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
|
|
file_menu->connect("about_to_show", callable_mp(this, &EditorNode::_update_file_menu_opened));
|
|
file_menu->get_popup()->connect("popup_hide", callable_mp(this, &EditorNode::_update_file_menu_closed));
|
|
|
|
settings_menu->get_popup()->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
|
|
|
|
file->connect("file_selected", callable_mp(this, &EditorNode::_dialog_action));
|
|
file_templates->connect("file_selected", callable_mp(this, &EditorNode::_dialog_action));
|
|
|
|
preview_gen = memnew(AudioStreamPreviewGenerator);
|
|
add_child(preview_gen);
|
|
//plugin stuff
|
|
|
|
add_editor_plugin(memnew(DebuggerEditorPlugin(this, debug_menu)));
|
|
add_editor_plugin(memnew(AnimationPlayerEditorPlugin(this)));
|
|
add_editor_plugin(memnew(CanvasItemEditorPlugin(this)));
|
|
add_editor_plugin(memnew(SpatialEditorPlugin(this)));
|
|
add_editor_plugin(memnew(ScriptEditorPlugin(this)));
|
|
|
|
EditorAudioBuses *audio_bus_editor = EditorAudioBuses::register_editor();
|
|
|
|
ScriptTextEditor::register_editor(); //register one for text scripts
|
|
TextEditor::register_editor();
|
|
|
|
if (StreamPeerSSL::is_available()) {
|
|
add_editor_plugin(memnew(AssetLibraryEditorPlugin(this)));
|
|
} else {
|
|
WARN_PRINT("Asset Library not available, as it requires SSL to work.");
|
|
}
|
|
|
|
//add interface before adding plugins
|
|
|
|
editor_interface = memnew(EditorInterface);
|
|
add_child(editor_interface);
|
|
|
|
//more visually meaningful to have this later
|
|
raise_bottom_panel_item(AnimationPlayerEditor::singleton);
|
|
|
|
add_editor_plugin(VersionControlEditorPlugin::get_singleton());
|
|
add_editor_plugin(memnew(ShaderEditorPlugin(this)));
|
|
add_editor_plugin(memnew(VisualShaderEditorPlugin(this)));
|
|
|
|
add_editor_plugin(memnew(CameraEditorPlugin(this)));
|
|
add_editor_plugin(memnew(ThemeEditorPlugin(this)));
|
|
add_editor_plugin(memnew(MultiMeshEditorPlugin(this)));
|
|
add_editor_plugin(memnew(MeshInstanceEditorPlugin(this)));
|
|
add_editor_plugin(memnew(AnimationTreeEditorPlugin(this)));
|
|
add_editor_plugin(memnew(MeshLibraryEditorPlugin(this)));
|
|
add_editor_plugin(memnew(StyleBoxEditorPlugin(this)));
|
|
add_editor_plugin(memnew(SpriteEditorPlugin(this)));
|
|
add_editor_plugin(memnew(Skeleton2DEditorPlugin(this)));
|
|
add_editor_plugin(memnew(ParticlesEditorPlugin(this)));
|
|
add_editor_plugin(memnew(CPUParticles2DEditorPlugin(this)));
|
|
add_editor_plugin(memnew(CPUParticlesEditorPlugin(this)));
|
|
add_editor_plugin(memnew(ResourcePreloaderEditorPlugin(this)));
|
|
add_editor_plugin(memnew(ItemListEditorPlugin(this)));
|
|
add_editor_plugin(memnew(Polygon3DEditorPlugin(this)));
|
|
add_editor_plugin(memnew(CollisionPolygon2DEditorPlugin(this)));
|
|
add_editor_plugin(memnew(TileSetEditorPlugin(this)));
|
|
add_editor_plugin(memnew(TileMapEditorPlugin(this)));
|
|
add_editor_plugin(memnew(SpriteFramesEditorPlugin(this)));
|
|
add_editor_plugin(memnew(TextureRegionEditorPlugin(this)));
|
|
add_editor_plugin(memnew(Particles2DEditorPlugin(this)));
|
|
add_editor_plugin(memnew(GIProbeEditorPlugin(this)));
|
|
// add_editor_plugin(memnew(BakedLightmapEditorPlugin(this)));
|
|
add_editor_plugin(memnew(Path2DEditorPlugin(this)));
|
|
add_editor_plugin(memnew(PathEditorPlugin(this)));
|
|
add_editor_plugin(memnew(Line2DEditorPlugin(this)));
|
|
add_editor_plugin(memnew(Polygon2DEditorPlugin(this)));
|
|
add_editor_plugin(memnew(LightOccluder2DEditorPlugin(this)));
|
|
add_editor_plugin(memnew(NavigationPolygonEditorPlugin(this)));
|
|
add_editor_plugin(memnew(GradientEditorPlugin(this)));
|
|
add_editor_plugin(memnew(CollisionShape2DEditorPlugin(this)));
|
|
add_editor_plugin(memnew(CurveEditorPlugin(this)));
|
|
add_editor_plugin(memnew(TextureEditorPlugin(this)));
|
|
add_editor_plugin(memnew(AudioStreamEditorPlugin(this)));
|
|
add_editor_plugin(memnew(AudioBusesEditorPlugin(audio_bus_editor)));
|
|
add_editor_plugin(memnew(SkeletonEditorPlugin(this)));
|
|
add_editor_plugin(memnew(SkeletonIKEditorPlugin(this)));
|
|
add_editor_plugin(memnew(PhysicalBonePlugin(this)));
|
|
add_editor_plugin(memnew(MeshEditorPlugin(this)));
|
|
add_editor_plugin(memnew(MaterialEditorPlugin(this)));
|
|
|
|
for (int i = 0; i < EditorPlugins::get_plugin_count(); i++)
|
|
add_editor_plugin(EditorPlugins::create(i, this));
|
|
|
|
for (int i = 0; i < plugin_init_callback_count; i++) {
|
|
plugin_init_callbacks[i]();
|
|
}
|
|
|
|
resource_preview->add_preview_generator(Ref<EditorTexturePreviewPlugin>(memnew(EditorTexturePreviewPlugin)));
|
|
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)));
|
|
resource_preview->add_preview_generator(Ref<EditorAudioStreamPreviewPlugin>(memnew(EditorAudioStreamPreviewPlugin)));
|
|
resource_preview->add_preview_generator(Ref<EditorMeshPreviewPlugin>(memnew(EditorMeshPreviewPlugin)));
|
|
resource_preview->add_preview_generator(Ref<EditorBitmapPreviewPlugin>(memnew(EditorBitmapPreviewPlugin)));
|
|
resource_preview->add_preview_generator(Ref<EditorFontPreviewPlugin>(memnew(EditorFontPreviewPlugin)));
|
|
|
|
{
|
|
Ref<StandardMaterial3DConversionPlugin> spatial_mat_convert;
|
|
spatial_mat_convert.instance();
|
|
resource_conversion_plugins.push_back(spatial_mat_convert);
|
|
|
|
Ref<CanvasItemMaterialConversionPlugin> canvas_item_mat_convert;
|
|
canvas_item_mat_convert.instance();
|
|
resource_conversion_plugins.push_back(canvas_item_mat_convert);
|
|
|
|
Ref<ParticlesMaterialConversionPlugin> particles_mat_convert;
|
|
particles_mat_convert.instance();
|
|
resource_conversion_plugins.push_back(particles_mat_convert);
|
|
|
|
Ref<VisualShaderConversionPlugin> vshader_convert;
|
|
vshader_convert.instance();
|
|
resource_conversion_plugins.push_back(vshader_convert);
|
|
}
|
|
update_spinner_step_msec = OS::get_singleton()->get_ticks_msec();
|
|
update_spinner_step_frame = Engine::get_singleton()->get_frames_drawn();
|
|
update_spinner_step = 0;
|
|
|
|
editor_plugin_screen = NULL;
|
|
editor_plugins_over = memnew(EditorPluginList);
|
|
editor_plugins_force_over = memnew(EditorPluginList);
|
|
editor_plugins_force_input_forwarding = memnew(EditorPluginList);
|
|
|
|
Ref<EditorExportTextSceneToBinaryPlugin> export_text_to_binary_plugin;
|
|
export_text_to_binary_plugin.instance();
|
|
|
|
EditorExport::get_singleton()->add_export_plugin(export_text_to_binary_plugin);
|
|
|
|
_edit_current();
|
|
current = NULL;
|
|
saving_resource = Ref<Resource>();
|
|
|
|
reference_resource_mem = true;
|
|
save_external_resources_mem = true;
|
|
|
|
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"), !OS::get_singleton()->get_swap_ok_cancel(), "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);
|
|
|
|
saved_version = 1;
|
|
unsaved_cache = true;
|
|
_last_instanced_scene = NULL;
|
|
|
|
quick_open = memnew(EditorQuickOpen);
|
|
gui_base->add_child(quick_open);
|
|
quick_open->connect("quick_open", callable_mp(this, &EditorNode::_quick_opened));
|
|
|
|
quick_run = memnew(EditorQuickOpen);
|
|
gui_base->add_child(quick_run);
|
|
quick_run->connect("quick_open", callable_mp(this, &EditorNode::_quick_run));
|
|
|
|
_update_recent_scenes();
|
|
|
|
editor_data.restore_editor_global_states();
|
|
convert_old = false;
|
|
opening_prev = false;
|
|
set_process_unhandled_input(true);
|
|
_playing_edited = false;
|
|
|
|
load_errors = memnew(RichTextLabel);
|
|
load_error_dialog = memnew(AcceptDialog);
|
|
load_error_dialog->add_child(load_errors);
|
|
load_error_dialog->set_title(TTR("Load Errors"));
|
|
gui_base->add_child(load_error_dialog);
|
|
|
|
execute_outputs = memnew(RichTextLabel);
|
|
execute_output_dialog = memnew(AcceptDialog);
|
|
execute_output_dialog->add_child(execute_outputs);
|
|
execute_output_dialog->set_title("");
|
|
gui_base->add_child(execute_output_dialog);
|
|
|
|
EditorFileSystem::get_singleton()->connect("sources_changed", callable_mp(this, &EditorNode::_sources_changed));
|
|
EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &EditorNode::_fs_changed));
|
|
EditorFileSystem::get_singleton()->connect("resources_reimported", callable_mp(this, &EditorNode::_resources_reimported));
|
|
EditorFileSystem::get_singleton()->connect("resources_reload", callable_mp(this, &EditorNode::_resources_changed));
|
|
|
|
_build_icon_type_cache();
|
|
|
|
Node::set_human_readable_collision_renaming(true);
|
|
|
|
pick_main_scene = memnew(ConfirmationDialog);
|
|
gui_base->add_child(pick_main_scene);
|
|
pick_main_scene->get_ok()->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++)
|
|
_init_callbacks[i]();
|
|
|
|
editor_data.add_edited_scene(-1);
|
|
editor_data.set_edited_scene(0);
|
|
_update_scene_tabs();
|
|
|
|
import_dock->initialize_import_options();
|
|
|
|
FileAccess::set_file_close_fail_notify_callback(_file_access_close_error_notify);
|
|
|
|
waiting_for_first_scan = true;
|
|
|
|
print_handler.printfunc = _print_handler;
|
|
print_handler.userdata = this;
|
|
add_print_handler(&print_handler);
|
|
|
|
ResourceSaver::set_save_callback(_resource_saved);
|
|
ResourceLoader::set_load_callback(_resource_loaded);
|
|
|
|
#ifdef OSX_ENABLED
|
|
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_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);
|
|
#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"));
|
|
|
|
screenshot_timer = memnew(Timer);
|
|
screenshot_timer->set_one_shot(true);
|
|
screenshot_timer->set_wait_time(settings_menu->get_popup()->get_submenu_popup_delay() + 0.1f);
|
|
screenshot_timer->connect("timeout", callable_mp(this, &EditorNode::_request_screenshot));
|
|
add_child(screenshot_timer);
|
|
screenshot_timer->set_owner(get_owner());
|
|
|
|
String exec = OS::get_singleton()->get_executable_path();
|
|
EditorSettings::get_singleton()->set_project_metadata("editor_metadata", "executable_path", exec); // Save editor executable path for third-party tools
|
|
}
|
|
|
|
EditorNode::~EditorNode() {
|
|
|
|
EditorInspector::cleanup_plugins();
|
|
|
|
remove_print_handler(&print_handler);
|
|
memdelete(EditorHelp::get_doc_data());
|
|
memdelete(editor_selection);
|
|
memdelete(editor_plugins_over);
|
|
memdelete(editor_plugins_force_over);
|
|
memdelete(editor_plugins_force_input_forwarding);
|
|
memdelete(progress_hb);
|
|
|
|
EditorSettings::destroy();
|
|
}
|
|
|
|
/*
|
|
* EDITOR PLUGIN LIST
|
|
*/
|
|
|
|
void EditorPluginList::make_visible(bool p_visible) {
|
|
|
|
for (int i = 0; i < plugins_list.size(); i++) {
|
|
plugins_list[i]->make_visible(p_visible);
|
|
}
|
|
}
|
|
|
|
void EditorPluginList::edit(Object *p_object) {
|
|
|
|
for (int i = 0; i < plugins_list.size(); i++) {
|
|
plugins_list[i]->edit(p_object);
|
|
}
|
|
}
|
|
|
|
bool EditorPluginList::forward_gui_input(const Ref<InputEvent> &p_event) {
|
|
|
|
bool discard = false;
|
|
|
|
for (int i = 0; i < plugins_list.size(); i++) {
|
|
if (plugins_list[i]->forward_canvas_gui_input(p_event)) {
|
|
discard = true;
|
|
}
|
|
}
|
|
|
|
return discard;
|
|
}
|
|
|
|
bool EditorPluginList::forward_spatial_gui_input(Camera *p_camera, const Ref<InputEvent> &p_event, bool serve_when_force_input_enabled) {
|
|
bool discard = false;
|
|
|
|
for (int i = 0; i < plugins_list.size(); i++) {
|
|
if ((!serve_when_force_input_enabled) && plugins_list[i]->is_input_event_forwarding_always_enabled()) {
|
|
continue;
|
|
}
|
|
|
|
if (plugins_list[i]->forward_spatial_gui_input(p_camera, p_event)) {
|
|
discard = true;
|
|
}
|
|
}
|
|
|
|
return discard;
|
|
}
|
|
|
|
void EditorPluginList::forward_canvas_draw_over_viewport(Control *p_overlay) {
|
|
|
|
for (int i = 0; i < plugins_list.size(); i++) {
|
|
plugins_list[i]->forward_canvas_draw_over_viewport(p_overlay);
|
|
}
|
|
}
|
|
|
|
void EditorPluginList::forward_canvas_force_draw_over_viewport(Control *p_overlay) {
|
|
|
|
for (int i = 0; i < plugins_list.size(); i++) {
|
|
plugins_list[i]->forward_canvas_force_draw_over_viewport(p_overlay);
|
|
}
|
|
}
|
|
|
|
void EditorPluginList::forward_spatial_draw_over_viewport(Control *p_overlay) {
|
|
|
|
for (int i = 0; i < plugins_list.size(); i++) {
|
|
plugins_list[i]->forward_spatial_draw_over_viewport(p_overlay);
|
|
}
|
|
}
|
|
|
|
void EditorPluginList::forward_spatial_force_draw_over_viewport(Control *p_overlay) {
|
|
|
|
for (int i = 0; i < plugins_list.size(); i++) {
|
|
plugins_list[i]->forward_spatial_force_draw_over_viewport(p_overlay);
|
|
}
|
|
}
|
|
|
|
void EditorPluginList::add_plugin(EditorPlugin *p_plugin) {
|
|
plugins_list.push_back(p_plugin);
|
|
}
|
|
|
|
void EditorPluginList::remove_plugin(EditorPlugin *p_plugin) {
|
|
plugins_list.erase(p_plugin);
|
|
}
|
|
|
|
bool EditorPluginList::empty() {
|
|
return plugins_list.empty();
|
|
}
|
|
|
|
void EditorPluginList::clear() {
|
|
plugins_list.clear();
|
|
}
|
|
|
|
EditorPluginList::EditorPluginList() {
|
|
}
|
|
|
|
EditorPluginList::~EditorPluginList() {
|
|
}
|