summaryrefslogtreecommitdiffstats
path: root/editor
diff options
context:
space:
mode:
Diffstat (limited to 'editor')
-rw-r--r--editor/action_map_editor.cpp3
-rw-r--r--editor/debugger/editor_debugger_node.cpp6
-rw-r--r--editor/debugger/script_editor_debugger.cpp1
-rw-r--r--editor/editor_help.cpp6
-rw-r--r--editor/editor_inspector.cpp63
-rw-r--r--editor/editor_node.cpp322
-rw-r--r--editor/editor_node.h26
-rw-r--r--editor/editor_settings.cpp6
-rw-r--r--editor/filesystem_dock.cpp12
-rw-r--r--editor/input_event_configuration_dialog.cpp6
-rw-r--r--editor/input_event_configuration_dialog.h1
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp33
-rw-r--r--editor/plugins/animation_player_editor_plugin.h1
-rw-r--r--editor/plugins/script_editor_plugin.cpp16
14 files changed, 323 insertions, 179 deletions
diff --git a/editor/action_map_editor.cpp b/editor/action_map_editor.cpp
index 3023c5907a..f70730d540 100644
--- a/editor/action_map_editor.cpp
+++ b/editor/action_map_editor.cpp
@@ -428,6 +428,7 @@ void ActionMapEditor::update_action_list(const Vector<ActionInfo> &p_action_info
// Update Tree...
TreeItem *action_item = action_tree->create_item(root);
+ ERR_FAIL_NULL(action_item);
action_item->set_meta("__action", action_info.action);
action_item->set_meta("__name", action_info.name);
@@ -604,7 +605,7 @@ ActionMapEditor::ActionMapEditor() {
action_tree->set_column_custom_minimum_width(1, 80 * EDSCALE);
action_tree->set_column_expand(2, false);
action_tree->set_column_custom_minimum_width(2, 50 * EDSCALE);
- action_tree->connect("item_edited", callable_mp(this, &ActionMapEditor::_action_edited));
+ action_tree->connect("item_edited", callable_mp(this, &ActionMapEditor::_action_edited), CONNECT_DEFERRED);
action_tree->connect("item_activated", callable_mp(this, &ActionMapEditor::_tree_item_activated));
action_tree->connect("button_clicked", callable_mp(this, &ActionMapEditor::_tree_button_pressed));
main_vbox->add_child(action_tree);
diff --git a/editor/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp
index 2f7183b883..2a98f50a3a 100644
--- a/editor/debugger/editor_debugger_node.cpp
+++ b/editor/debugger/editor_debugger_node.cpp
@@ -268,11 +268,7 @@ Error EditorDebuggerNode::start(const String &p_uri) {
}
stop(true);
current_uri = p_uri;
- if (EDITOR_GET("run/output/always_open_output_on_play")) {
- EditorNode::get_bottom_panel()->make_item_visible(EditorNode::get_log());
- } else {
- EditorNode::get_bottom_panel()->make_item_visible(this);
- }
+
server = Ref<EditorDebuggerServer>(EditorDebuggerServer::create(p_uri.substr(0, p_uri.find("://") + 3)));
const Error err = server->start(p_uri);
if (err != OK) {
diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp
index 156e740509..37bb048b19 100644
--- a/editor/debugger/script_editor_debugger.cpp
+++ b/editor/debugger/script_editor_debugger.cpp
@@ -1009,7 +1009,6 @@ void ScriptEditorDebugger::start(Ref<RemoteDebuggerPeer> p_peer) {
set_process(true);
camera_override = CameraOverride::OVERRIDE_NONE;
- tabs->set_current_tab(0);
_set_reason_text(TTR("Debug session started."), MESSAGE_SUCCESS);
_update_buttons_state();
emit_signal(SNAME("started"));
diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp
index 48c0c7ac06..fa5cdd185f 100644
--- a/editor/editor_help.cpp
+++ b/editor/editor_help.cpp
@@ -37,7 +37,7 @@
#include "core/object/script_language.h"
#include "core/os/keyboard.h"
#include "core/string/string_builder.h"
-#include "core/version.h"
+#include "core/version_generated.gen.h"
#include "editor/doc_data_compressed.gen.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
@@ -2340,6 +2340,9 @@ void EditorHelp::_help_callback(const String &p_topic) {
if (class_desc->is_ready()) {
// call_deferred() is not enough.
+ if (class_desc->is_connected(SceneStringName(draw), callable_mp(class_desc, &RichTextLabel::scroll_to_paragraph))) {
+ class_desc->disconnect(SceneStringName(draw), callable_mp(class_desc, &RichTextLabel::scroll_to_paragraph));
+ }
class_desc->connect(SceneStringName(draw), callable_mp(class_desc, &RichTextLabel::scroll_to_paragraph).bind(line), CONNECT_ONE_SHOT | CONNECT_DEFERRED);
} else {
scroll_to = line;
@@ -3380,6 +3383,7 @@ EditorHelpBit::HelpData EditorHelpBit::_get_theme_item_help_data(const StringNam
if (theme_item.name == p_theme_item_name) {
result = current;
found = true;
+
if (!is_native) {
break;
}
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index f4dcc8bd4a..fafefa7771 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -2869,15 +2869,6 @@ void EditorInspector::update_tree() {
// Otherwise the category was probably added via `@export_category` or `_get_property_list()`.
const bool is_custom_category = p.hint_string.is_empty();
- if ((is_custom_category && !show_custom_categories) || (!is_custom_category && !show_standard_categories)) {
- continue;
- }
-
- // Hide the "MultiNodeEdit" category for MultiNodeEdit.
- if (Object::cast_to<MultiNodeEdit>(object) && p.name == "MultiNodeEdit") {
- continue;
- }
-
// Iterate over remaining properties. If no properties in category, skip the category.
List<PropertyInfo>::Element *N = E_property->next();
bool valid = true;
@@ -2898,22 +2889,20 @@ void EditorInspector::update_tree() {
continue; // Empty, ignore it.
}
- // Create an EditorInspectorCategory and add it to the inspector.
- EditorInspectorCategory *category = memnew(EditorInspectorCategory);
- main_vbox->add_child(category);
- category_vbox = nullptr; //reset
+ String category_label;
+ String category_tooltip;
+ Ref<Texture> category_icon;
// Do not add an icon, do not change the current class (`doc_name`) for custom categories.
if (is_custom_category) {
- category->label = p.name;
- category->set_tooltip_text(p.name);
+ category_label = p.name;
+ category_tooltip = p.name;
} else {
- String type = p.name;
- String label = p.name;
doc_name = p.name;
+ category_label = p.name;
// Use category's owner script to update some of its information.
- if (!EditorNode::get_editor_data().is_type_recognized(type) && ResourceLoader::exists(p.hint_string)) {
+ if (!EditorNode::get_editor_data().is_type_recognized(p.name) && ResourceLoader::exists(p.hint_string)) {
Ref<Script> scr = ResourceLoader::load(p.hint_string, "Script");
if (scr.is_valid()) {
StringName script_name = EditorNode::get_editor_data().script_class_get_name(scr->get_path());
@@ -2926,32 +2915,50 @@ void EditorInspector::update_tree() {
doc_name = docs[docs.size() - 1].name;
}
if (script_name != StringName()) {
- label = script_name;
+ category_label = script_name;
}
// Find the icon corresponding to the script.
if (script_name != StringName()) {
- category->icon = EditorNode::get_singleton()->get_class_icon(script_name);
+ category_icon = EditorNode::get_singleton()->get_class_icon(script_name);
} else {
- category->icon = EditorNode::get_singleton()->get_object_icon(scr.ptr(), "Object");
+ category_icon = EditorNode::get_singleton()->get_object_icon(scr.ptr(), "Object");
}
}
}
- if (category->icon.is_null() && !type.is_empty()) {
- category->icon = EditorNode::get_singleton()->get_class_icon(type);
+ if (category_icon.is_null() && !p.name.is_empty()) {
+ category_icon = EditorNode::get_singleton()->get_class_icon(p.name);
}
- // Set the category label.
- category->label = label;
- category->doc_class_name = doc_name;
-
if (use_doc_hints) {
// `|` separators used in `EditorHelpBit`.
- category->set_tooltip_text("class|" + doc_name + "|");
+ category_tooltip = "class|" + doc_name + "|";
}
}
+ if ((is_custom_category && !show_custom_categories) || (!is_custom_category && !show_standard_categories)) {
+ continue;
+ }
+
+ // Hide the "MultiNodeEdit" category for MultiNodeEdit.
+ if (Object::cast_to<MultiNodeEdit>(object) && p.name == "MultiNodeEdit") {
+ continue;
+ }
+
+ // Create an EditorInspectorCategory and add it to the inspector.
+ EditorInspectorCategory *category = memnew(EditorInspectorCategory);
+ main_vbox->add_child(category);
+ category_vbox = nullptr; // Reset.
+
+ // Set the category info.
+ category->label = category_label;
+ category->set_tooltip_text(category_tooltip);
+ category->icon = category_icon;
+ if (!is_custom_category) {
+ category->doc_class_name = doc_name;
+ }
+
// Add editors at the start of a category.
for (Ref<EditorInspectorPlugin> &ped : valid_plugins) {
ped->parse_category(object, p.name);
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 6599a58da4..0df4df36bc 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -4107,7 +4107,7 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b
return OK;
}
-HashMap<StringName, Variant> EditorNode::get_modified_properties_for_node(Node *p_node) {
+HashMap<StringName, Variant> EditorNode::get_modified_properties_for_node(Node *p_node, bool p_node_references_only) {
HashMap<StringName, Variant> modified_property_map;
List<PropertyInfo> pinfo;
@@ -4119,7 +4119,17 @@ HashMap<StringName, Variant> EditorNode::get_modified_properties_for_node(Node *
Variant current_value = p_node->get(E.name);
if (is_valid_revert) {
if (PropertyUtils::is_property_value_different(current_value, revert_value)) {
- modified_property_map[E.name] = current_value;
+ // If this property is a direct node reference, save a NodePath instead to prevent corrupted references.
+ if (E.type == Variant::OBJECT && E.hint == PROPERTY_HINT_NODE_TYPE) {
+ Node *target_node = Object::cast_to<Node>(current_value);
+ if (target_node) {
+ modified_property_map[E.name] = p_node->get_path_to(target_node);
+ }
+ } else {
+ if (!p_node_references_only) {
+ modified_property_map[E.name] = current_value;
+ }
+ }
}
}
}
@@ -4137,10 +4147,118 @@ void EditorNode::update_ownership_table_for_addition_node_ancestors(Node *p_curr
}
}
-void EditorNode::update_diff_data_for_node(
- Node *p_edited_scene,
+void EditorNode::update_node_from_node_modification_entry(Node *p_node, ModificationNodeEntry &p_node_modification) {
+ if (p_node) {
+ // First, attempt to restore the script property since it may affect the get_property_list method.
+ Variant *script_property_table_entry = p_node_modification.property_table.getptr(CoreStringName(script));
+ if (script_property_table_entry) {
+ p_node->set_script(*script_property_table_entry);
+ }
+
+ // Get properties for this node.
+ List<PropertyInfo> pinfo;
+ p_node->get_property_list(&pinfo);
+
+ // Get names of all valid property names.
+ HashMap<StringName, bool> property_node_reference_table;
+ for (const PropertyInfo &E : pinfo) {
+ if (E.usage & PROPERTY_USAGE_STORAGE) {
+ if (E.type == Variant::OBJECT && E.hint == PROPERTY_HINT_NODE_TYPE) {
+ property_node_reference_table[E.name] = true;
+ } else {
+ property_node_reference_table[E.name] = false;
+ }
+ }
+ }
+
+ // Restore the modified properties for this node.
+ for (const KeyValue<StringName, Variant> &E : p_node_modification.property_table) {
+ bool *property_node_reference_table_entry = property_node_reference_table.getptr(E.key);
+ if (property_node_reference_table_entry) {
+ // If the property is a node reference, attempt to restore from the node path instead.
+ bool is_node_reference = *property_node_reference_table_entry;
+ if (is_node_reference) {
+ if (E.value.get_type() == Variant::NODE_PATH) {
+ p_node->set(E.key, p_node->get_node_or_null(E.value));
+ }
+ } else {
+ p_node->set(E.key, E.value);
+ }
+ }
+ }
+
+ // Restore the connections to other nodes.
+ for (const ConnectionWithNodePath &E : p_node_modification.connections_to) {
+ Connection conn = E.connection;
+
+ // Get the node the callable is targeting.
+ Node *target_node = Object::cast_to<Node>(conn.callable.get_object());
+
+ // If the callable object no longer exists or is marked for deletion,
+ // attempt to reaccquire the closest match by using the node path
+ // we saved earlier.
+ if (!target_node || !target_node->is_queued_for_deletion()) {
+ target_node = p_node->get_node_or_null(E.node_path);
+ }
+
+ if (target_node) {
+ // Reconstruct the callable.
+ Callable new_callable = Callable(target_node, conn.callable.get_method());
+
+ if (!p_node->is_connected(conn.signal.get_name(), new_callable)) {
+ ERR_FAIL_COND(p_node->connect(conn.signal.get_name(), new_callable, conn.flags) != OK);
+ }
+ }
+ }
+
+ // Restore the connections from other nodes.
+ for (const Connection &E : p_node_modification.connections_from) {
+ Connection conn = E;
+
+ bool valid = p_node->has_method(conn.callable.get_method()) || Ref<Script>(p_node->get_script()).is_null() || Ref<Script>(p_node->get_script())->has_method(conn.callable.get_method());
+ ERR_CONTINUE_MSG(!valid, vformat("Attempt to connect signal '%s.%s' to nonexistent method '%s.%s'.", conn.signal.get_object()->get_class(), conn.signal.get_name(), conn.callable.get_object()->get_class(), conn.callable.get_method()));
+
+ // Get the object which the signal is connected from.
+ Object *source_object = conn.signal.get_object();
+
+ if (source_object) {
+ ERR_FAIL_COND(source_object->connect(conn.signal.get_name(), Callable(p_node, conn.callable.get_method()), conn.flags) != OK);
+ }
+ }
+
+ // Re-add the groups.
+ for (const Node::GroupInfo &E : p_node_modification.groups) {
+ p_node->add_to_group(E.name, E.persistent);
+ }
+ }
+}
+
+void EditorNode::update_node_reference_modification_table_for_node(
Node *p_root,
Node *p_node,
+ List<Node *> p_excluded_nodes,
+ HashMap<NodePath, ModificationNodeEntry> &p_modification_table) {
+ if (!p_excluded_nodes.find(p_node)) {
+ HashMap<StringName, Variant> modified_properties = get_modified_properties_for_node(p_node, false);
+
+ if (!modified_properties.is_empty()) {
+ ModificationNodeEntry modification_node_entry;
+ modification_node_entry.property_table = modified_properties;
+
+ p_modification_table[p_root->get_path_to(p_node)] = modification_node_entry;
+ }
+
+ for (int i = 0; i < p_node->get_child_count(); i++) {
+ Node *child = p_node->get_child(i);
+ update_node_reference_modification_table_for_node(p_root, child, p_excluded_nodes, p_modification_table);
+ }
+ }
+}
+
+void EditorNode::update_reimported_diff_data_for_node(
+ Node *p_edited_scene,
+ Node *p_reimported_root,
+ Node *p_node,
HashMap<NodePath, ModificationNodeEntry> &p_modification_table,
List<AdditiveNodeEntry> &p_addition_list) {
bool node_part_of_subscene = p_node != p_edited_scene &&
@@ -4150,14 +4268,14 @@ void EditorNode::update_diff_data_for_node(
// Loop through the owners until either we reach the root node or nullptr
Node *valid_node_owner = p_node->get_owner();
while (valid_node_owner) {
- if (valid_node_owner == p_root) {
+ if (valid_node_owner == p_reimported_root) {
break;
}
valid_node_owner = valid_node_owner->get_owner();
}
- if ((valid_node_owner == p_root && (p_root != p_edited_scene || !p_edited_scene->get_scene_file_path().is_empty())) || node_part_of_subscene || p_node == p_root) {
- HashMap<StringName, Variant> modified_properties = get_modified_properties_for_node(p_node);
+ if ((valid_node_owner == p_reimported_root && (p_reimported_root != p_edited_scene || !p_edited_scene->get_scene_file_path().is_empty())) || node_part_of_subscene || p_node == p_reimported_root) {
+ HashMap<StringName, Variant> modified_properties = get_modified_properties_for_node(p_node, false);
// Find all valid connections to other nodes.
List<Connection> connections_to;
@@ -4189,7 +4307,7 @@ void EditorNode::update_diff_data_for_node(
if (source_node) {
valid_source_owner = source_node->get_owner();
while (valid_source_owner) {
- if (valid_source_owner == p_root) {
+ if (valid_source_owner == p_reimported_root) {
break;
}
valid_source_owner = valid_source_owner->get_owner();
@@ -4215,41 +4333,55 @@ void EditorNode::update_diff_data_for_node(
modification_node_entry.connections_from = valid_connections_from;
modification_node_entry.groups = groups;
- p_modification_table[p_root->get_path_to(p_node)] = modification_node_entry;
+ p_modification_table[p_reimported_root->get_path_to(p_node)] = modification_node_entry;
}
} else {
- AdditiveNodeEntry new_additive_node_entry;
- new_additive_node_entry.node = p_node;
- new_additive_node_entry.parent = p_root->get_path_to(p_node->get_parent());
- new_additive_node_entry.owner = p_node->get_owner();
- new_additive_node_entry.index = p_node->get_index();
+ // Only save additional nodes which have an owner since this was causing issues transient ownerless nodes
+ // which get recreated upon scene tree entry.
+ // For now instead, assume all ownerless nodes are transient and will have to be recreated.
+ if (p_node->get_owner()) {
+ HashMap<StringName, Variant> modified_properties = get_modified_properties_for_node(p_node, true);
+
+ if (p_node->get_parent()->get_owner() != nullptr && p_node->get_parent()->get_owner() != p_edited_scene) {
+ AdditiveNodeEntry new_additive_node_entry;
+ new_additive_node_entry.node = p_node;
+ new_additive_node_entry.parent = p_reimported_root->get_path_to(p_node->get_parent());
+ new_additive_node_entry.owner = p_node->get_owner();
+ new_additive_node_entry.index = p_node->get_index();
+
+ Node2D *node_2d = Object::cast_to<Node2D>(p_node);
+ if (node_2d) {
+ new_additive_node_entry.transform_2d = node_2d->get_relative_transform_to_parent(node_2d->get_parent());
+ }
+ Node3D *node_3d = Object::cast_to<Node3D>(p_node);
+ if (node_3d) {
+ new_additive_node_entry.transform_3d = node_3d->get_relative_transform(node_3d->get_parent());
+ }
- Node2D *node_2d = Object::cast_to<Node2D>(p_node);
- if (node_2d) {
- new_additive_node_entry.transform_2d = node_2d->get_relative_transform_to_parent(node_2d->get_parent());
- }
- Node3D *node_3d = Object::cast_to<Node3D>(p_node);
- if (node_3d) {
- new_additive_node_entry.transform_3d = node_3d->get_relative_transform(node_3d->get_parent());
- }
+ // Gathers the ownership of all ancestor nodes for later use.
+ HashMap<Node *, Node *> ownership_table;
+ for (int i = 0; i < p_node->get_child_count(); i++) {
+ Node *child = p_node->get_child(i);
+ update_ownership_table_for_addition_node_ancestors(child, ownership_table);
+ }
- // Gathers the ownership of all ancestor nodes for later use.
- HashMap<Node *, Node *> ownership_table;
- for (int i = 0; i < p_node->get_child_count(); i++) {
- Node *child = p_node->get_child(i);
- update_ownership_table_for_addition_node_ancestors(child, ownership_table);
- }
+ new_additive_node_entry.ownership_table = ownership_table;
- new_additive_node_entry.ownership_table = ownership_table;
+ p_addition_list.push_back(new_additive_node_entry);
+ }
- p_addition_list.push_back(new_additive_node_entry);
+ if (!modified_properties.is_empty()) {
+ ModificationNodeEntry modification_node_entry;
+ modification_node_entry.property_table = modified_properties;
- return;
+ p_modification_table[p_reimported_root->get_path_to(p_node)] = modification_node_entry;
+ }
+ }
}
for (int i = 0; i < p_node->get_child_count(); i++) {
Node *child = p_node->get_child(i);
- update_diff_data_for_node(p_edited_scene, p_root, child, p_modification_table, p_addition_list);
+ update_reimported_diff_data_for_node(p_edited_scene, p_reimported_root, child, p_modification_table, p_addition_list);
}
}
//
@@ -4411,17 +4543,19 @@ void EditorNode::_project_run_started() {
log->clear();
}
- if (bool(EDITOR_GET("run/output/always_open_output_on_play"))) {
+ int action_on_play = EDITOR_GET("run/bottom_panel/action_on_play");
+ if (action_on_play == ACTION_ON_PLAY_OPEN_OUTPUT) {
bottom_panel->make_item_visible(log);
+ } else if (action_on_play == ACTION_ON_PLAY_OPEN_DEBUGGER) {
+ bottom_panel->make_item_visible(EditorDebuggerNode::get_singleton());
}
}
void EditorNode::_project_run_stopped() {
- if (!bool(EDITOR_GET("run/output/always_close_output_on_stop"))) {
- return;
+ int action_on_stop = EDITOR_GET("run/bottom_panel/action_on_stop");
+ if (action_on_stop == ACTION_ON_STOP_CLOSE_BUTTOM_PANEL) {
+ bottom_panel->hide_bottom_panel();
}
-
- bottom_panel->make_item_visible(log, false);
}
void EditorNode::notify_all_debug_sessions_exited() {
@@ -5548,12 +5682,11 @@ void EditorNode::_file_access_close_error_notify_impl(const String &p_str) {
add_io_error(vformat(TTR("Unable to write to file '%s', file in use, locked or lacking permissions."), p_str));
}
-// Since we felt that a bespoke NOTIFICATION might not be desirable, this function
-// provides the hardcoded callbacks to address known bugs which occur on certain
-// nodes during reimport.
-// Ideally, we should probably agree on a standardized method name which could be
-// called from here instead.
-void EditorNode::_notify_scene_updated(Node *p_node) {
+// Recursive function to inform nodes that an array of nodes have had their scene reimported.
+// It will attempt to call a method named '_nodes_scene_reimported' on every node in the
+// tree so that editor scripts which create transient nodes will have the opportunity
+// to recreate them.
+void EditorNode::_notify_nodes_scene_reimported(Node *p_node, Array p_reimported_nodes) {
Skeleton3D *skel_3d = Object::cast_to<Skeleton3D>(p_node);
if (skel_3d) {
skel_3d->reset_bone_poses();
@@ -5564,8 +5697,12 @@ void EditorNode::_notify_scene_updated(Node *p_node) {
}
}
+ if (p_node->has_method("_nodes_scene_reimported")) {
+ p_node->call("_nodes_scene_reimported", p_reimported_nodes);
+ }
+
for (int i = 0; i < p_node->get_child_count(); i++) {
- _notify_scene_updated(p_node->get_child(i));
+ _notify_nodes_scene_reimported(p_node->get_child(i), p_reimported_nodes);
}
}
@@ -5635,6 +5772,7 @@ void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node *
void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_instance_path) {
int original_edited_scene_idx = editor_data.get_edited_scene();
HashMap<int, List<Node *>> edited_scene_map;
+ Array replaced_nodes;
// Walk through each opened scene to get a global list of all instances which match
// the current reimported scenes.
@@ -5675,12 +5813,16 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins
// Update the version
editor_data.is_scene_changed(current_scene_idx);
+ // Contains modifications in the edited scene which reference nodes inside of any nodes we will be reimporting.
+ HashMap<NodePath, ModificationNodeEntry> edited_scene_global_modification_table;
+ update_node_reference_modification_table_for_node(current_edited_scene, current_edited_scene, edited_scene_map_elem.value, edited_scene_global_modification_table);
+
for (Node *original_node : edited_scene_map_elem.value) {
// Walk the tree for the current node and extract relevant diff data, storing it in the modification table.
// For additional nodes which are part of the current scene, they get added to the addition table.
HashMap<NodePath, ModificationNodeEntry> modification_table;
List<AdditiveNodeEntry> addition_list;
- update_diff_data_for_node(current_edited_scene, original_node, original_node, modification_table, addition_list);
+ update_reimported_diff_data_for_node(current_edited_scene, original_node, original_node, modification_table, addition_list);
// Disconnect all relevant connections, all connections from and persistent connections to.
for (const KeyValue<NodePath, ModificationNodeEntry> &modification_table_entry : modification_table) {
@@ -5762,7 +5904,7 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins
// be properly updated.
for (String path : required_load_paths) {
if (!local_scene_cache.find(path)) {
- current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REPLACE, &err);
+ current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REPLACE_DEEP, &err);
local_scene_cache[path] = current_packed_scene;
} else {
current_packed_scene = local_scene_cache[path];
@@ -5780,6 +5922,9 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins
ERR_FAIL_NULL(instantiated_node);
+ // For clear instance state for path recaching.
+ instantiated_node->set_scene_instance_state(Ref<SceneState>());
+
bool original_node_is_displayed_folded = original_node->is_displayed_folded();
bool original_node_scene_instance_load_placeholder = original_node->get_scene_instance_load_placeholder();
@@ -5885,77 +6030,30 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins
NodePath new_current_path = E.key;
Node *modifiable_node = instantiated_node->get_node_or_null(new_current_path);
- if (modifiable_node) {
- // Get properties for this node.
- List<PropertyInfo> pinfo;
- modifiable_node->get_property_list(&pinfo);
-
- // Get names of all valid property names (TODO: make this more efficient).
- List<String> property_names;
- for (const PropertyInfo &E2 : pinfo) {
- if (E2.usage & PROPERTY_USAGE_STORAGE) {
- property_names.push_back(E2.name);
- }
- }
-
- // Restore the modified properties for this node.
- for (const KeyValue<StringName, Variant> &E2 : E.value.property_table) {
- if (property_names.find(E2.key)) {
- modifiable_node->set(E2.key, E2.value);
- }
- }
- // Restore the connections to other nodes.
- for (const ConnectionWithNodePath &E2 : E.value.connections_to) {
- Connection conn = E2.connection;
-
- // Get the node the callable is targeting.
- Node *target_node = cast_to<Node>(conn.callable.get_object());
-
- // If the callable object no longer exists or is marked for deletion,
- // attempt to reaccquire the closest match by using the node path
- // we saved earlier.
- if (!target_node || !target_node->is_queued_for_deletion()) {
- target_node = modifiable_node->get_node_or_null(E2.node_path);
- }
-
- if (target_node) {
- // Reconstruct the callable.
- Callable new_callable = Callable(target_node, conn.callable.get_method());
-
- if (!modifiable_node->is_connected(conn.signal.get_name(), new_callable)) {
- ERR_FAIL_COND(modifiable_node->connect(conn.signal.get_name(), new_callable, conn.flags) != OK);
- }
- }
- }
-
- // Restore the connections from other nodes.
- for (const Connection &E2 : E.value.connections_from) {
- Connection conn = E2;
-
- bool valid = modifiable_node->has_method(conn.callable.get_method()) || Ref<Script>(modifiable_node->get_script()).is_null() || Ref<Script>(modifiable_node->get_script())->has_method(conn.callable.get_method());
- ERR_CONTINUE_MSG(!valid, vformat("Attempt to connect signal '%s.%s' to nonexistent method '%s.%s'.", conn.signal.get_object()->get_class(), conn.signal.get_name(), conn.callable.get_object()->get_class(), conn.callable.get_method()));
-
- // Get the object which the signal is connected from.
- Object *source_object = conn.signal.get_object();
+ update_node_from_node_modification_entry(modifiable_node, E.value);
+ }
+ // Add the newly instantiated node to the edited scene's replaced node list.
+ replaced_nodes.push_back(instantiated_node);
+ }
- if (source_object) {
- ERR_FAIL_COND(source_object->connect(conn.signal.get_name(), Callable(modifiable_node, conn.callable.get_method()), conn.flags) != OK);
- }
- }
+ // Attempt to restore the modified properties and signals for the instantitated node and all its owned children.
+ for (KeyValue<NodePath, ModificationNodeEntry> &E : edited_scene_global_modification_table) {
+ NodePath new_current_path = E.key;
+ Node *modifiable_node = current_edited_scene->get_node_or_null(new_current_path);
- // Re-add the groups.
- for (const Node::GroupInfo &E2 : E.value.groups) {
- modifiable_node->add_to_group(E2.name, E2.persistent);
- }
- }
+ if (modifiable_node) {
+ update_node_from_node_modification_entry(modifiable_node, E.value);
}
}
// Cleanup the history of the changes.
editor_history.cleanup_history();
-
- _notify_scene_updated(current_edited_scene);
}
+
+ // For the whole editor, call the _notify_nodes_scene_reimported with a list of replaced nodes.
+ // To inform anything that depends on them that they should update as appropriate.
+ _notify_nodes_scene_reimported(this, replaced_nodes);
+
edited_scene_map.clear();
}
editor_data.set_edited_scene(original_edited_scene_idx);
@@ -7228,6 +7326,8 @@ EditorNode::EditorNode() {
disk_changed = memnew(ConfirmationDialog);
{
+ disk_changed->set_title(TTR("Files have been modified on disk"));
+
VBoxContainer *vbc = memnew(VBoxContainer);
disk_changed->add_child(vbc);
@@ -7241,9 +7341,9 @@ EditorNode::EditorNode() {
disk_changed->connect("confirmed", callable_mp(this, &EditorNode::_reload_modified_scenes));
disk_changed->connect("confirmed", callable_mp(this, &EditorNode::_reload_project_settings));
- disk_changed->set_ok_button_text(TTR("Reload"));
+ disk_changed->set_ok_button_text(TTR("Discard local changes and reload"));
- disk_changed->add_button(TTR("Resave"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave");
+ disk_changed->add_button(TTR("Keep local changes and overwrite"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave");
disk_changed->connect("custom_action", callable_mp(this, &EditorNode::_resave_scenes));
}
diff --git a/editor/editor_node.h b/editor/editor_node.h
index 6b3359eaee..5d7bd5b4f8 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -140,6 +140,17 @@ public:
SCENE_NAME_CASING_KEBAB_CASE,
};
+ enum ActionOnPlay {
+ ACTION_ON_PLAY_DO_NOTHING,
+ ACTION_ON_PLAY_OPEN_OUTPUT,
+ ACTION_ON_PLAY_OPEN_DEBUGGER,
+ };
+
+ enum ActionOnStop {
+ ACTION_ON_STOP_DO_NOTHING,
+ ACTION_ON_STOP_CLOSE_BUTTOM_PANEL,
+ };
+
struct ExecuteThreadArgs {
String path;
List<String> args;
@@ -662,7 +673,7 @@ private:
void _begin_first_scan();
- void _notify_scene_updated(Node *p_node);
+ void _notify_nodes_scene_reimported(Node *p_node, Array p_reimported_nodes);
protected:
friend class FileSystemDock;
@@ -779,7 +790,7 @@ public:
Error load_scene(const String &p_scene, bool p_ignore_broken_deps = false, bool p_set_inherited = false, bool p_clear_errors = true, bool p_force_open_imported = false, bool p_silent_change_tab = false);
Error load_resource(const String &p_resource, bool p_ignore_broken_deps = false);
- HashMap<StringName, Variant> get_modified_properties_for_node(Node *p_node);
+ HashMap<StringName, Variant> get_modified_properties_for_node(Node *p_node, bool p_node_references_only);
struct AdditiveNodeEntry {
Node *node = nullptr;
@@ -806,11 +817,18 @@ public:
};
void update_ownership_table_for_addition_node_ancestors(Node *p_current_node, HashMap<Node *, Node *> &p_ownership_table);
+ void update_node_from_node_modification_entry(Node *p_node, ModificationNodeEntry &p_node_modification);
- void update_diff_data_for_node(
- Node *p_edited_scene,
+ void update_node_reference_modification_table_for_node(
Node *p_root,
Node *p_node,
+ List<Node *> p_excluded_nodes,
+ HashMap<NodePath, ModificationNodeEntry> &p_modification_table);
+
+ void update_reimported_diff_data_for_node(
+ Node *p_edited_scene,
+ Node *p_reimported_root,
+ Node *p_node,
HashMap<NodePath, ModificationNodeEntry> &p_modification_table,
List<AdditiveNodeEntry> &p_addition_list);
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index d7bc3502ce..737bec352d 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -818,11 +818,13 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
// Auto save
_initial_set("run/auto_save/save_before_running", true);
+ // Bottom panel
+ EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "run/bottom_panel/action_on_play", EditorNode::ACTION_ON_PLAY_OPEN_OUTPUT, "Do Nothing,Open Output,Open Debugger")
+ EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "run/bottom_panel/action_on_stop", EditorNode::ACTION_ON_STOP_DO_NOTHING, "Do Nothing,Close Bottom Panel")
+
// Output
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "run/output/font_size", 13, "8,48,1")
_initial_set("run/output/always_clear_output_on_play", true);
- _initial_set("run/output/always_open_output_on_play", true);
- _initial_set("run/output/always_close_output_on_stop", false);
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "run/output/max_lines", 10000, "100,100000,1")
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index 2e88540fc4..c07667ac12 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -3310,15 +3310,15 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, const Vect
// Opening the system file manager is not supported on the Android and web editors.
const bool is_directory = fpath.ends_with("/");
- p_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Filesystem")), ED_GET_SHORTCUT("filesystem_dock/show_in_explorer"), FILE_SHOW_IN_EXPLORER);
- p_popup->set_item_text(p_popup->get_item_index(FILE_SHOW_IN_EXPLORER), is_directory ? TTR("Open in File Manager") : TTR("Show in File Manager"));
+ p_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Terminal")), ED_GET_SHORTCUT("filesystem_dock/open_in_terminal"), FILE_OPEN_IN_TERMINAL);
+ p_popup->set_item_text(p_popup->get_item_index(FILE_OPEN_IN_TERMINAL), is_directory ? TTR("Open in Terminal") : TTR("Open Containing Folder in Terminal"));
if (!is_directory) {
p_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("ExternalLink")), ED_GET_SHORTCUT("filesystem_dock/open_in_external_program"), FILE_OPEN_EXTERNAL);
}
- p_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Terminal")), ED_GET_SHORTCUT("filesystem_dock/open_in_terminal"), FILE_OPEN_IN_TERMINAL);
- p_popup->set_item_text(p_popup->get_item_index(FILE_OPEN_IN_TERMINAL), is_directory ? TTR("Open in Terminal") : TTR("Open Containing Folder in Terminal"));
+ p_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Filesystem")), ED_GET_SHORTCUT("filesystem_dock/show_in_explorer"), FILE_SHOW_IN_EXPLORER);
+ p_popup->set_item_text(p_popup->get_item_index(FILE_SHOW_IN_EXPLORER), is_directory ? TTR("Open in File Manager") : TTR("Show in File Manager"));
#endif
current_path = fpath;
@@ -3362,8 +3362,8 @@ void FileSystemDock::_tree_empty_click(const Vector2 &p_pos, MouseButton p_butto
#if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED)
// Opening the system file manager is not supported on the Android and web editors.
tree_popup->add_separator();
- tree_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Filesystem")), ED_GET_SHORTCUT("filesystem_dock/show_in_explorer"), FILE_SHOW_IN_EXPLORER);
tree_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Terminal")), ED_GET_SHORTCUT("filesystem_dock/open_in_terminal"), FILE_OPEN_IN_TERMINAL);
+ tree_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Filesystem")), ED_GET_SHORTCUT("filesystem_dock/show_in_explorer"), FILE_SHOW_IN_EXPLORER);
#endif
tree_popup->set_position(tree->get_screen_position() + p_pos);
@@ -3425,8 +3425,8 @@ void FileSystemDock::_file_list_empty_clicked(const Vector2 &p_pos, MouseButton
file_list_popup->add_icon_item(get_editor_theme_icon(SNAME("Object")), TTR("New Resource..."), FILE_NEW_RESOURCE);
file_list_popup->add_icon_item(get_editor_theme_icon(SNAME("TextFile")), TTR("New TextFile..."), FILE_NEW_TEXTFILE);
file_list_popup->add_separator();
- file_list_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Filesystem")), ED_GET_SHORTCUT("filesystem_dock/show_in_explorer"), FILE_SHOW_IN_EXPLORER);
file_list_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Terminal")), ED_GET_SHORTCUT("filesystem_dock/open_in_terminal"), FILE_OPEN_IN_TERMINAL);
+ file_list_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Filesystem")), ED_GET_SHORTCUT("filesystem_dock/show_in_explorer"), FILE_SHOW_IN_EXPLORER);
file_list_popup->set_position(files->get_screen_position() + p_pos);
file_list_popup->reset_size();
diff --git a/editor/input_event_configuration_dialog.cpp b/editor/input_event_configuration_dialog.cpp
index 865729c7c3..2ecce2f739 100644
--- a/editor/input_event_configuration_dialog.cpp
+++ b/editor/input_event_configuration_dialog.cpp
@@ -446,6 +446,11 @@ void InputEventConfigurationDialog::_key_location_selected(int p_location) {
_set_event(k, original_event);
}
+void InputEventConfigurationDialog::_input_list_item_activated() {
+ TreeItem *selected = input_list_tree->get_selected();
+ selected->set_collapsed(!selected->is_collapsed());
+}
+
void InputEventConfigurationDialog::_input_list_item_selected() {
TreeItem *selected = input_list_tree->get_selected();
@@ -670,6 +675,7 @@ InputEventConfigurationDialog::InputEventConfigurationDialog() {
input_list_tree = memnew(Tree);
input_list_tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
input_list_tree->set_custom_minimum_size(Size2(0, 100 * EDSCALE)); // Min height for tree
+ input_list_tree->connect("item_activated", callable_mp(this, &InputEventConfigurationDialog::_input_list_item_activated));
input_list_tree->connect("item_selected", callable_mp(this, &InputEventConfigurationDialog::_input_list_item_selected));
input_list_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
manual_vbox->add_child(input_list_tree);
diff --git a/editor/input_event_configuration_dialog.h b/editor/input_event_configuration_dialog.h
index 1d2cc8ba36..b27f25a5b7 100644
--- a/editor/input_event_configuration_dialog.h
+++ b/editor/input_event_configuration_dialog.h
@@ -107,6 +107,7 @@ private:
void _search_term_updated(const String &p_term);
void _update_input_list();
+ void _input_list_item_activated();
void _input_list_item_selected();
void _mod_toggled(bool p_checked, int p_index);
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index 794f6190bb..1cf11f2a43 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -886,6 +886,7 @@ void AnimationPlayerEditor::_update_player() {
tool_anim->set_disabled(player == nullptr);
pin->set_disabled(player == nullptr);
+ _set_controls_disabled(player == nullptr);
if (!player) {
AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying();
@@ -936,17 +937,6 @@ void AnimationPlayerEditor::_update_player() {
ITEM_CHECK_DISABLED(TOOL_NEW_ANIM);
#undef ITEM_CHECK_DISABLED
- stop->set_disabled(no_anims_found);
- play->set_disabled(no_anims_found);
- play_bw->set_disabled(no_anims_found);
- play_bw_from->set_disabled(no_anims_found);
- play_from->set_disabled(no_anims_found);
- frame->set_editable(!no_anims_found);
- animation->set_disabled(no_anims_found);
- autoplay->set_disabled(no_anims_found);
- onion_toggle->set_disabled(no_anims_found);
- onion_skinning->set_disabled(no_anims_found);
-
_update_animation_list_icons();
updating = false;
@@ -963,7 +953,9 @@ void AnimationPlayerEditor::_update_player() {
_animation_selected(0);
}
- if (!no_anims_found) {
+ if (no_anims_found) {
+ _set_controls_disabled(true);
+ } else {
String current = animation->get_item_text(animation->get_selected());
Ref<Animation> anim = player->get_animation(current);
@@ -979,6 +971,20 @@ void AnimationPlayerEditor::_update_player() {
_update_animation();
}
+void AnimationPlayerEditor::_set_controls_disabled(bool p_disabled) {
+ frame->set_editable(!p_disabled);
+
+ stop->set_disabled(p_disabled);
+ play->set_disabled(p_disabled);
+ play_bw->set_disabled(p_disabled);
+ play_bw_from->set_disabled(p_disabled);
+ play_from->set_disabled(p_disabled);
+ animation->set_disabled(p_disabled);
+ autoplay->set_disabled(p_disabled);
+ onion_toggle->set_disabled(p_disabled);
+ onion_skinning->set_disabled(p_disabled);
+}
+
void AnimationPlayerEditor::_update_animation_list_icons() {
for (int i = 0; i < animation->get_item_count(); i++) {
String anim_name = animation->get_item_text(i);
@@ -1081,9 +1087,6 @@ void AnimationPlayerEditor::_ensure_dummy_player() {
}
}
- // Make some options disabled.
- onion_toggle->set_disabled(dummy_exists);
- onion_skinning->set_disabled(dummy_exists);
int selected = animation->get_selected();
autoplay->set_disabled(selected != -1 ? (animation->get_item_text(selected).is_empty() ? true : dummy_exists) : true);
diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h
index 70b31759fc..e624522566 100644
--- a/editor/plugins/animation_player_editor_plugin.h
+++ b/editor/plugins/animation_player_editor_plugin.h
@@ -206,6 +206,7 @@ class AnimationPlayerEditor : public VBoxContainer {
void _current_animation_changed(const String &p_name);
void _update_animation();
void _update_player();
+ void _set_controls_disabled(bool p_disabled);
void _update_animation_list_icons();
void _update_name_dialog_library_dropdown();
void _blend_edited();
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index 22dbb6e9f2..0a0909ec9f 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -4249,12 +4249,18 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) {
disk_changed = memnew(ConfirmationDialog);
{
+ disk_changed->set_title(TTR("Files have been modified on disk"));
+
VBoxContainer *vbc = memnew(VBoxContainer);
disk_changed->add_child(vbc);
- Label *dl = memnew(Label);
- dl->set_text(TTR("The following files are newer on disk.\nWhat action should be taken?:"));
- vbc->add_child(dl);
+ Label *files_are_newer_label = memnew(Label);
+ files_are_newer_label->set_text(TTR("The following files are newer on disk."));
+ vbc->add_child(files_are_newer_label);
+
+ Label *what_action_label = memnew(Label);
+ what_action_label->set_text(TTR("What action should be taken?:"));
+ vbc->add_child(what_action_label);
disk_changed_list = memnew(Tree);
vbc->add_child(disk_changed_list);
@@ -4262,9 +4268,9 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) {
disk_changed_list->set_v_size_flags(SIZE_EXPAND_FILL);
disk_changed->connect("confirmed", callable_mp(this, &ScriptEditor::reload_scripts).bind(false));
- disk_changed->set_ok_button_text(TTR("Reload"));
+ disk_changed->set_ok_button_text(TTR("Discard local changes and reload"));
- disk_changed->add_button(TTR("Resave"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave");
+ disk_changed->add_button(TTR("Keep local changes and overwrite"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave");
disk_changed->connect("custom_action", callable_mp(this, &ScriptEditor::_resave_scripts));
}