summaryrefslogtreecommitdiff
path: root/editor
diff options
context:
space:
mode:
Diffstat (limited to 'editor')
-rw-r--r--editor/action_map_editor.cpp8
-rw-r--r--editor/editor_data.h1
-rw-r--r--editor/editor_node.cpp479
-rw-r--r--editor/editor_node.h35
-rw-r--r--editor/event_listener_line_edit.cpp36
-rw-r--r--editor/export/editor_export_platform.cpp4
-rw-r--r--editor/icons/Keyboard.svg2
-rw-r--r--editor/icons/KeyboardError.svg1
-rw-r--r--editor/icons/KeyboardLabel.svg1
-rw-r--r--editor/icons/KeyboardPhysical.svg2
-rw-r--r--editor/input_event_configuration_dialog.cpp156
-rw-r--r--editor/input_event_configuration_dialog.h17
-rw-r--r--editor/plugins/lightmap_gi_editor_plugin.cpp56
-rw-r--r--editor/plugins/voxel_gi_editor_plugin.cpp29
14 files changed, 753 insertions, 74 deletions
diff --git a/editor/action_map_editor.cpp b/editor/action_map_editor.cpp
index cb8d98932d..ae54c20fe2 100644
--- a/editor/action_map_editor.cpp
+++ b/editor/action_map_editor.cpp
@@ -449,10 +449,14 @@ void ActionMapEditor::update_action_list(const Vector<ActionInfo> &p_action_info
// First Column - Icon
Ref<InputEventKey> k = event;
if (k.is_valid()) {
- if (k->get_physical_keycode() == Key::NONE) {
+ if (k->get_physical_keycode() == Key::NONE && k->get_keycode() == Key::NONE && k->get_key_label() != Key::NONE) {
+ event_item->set_icon(0, action_tree->get_theme_icon(SNAME("KeyboardLabel"), SNAME("EditorIcons")));
+ } else if (k->get_keycode() != Key::NONE) {
event_item->set_icon(0, action_tree->get_theme_icon(SNAME("Keyboard"), SNAME("EditorIcons")));
- } else {
+ } else if (k->get_physical_keycode() != Key::NONE) {
event_item->set_icon(0, action_tree->get_theme_icon(SNAME("KeyboardPhysical"), SNAME("EditorIcons")));
+ } else {
+ event_item->set_icon(0, action_tree->get_theme_icon(SNAME("KeyboardError"), SNAME("EditorIcons")));
}
}
diff --git a/editor/editor_data.h b/editor/editor_data.h
index 6a89b3572c..d00501280d 100644
--- a/editor/editor_data.h
+++ b/editor/editor_data.h
@@ -201,7 +201,6 @@ public:
String get_scene_type(int p_idx) const;
void set_scene_path(int p_idx, const String &p_path);
Ref<Script> get_scene_root_script(int p_idx) const;
- void set_edited_scene_version(uint64_t version, int p_scene_idx = -1);
void set_scene_modified_time(int p_idx, uint64_t p_time);
uint64_t get_scene_modified_time(int p_idx) const;
void clear_edited_scenes();
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 19d991b112..c344812065 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -58,6 +58,7 @@
#include "scene/gui/tab_bar.h"
#include "scene/gui/tab_container.h"
#include "scene/main/window.h"
+#include "scene/property_utils.h"
#include "scene/resources/packed_scene.h"
#include "servers/display_server.h"
#include "servers/navigation_server_3d.h"
@@ -997,6 +998,7 @@ void EditorNode::_resources_reimported(const Vector<String> &p_resources) {
for (const String &E : scenes) {
reload_scene(E);
+ reload_instances_with_path_in_edited_scenes(E);
}
scene_tabs->set_current_tab(current_tab);
@@ -2112,6 +2114,7 @@ void EditorNode::edit_item(Object *p_object, Object *p_editing_owner) {
}
}
active_plugins[owner_id].insert(plugin);
+ editor_plugins_over->add_plugin(plugin);
plugin->edit(p_object);
plugin->make_visible(true);
}
@@ -3898,7 +3901,7 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b
Ref<SceneState> state = sdata->get_state();
state->set_path(lpath);
new_scene->set_scene_inherited_state(state);
- new_scene->set_scene_file_path(String());
+ new_scene->set_scene_file_path(lpath);
}
new_scene->set_scene_instance_state(Ref<SceneState>());
@@ -3932,6 +3935,134 @@ 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> modified_property_map;
+
+ List<PropertyInfo> pinfo;
+ p_node->get_property_list(&pinfo);
+ for (const PropertyInfo &E : pinfo) {
+ if (E.usage & PROPERTY_USAGE_STORAGE) {
+ bool is_valid_revert = false;
+ Variant revert_value = EditorPropertyRevert::get_property_revert_value(p_node, E.name, &is_valid_revert);
+ 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;
+ }
+ }
+ }
+ }
+
+ return modified_property_map;
+}
+
+void EditorNode::update_diff_data_for_node(
+ Node *p_edited_scene,
+ Node *p_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 &&
+ p_edited_scene->get_scene_inherited_state().is_valid() &&
+ p_edited_scene->get_scene_inherited_state()->find_node_by_path(p_edited_scene->get_path_to(p_node)) >= 0;
+
+ // 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) {
+ 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);
+
+ // Find all valid connections to other nodes.
+ List<Connection> connections_to;
+ p_node->get_all_signal_connections(&connections_to);
+
+ List<ConnectionWithNodePath> valid_connections_to;
+ for (const Connection &c : connections_to) {
+ Node *connection_target_node = Object::cast_to<Node>(c.callable.get_object());
+ if (connection_target_node) {
+ // TODO: add support for reinstating custom callables
+ if (!c.callable.is_custom()) {
+ ConnectionWithNodePath connection_to;
+ connection_to.connection = c;
+ connection_to.node_path = p_node->get_path_to(connection_target_node);
+ valid_connections_to.push_back(connection_to);
+ }
+ }
+ }
+
+ // Find all valid connections from other nodes.
+ List<Connection> connections_from;
+ p_node->get_signals_connected_to_this(&connections_from);
+
+ List<Connection> valid_connections_from;
+ for (const Connection &c : connections_from) {
+ Node *source_node = Object::cast_to<Node>(c.signal.get_object());
+
+ Node *valid_source_owner = nullptr;
+ if (source_node) {
+ valid_source_owner = source_node->get_owner();
+ while (valid_source_owner) {
+ if (valid_source_owner == p_root) {
+ break;
+ }
+ valid_source_owner = valid_source_owner->get_owner();
+ }
+ }
+
+ if (!source_node || valid_source_owner == nullptr) {
+ // TODO: add support for reinstating custom callables
+ if (!c.callable.is_custom()) {
+ valid_connections_from.push_back(c);
+ }
+ }
+ }
+
+ // Find all node groups.
+ List<Node::GroupInfo> groups;
+ p_node->get_groups(&groups);
+
+ if (!modified_properties.is_empty() || !valid_connections_to.is_empty() || !valid_connections_from.is_empty() || !groups.is_empty()) {
+ ModificationNodeEntry modification_node_entry;
+ modification_node_entry.property_table = modified_properties;
+ modification_node_entry.connections_to = valid_connections_to;
+ 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;
+ }
+ } 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();
+
+ 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());
+ }
+ p_addition_list.push_back(new_additive_node_entry);
+
+ return;
+ }
+
+ 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);
+ }
+}
+//
+
void EditorNode::open_request(const String &p_path) {
if (!opening_prev) {
List<String>::Element *prev_scene_item = previous_scenes.find(p_path);
@@ -5788,6 +5919,352 @@ void EditorNode::reload_scene(const String &p_path) {
scene_tabs->set_current_tab(current_tab);
}
+void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, List<Node *> &p_instance_list) {
+ String scene_file_path = p_node->get_scene_file_path();
+
+ // This is going to get messy...
+ if (p_node->get_scene_file_path() == p_instance_path) {
+ p_instance_list.push_back(p_node);
+ } else {
+ Node *current_node = p_node;
+
+ Ref<SceneState> inherited_state = current_node->get_scene_inherited_state();
+ while (inherited_state.is_valid()) {
+ String inherited_path = inherited_state->get_path();
+ if (inherited_path == p_instance_path) {
+ p_instance_list.push_back(p_node);
+ break;
+ }
+
+ inherited_state = inherited_state->get_base_scene_state();
+ }
+ }
+
+ for (int i = 0; i < p_node->get_child_count(); i++) {
+ Node *child = p_node->get_child(i);
+ find_all_instances_inheriting_path_in_node(p_root, child, p_instance_path, p_instance_list);
+ }
+}
+
+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;
+
+ // Walk through each opened scene to get a global list of all instances which match
+ // the current reimported scenes.
+ for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
+ if (editor_data.get_scene_path(i) != p_instance_path) {
+ Node *edited_scene_root = editor_data.get_edited_scene_root(i);
+
+ if (edited_scene_root) {
+ List<Node *> valid_nodes;
+ find_all_instances_inheriting_path_in_node(edited_scene_root, edited_scene_root, p_instance_path, valid_nodes);
+ if (valid_nodes.size() > 0) {
+ edited_scene_map[i] = valid_nodes;
+ }
+ }
+ }
+ }
+
+ if (edited_scene_map.size() > 0) {
+ // Reload the new instance.
+ Error err;
+ Ref<PackedScene> instance_scene_packed_scene = ResourceLoader::load(p_instance_path, "", ResourceFormatLoader::CACHE_MODE_IGNORE, &err);
+ instance_scene_packed_scene->set_path(p_instance_path, true);
+
+ ERR_FAIL_COND(err != OK);
+ ERR_FAIL_COND(instance_scene_packed_scene.is_null());
+
+ HashMap<String, Ref<PackedScene>> local_scene_cache;
+ local_scene_cache[p_instance_path] = instance_scene_packed_scene;
+
+ for (const KeyValue<int, List<Node *>> &edited_scene_map_elem : edited_scene_map) {
+ // Set the current scene.
+ int current_scene_idx = edited_scene_map_elem.key;
+ editor_data.set_edited_scene(current_scene_idx);
+ Node *current_edited_scene = editor_data.get_edited_scene_root(current_scene_idx);
+
+ // Clear the history for this tab (should we allow history to be retained?).
+ EditorUndoRedoManager::get_singleton()->clear_history();
+
+ // Update the version
+ editor_data.is_scene_changed(current_scene_idx);
+
+ 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);
+
+ // Disconnect all relevant connections, all connections from and persistent connections to.
+ for (const KeyValue<NodePath, ModificationNodeEntry> &modification_table_entry : modification_table) {
+ for (Connection conn : modification_table_entry.value.connections_from) {
+ conn.signal.get_object()->disconnect(conn.signal.get_name(), conn.callable);
+ }
+ for (ConnectionWithNodePath cwnp : modification_table_entry.value.connections_to) {
+ Connection conn = cwnp.connection;
+ if (conn.flags & CONNECT_PERSIST) {
+ conn.signal.get_object()->disconnect(conn.signal.get_name(), conn.callable);
+ }
+ }
+ }
+
+ // Store all the paths for any selected nodes which are ancestors of the node we're replacing.
+ List<NodePath> selected_node_paths;
+ for (Node *selected_node : editor_selection->get_selected_node_list()) {
+ if (selected_node == original_node || original_node->is_ancestor_of(selected_node)) {
+ selected_node_paths.push_back(original_node->get_path_to(selected_node));
+ editor_selection->remove_node(selected_node);
+ }
+ }
+
+ // Remove all nodes which were added as additional elements (they will be restored later).
+ for (AdditiveNodeEntry additive_node_entry : addition_list) {
+ Node *addition_node = additive_node_entry.node;
+ addition_node->get_parent()->remove_child(addition_node);
+ }
+
+ // Clear ownership of the nodes (kind of hack to workaround an issue with
+ // replace_by when called on nodes in other tabs).
+ List<Node *> nodes_owned_by_original_node;
+ original_node->get_owned_by(original_node, &nodes_owned_by_original_node);
+ for (Node *owned_node : nodes_owned_by_original_node) {
+ owned_node->set_owner(nullptr);
+ }
+
+ // Delete all the remaining node children.
+ while (original_node->get_child_count()) {
+ Node *child = original_node->get_child(0);
+
+ original_node->remove_child(child);
+ child->queue_free();
+ }
+
+ // Reset the editable instance state.
+ bool is_editable = true;
+ Node *owner = original_node->get_owner();
+ if (owner) {
+ is_editable = owner->is_editable_instance(original_node);
+ }
+
+ // Load a replacement scene for the node.
+ Ref<PackedScene> current_packed_scene;
+ if (original_node->get_scene_file_path() == p_instance_path) {
+ // If the node file name directly matches the scene we're replacing,
+ // just load it since we already cached it.
+ current_packed_scene = instance_scene_packed_scene;
+ } else {
+ // Otherwise, check the inheritance chain, reloading and caching any scenes
+ // we require along the way.
+ List<String> required_load_paths;
+ String scene_path = original_node->get_scene_file_path();
+ // Do we need to check if the paths are empty?
+ if (!scene_path.is_empty()) {
+ required_load_paths.push_front(scene_path);
+ }
+ Ref<SceneState> inherited_state = original_node->get_scene_inherited_state();
+ while (inherited_state.is_valid()) {
+ String inherited_path = inherited_state->get_path();
+ // Do we need to check if the paths are empty?
+ if (!inherited_path.is_empty()) {
+ required_load_paths.push_front(inherited_path);
+ }
+ inherited_state = inherited_state->get_base_scene_state();
+ }
+
+ // Ensure the inheritance chain is loaded in the correct order so that cache can
+ // be properly updated.
+ for (String path : required_load_paths) {
+ if (!local_scene_cache.find(path)) {
+ current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_IGNORE, &err);
+ current_packed_scene->set_path(path, true);
+ local_scene_cache[path] = current_packed_scene;
+ } else {
+ current_packed_scene = local_scene_cache[path];
+ }
+ }
+ }
+
+ ERR_FAIL_COND(current_packed_scene.is_null());
+
+ // Instantiate the node.
+ Node *instantiated_node = nullptr;
+ if (current_packed_scene.is_valid()) {
+ instantiated_node = current_packed_scene->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
+ }
+
+ ERR_FAIL_COND(!instantiated_node);
+
+ 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();
+
+ // Update the name to match
+ instantiated_node->set_name(original_node->get_name());
+
+ // Is this replacing the edited root node?
+ String original_node_file_path = original_node->get_scene_file_path();
+
+ if (current_edited_scene == original_node) {
+ instantiated_node->set_scene_instance_state(original_node->get_scene_instance_state());
+ // Fix unsaved inherited scene
+ if (original_node_file_path.is_empty()) {
+ Ref<SceneState> state = current_packed_scene->get_state();
+ state->set_path(current_packed_scene->get_path());
+ instantiated_node->set_scene_inherited_state(state);
+ }
+ editor_data.set_edited_scene_root(instantiated_node);
+ current_edited_scene = instantiated_node;
+
+ if (original_node->is_inside_tree()) {
+ SceneTreeDock::get_singleton()->set_edited_scene(current_edited_scene);
+ original_node->get_tree()->set_edited_scene_root(instantiated_node);
+ }
+ }
+
+ // Replace the original node with the instantiated version.
+ original_node->replace_by(instantiated_node, false);
+
+ // Mark the old node for deletion.
+ original_node->queue_free();
+
+ // Restore the folded and placeholder state from the original node.
+ instantiated_node->set_display_folded(original_node_is_displayed_folded);
+ instantiated_node->set_scene_instance_load_placeholder(original_node_scene_instance_load_placeholder);
+
+ if (owner) {
+ Ref<SceneState> ss_inst = owner->get_scene_instance_state();
+ if (ss_inst.is_valid()) {
+ ss_inst->update_instance_resource(p_instance_path, current_packed_scene);
+ }
+
+ owner->set_editable_instance(instantiated_node, is_editable);
+ }
+
+ // Attempt to re-add all the additional nodes.
+ for (AdditiveNodeEntry additive_node_entry : addition_list) {
+ Node *parent_node = instantiated_node->get_node_or_null(additive_node_entry.parent);
+
+ if (!parent_node) {
+ parent_node = current_edited_scene;
+ }
+
+ parent_node->add_child(additive_node_entry.node);
+ parent_node->move_child(additive_node_entry.node, additive_node_entry.index);
+ // If the additive node's owner was the node which got replaced, update it.
+ if (additive_node_entry.owner == original_node) {
+ additive_node_entry.owner = instantiated_node;
+ }
+
+ additive_node_entry.node->set_owner(additive_node_entry.owner);
+
+ // If the parent node was lost, attempt to restore the original global transform.
+ {
+ Node2D *node_2d = Object::cast_to<Node2D>(additive_node_entry.node);
+ if (node_2d) {
+ node_2d->set_transform(additive_node_entry.transform_2d);
+ }
+
+ Node3D *node_3d = Object::cast_to<Node3D>(additive_node_entry.node);
+ if (node_3d) {
+ node_3d->set_transform(additive_node_entry.transform_3d);
+ }
+ }
+ }
+
+ // Restore the selection.
+ if (selected_node_paths.size()) {
+ for (NodePath selected_node_path : selected_node_paths) {
+ Node *selected_node = instantiated_node->get_node_or_null(selected_node_path);
+ if (selected_node) {
+ editor_selection->add_node(selected_node);
+ }
+ }
+ editor_selection->update();
+ }
+
+ // Attempt to restore the modified properties and signals for the instantitated node and all its owned children.
+ for (KeyValue<NodePath, ModificationNodeEntry> &E : modification_table) {
+ 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 efficent).
+ 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 targetting.
+ 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();
+
+ if (source_object) {
+ ERR_FAIL_COND(source_object->connect(conn.signal.get_name(), Callable(modifiable_node, conn.callable.get_method()), conn.flags) != OK);
+ }
+ }
+
+ // Re-add the groups.
+ for (const Node::GroupInfo &E2 : E.value.groups) {
+ modifiable_node->add_to_group(E2.name, E2.persistent);
+ }
+ }
+ }
+ }
+ // Cleanup the history of the changes.
+ editor_history.cleanup_history();
+
+ current_edited_scene->propagate_notification(NOTIFICATION_NODE_RECACHE_REQUESTED);
+ }
+ edited_scene_map.clear();
+ }
+ editor_data.set_edited_scene(original_edited_scene_idx);
+
+ _edit_current();
+}
+
int EditorNode::plugin_init_callback_count = 0;
void EditorNode::add_plugin_init_callback(EditorPluginInitializeCallback p_callback) {
diff --git a/editor/editor_node.h b/editor/editor_node.h
index ef4ea34da2..bb10abb589 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -87,6 +87,7 @@ class ImportDock;
class LinkButton;
class MenuBar;
class MenuButton;
+class Node2D;
class NodeDock;
class OptionButton;
class OrphanResourcesDialog;
@@ -818,6 +819,37 @@ 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);
+
+ struct AdditiveNodeEntry {
+ Node *node = nullptr;
+ NodePath parent = NodePath();
+ Node *owner = nullptr;
+ int index = 0;
+ // Used if the original parent node is lost
+ Transform2D transform_2d;
+ Transform3D transform_3d;
+ };
+
+ struct ConnectionWithNodePath {
+ Connection connection;
+ NodePath node_path;
+ };
+
+ struct ModificationNodeEntry {
+ HashMap<StringName, Variant> property_table;
+ List<ConnectionWithNodePath> connections_to;
+ List<Connection> connections_from;
+ List<Node::GroupInfo> groups;
+ };
+
+ void update_diff_data_for_node(
+ Node *p_edited_scene,
+ Node *p_root,
+ Node *p_node,
+ HashMap<NodePath, ModificationNodeEntry> &p_modification_table,
+ List<AdditiveNodeEntry> &p_addition_list);
+
bool is_scene_open(const String &p_path);
void set_current_scene(int p_idx);
@@ -870,6 +902,9 @@ public:
void reload_scene(const String &p_path);
+ void find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, List<Node *> &p_instance_list);
+ void reload_instances_with_path_in_edited_scenes(const String &p_path);
+
bool is_exiting() const { return exiting; }
Button *get_pause_button() { return pause_button; }
diff --git a/editor/event_listener_line_edit.cpp b/editor/event_listener_line_edit.cpp
index 274fe34c52..ea4a7133bf 100644
--- a/editor/event_listener_line_edit.cpp
+++ b/editor/event_listener_line_edit.cpp
@@ -59,16 +59,42 @@ static const char *_joy_axis_descriptions[(size_t)JoyAxis::MAX * 2] = {
String EventListenerLineEdit::get_event_text(const Ref<InputEvent> &p_event, bool p_include_device) {
ERR_FAIL_COND_V_MSG(p_event.is_null(), String(), "Provided event is not a valid instance of InputEvent");
- String text = p_event->as_text();
-
+ String text;
Ref<InputEventKey> key = p_event;
- if (key.is_valid() && key->is_command_or_control_autoremap()) {
+ if (key.is_valid()) {
+ String mods_text = key->InputEventWithModifiers::as_text();
+ mods_text = mods_text.is_empty() ? mods_text : mods_text + "+";
+ if (key->is_command_or_control_autoremap()) {
#ifdef MACOS_ENABLED
- text = text.replace("Command", "Command/Ctrl");
+ mods_text = mods_text.replace("Command", "Command/Ctrl");
#else
- text = text.replace("Ctrl", "Command/Ctrl");
+ mods_text = mods_text.replace("Ctrl", "Command/Ctrl");
#endif
+ }
+
+ if (key->get_keycode() != Key::NONE) {
+ text += mods_text + keycode_get_string(key->get_keycode());
+ }
+ if (key->get_physical_keycode() != Key::NONE) {
+ if (!text.is_empty()) {
+ text += " or ";
+ }
+ text += mods_text + keycode_get_string(key->get_physical_keycode()) + " (" + RTR("Physical") + ")";
+ }
+ if (key->get_key_label() != Key::NONE) {
+ if (!text.is_empty()) {
+ text += " or ";
+ }
+ text += mods_text + keycode_get_string(key->get_key_label()) + " (Unicode)";
+ }
+
+ if (text.is_empty()) {
+ text = "(" + RTR("Unset") + ")";
+ }
+ } else {
+ text = p_event->as_text();
}
+
Ref<InputEventMouse> mouse = p_event;
Ref<InputEventJoypadMotion> jp_motion = p_event;
Ref<InputEventJoypadButton> jp_button = p_event;
diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp
index 8da52e20f7..27ca0b84a1 100644
--- a/editor/export/editor_export_platform.cpp
+++ b/editor/export/editor_export_platform.cpp
@@ -1294,7 +1294,9 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
} else {
// Use default text server data.
String icu_data_file = EditorPaths::get_singleton()->get_cache_dir().path_join("tmp_icu_data");
- TS->save_support_data(icu_data_file);
+ if (!TS->save_support_data(icu_data_file)) {
+ return ERR_INVALID_DATA;
+ }
Vector<uint8_t> array = FileAccess::get_file_as_bytes(icu_data_file);
err = p_func(p_udata, ts_data, array, idx, total, enc_in_filters, enc_ex_filters, key);
DirAccess::remove_file_or_error(icu_data_file);
diff --git a/editor/icons/Keyboard.svg b/editor/icons/Keyboard.svg
index b9dfab71ed..b6d963f9d7 100644
--- a/editor/icons/Keyboard.svg
+++ b/editor/icons/Keyboard.svg
@@ -1 +1 @@
-<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill-opacity=".996"><path d="m4 2a1 1 0 0 0 -1 1v9.084c0 .506.448.916 1 .916h8c.552 0 1-.41 1-.916v-9.084a1 1 0 0 0 -1-1zm1.543 1.139h1.393l1.834 4.199h1.295v.437c.708.052 1.246.239 1.61.559.368.316.55.747.55 1.295 0 .552-.182.99-.55 1.314-.368.32-.906.505-1.61.553v.467h-1.294v-.473c-.708-.06-1.247-.248-1.615-.564-.364-.316-.545-.75-.545-1.297 0-.548.181-.977.545-1.29.368-.315.907-.504 1.615-.564v-.437h-1.464l-.282-.733h-1.595l-.284.733h-1.439l1.836-4.2zm.684 1.39-.409 1.057h.817zm3.84 4.338v1.526c.28-.04.483-.12.607-.24.124-.125.185-.302.185-.53 0-.224-.063-.396-.191-.516-.124-.12-.326-.2-.602-.24zm-1.296.006c-.284.04-.487.12-.61.24-.12.116-.182.288-.182.516 0 .22.065.392.193.512.132.12.331.202.6.246v-1.514z" fill="#e0e0e0"/><path d="m27 2h7v14h-7z" fill="#fff"/><path d="m1 4v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-9h-1v9a1 1 0 0 1 -1 1h-10a1 1 0 0 1 -1-1v-9z" fill="#e0e0e0"/></g></svg>
+<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="16"><path d="M6.584 5.135 6.17 4.059l-.412 1.076h.826zm3.203 2.976a1.032 1.032 0 0 0-.287.041.953.953 0 0 0-.09.034c-.028.012-.057.024-.084.039a.912.912 0 0 0-.152.107.988.988 0 0 0-.23.305.937.937 0 0 0-.04.097 1.017 1.017 0 0 0-.068.323 1.553 1.553 0 0 0 0 .244 1.374 1.374 0 0 0 .068.328 1.03 1.03 0 0 0 .201.336c.023.022.045.044.069.064a.96.96 0 0 0 .152.104c.027.015.056.027.084.039.03.012.06.024.09.033a.965.965 0 0 0 .19.035 1.028 1.028 0 0 0 .197 0 .974.974 0 0 0 .36-.107.876.876 0 0 0 .077-.049.872.872 0 0 0 .074-.055.882.882 0 0 0 .13-.136.978.978 0 0 0 .177-.368 1.225 1.225 0 0 0 .035-.224 1.61 1.61 0 0 0 0-.244 1.361 1.361 0 0 0-.035-.223 1.092 1.092 0 0 0-.121-.285.87.87 0 0 0-.12-.15.862.862 0 0 0-.14-.124c-.025-.017-.05-.035-.078-.05-.027-.015-.056-.027-.086-.04a.892.892 0 0 0-.373-.074z" style="fill-opacity:.99607843;fill:#e0e0e0"/><path d="M4 2c-.616-.02-1.084.59-1 1.178.003 3-.007 6 .005 9 .057.577.672.889 1.203.822 2.631-.003 5.263.006 7.894-.005a.973.973 0 0 0 .898-1.09c-.003-3.002.007-6.005-.005-9.007-.042-.592-.643-.976-1.203-.898H4Zm1.475.646H6.89l1.865 4.27H7.268l-.286-.744H5.36l-.287.744H3.61l1.866-4.27Zm4.312 4.301c1.069-.042 2.164.679 2.363 1.766.232 1.01-.34 2.144-1.326 2.502.296.465.837-.109 1.06-.007l.544.642c-.64.756-1.883.677-2.605.084-.394-.448-.866-.673-1.409-.887-1.175-.66-1.391-2.456-.43-3.39.463-.486 1.141-.716 1.803-.71Z" style="fill:#e0e0e0;fill-opacity:.996078"/><path fill="#e0e0e0" d="M1 4v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V4h-1v9a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4Z" style="fill-opacity:.996"/></svg>
diff --git a/editor/icons/KeyboardError.svg b/editor/icons/KeyboardError.svg
new file mode 100644
index 0000000000..e20d133155
--- /dev/null
+++ b/editor/icons/KeyboardError.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="16"><path d="M4 2c-.616-.02-1.084.59-1 1.178.003 3-.007 6 .005 9 .057.577.672.889 1.203.822 2.631-.003 5.263.006 7.894-.005a.973.973 0 0 0 .898-1.09c-.003-3.002.007-6.005-.005-9.007-.042-.592-.643-.976-1.203-.898H4Zm4.223 1.262c1.06.005 2.29.257 2.92 1.197.532.862.275 2.057-.484 2.703-.346.382-.862.629-1.075 1.117.055.345-.33.172-.537.213H7.148c-.037-.749.503-1.335 1.026-1.796.406-.253.744-1.002.129-1.22-.626-.25-1.374.117-1.645.715l-2.08-1.039c.599-1.147 1.868-1.818 3.136-1.872.17-.013.339-.018.509-.018Zm.127 5.697c.798-.057 1.616.616 1.54 1.45-.023.81-.841 1.413-1.623 1.328-.833.022-1.6-.771-1.443-1.613.097-.721.83-1.195 1.526-1.165z" style="fill:#ff5f5f;fill-opacity:1"/><path fill="#e0e0e0" d="M1 4v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V4h-1v9a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4Z" style="fill-opacity:1;fill:#ff5f5f"/></svg>
diff --git a/editor/icons/KeyboardLabel.svg b/editor/icons/KeyboardLabel.svg
new file mode 100644
index 0000000000..07a687f447
--- /dev/null
+++ b/editor/icons/KeyboardLabel.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M11.814 1.977c-.078 0-.157.008-.232.023H4c-.7-.034-1.143.765-1 1.39.008 2.95-.017 5.9.014 8.848.13.705.965.847 1.562.76 2.542-.008 5.085.02 7.627-.014.734-.1.9-.952.797-1.564-.003-2.84.006-5.68-.006-8.522-.035-.594-.628-.927-1.18-.921zM9.797 4.016l.572.58-.572.578-.57-.578.57-.58zm-.606 1.05.594.606-.594.601-.591-.601.591-.606zm1.213 0 .596.606-.596.601-.593-.601.593-.606zm.717 1.436h1.06c.053.217.093.428.122.63.028.201.043.395.043.58a2.363 2.363 0 0 1-.133.724 1.425 1.425 0 0 1-.31.515c-.249.265-.598.399-1.05.399-.331 0-.594-.08-.785-.235a1.091 1.091 0 0 1-.236-.275c-.063.1-.14.19-.236.265-.206.163-.467.245-.787.245-.252 0-.457-.057-.614-.166a2.75 2.75 0 0 1-.095.42 1.936 1.936 0 0 1-.403.722c-.2.22-.452.383-.756.49-.303.11-.654.166-1.052.166-.466 0-.865-.089-1.2-.265a1.817 1.817 0 0 1-.765-.752c-.18-.327-.27-.715-.27-1.164 0-.256.027-.525.082-.809.055-.284.126-.545.21-.781h1.001c-.062.232-.112.46-.15.684a3.87 3.87 0 0 0-.053.613c0 .37.1.643.3.82.204.177.523.264.96.264.222 0 .425-.03.61-.092a.97.97 0 0 0 .439-.299.803.803 0 0 0 .166-.521 5.463 5.463 0 0 0-.051-.725 11.61 11.61 0 0 0-.068-.482 26.51 26.51 0 0 0-.096-.606h1.135l.043.276c.047.32.123.532.228.634.105.1.24.15.402.15.165 0 .284-.04.358-.124.076-.086.115-.224.115-.412v-.524h1.027v.524c0 .203.032.351.096.447.065.095.202.142.412.142a.637.637 0 0 0 .33-.078c.089-.052.133-.15.133-.297 0-.128-.019-.295-.054-.498l-.108-.605z" style="fill:#e0e0e0;fill-opacity:.996078"/><path fill="#e0e0e0" d="M1 4v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V4h-1v9a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4Z" style="fill-opacity:.996"/></svg>
diff --git a/editor/icons/KeyboardPhysical.svg b/editor/icons/KeyboardPhysical.svg
index 4364e0b4fa..7d4b7e2999 100644
--- a/editor/icons/KeyboardPhysical.svg
+++ b/editor/icons/KeyboardPhysical.svg
@@ -1 +1 @@
-<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill-opacity=".996"><path d="m4 2a1 1 0 0 0 -1 1v9.084c0 .506.448.916 1 .916h8c.552 0 1-.41 1-.916v-9.084a1 1 0 0 0 -1-1zm2.762 1.768h2.476l3.264 7.464h-2.604l-.502-1.3h-2.835l-.502 1.3h-2.561zm1.217 2.474-.725 1.878h1.45z" fill="#e0e0e0"/><path d="m27 2h7v14h-7z" fill="#fff"/><path d="m1 4v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-9h-1v9a1 1 0 0 1 -1 1h-10a1 1 0 0 1 -1-1v-9z" fill="#e0e0e0"/></g></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M4 2a1 1 0 0 0-1 1v9.084c0 .506.448.916 1 .916h8c.552 0 1-.41 1-.916V3a1 1 0 0 0-1-1Zm2.762 1.768h2.476l3.264 7.464H9.898l-.502-1.3H6.561l-.502 1.3H3.498Zm1.217 2.474L7.254 8.12h1.45z" style="fill-opacity:.996"/><path fill="#e0e0e0" d="M1 4v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V4h-1v9a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4Z" style="fill-opacity:.996"/></svg>
diff --git a/editor/input_event_configuration_dialog.cpp b/editor/input_event_configuration_dialog.cpp
index 4f7d99c567..48e3759e57 100644
--- a/editor/input_event_configuration_dialog.cpp
+++ b/editor/input_event_configuration_dialog.cpp
@@ -38,9 +38,10 @@
#include "scene/gui/separator.h"
#include "scene/gui/tree.h"
-void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, bool p_update_input_list_selection) {
+void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, const Ref<InputEvent> &p_original_event, bool p_update_input_list_selection) {
if (p_event.is_valid()) {
event = p_event;
+ original_event = p_original_event;
// If the event is changed to something which is not the same as the listener,
// clear out the event from the listener text box to avoid confusion.
@@ -61,7 +62,7 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, b
// Update option values and visibility
bool show_mods = false;
bool show_device = false;
- bool show_phys_key = false;
+ bool show_key = false;
if (mod.is_valid()) {
show_mods = true;
@@ -74,9 +75,25 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, b
}
if (k.is_valid()) {
- show_phys_key = true;
- physical_key_checkbox->set_pressed(k->get_physical_keycode() != Key::NONE && k->get_keycode() == Key::NONE);
-
+ show_key = true;
+ if (k->get_keycode() == Key::NONE && k->get_physical_keycode() == Key::NONE && k->get_key_label() != Key::NONE) {
+ key_mode->select(KEYMODE_UNICODE);
+ } else if (k->get_keycode() != Key::NONE) {
+ key_mode->select(KEYMODE_KEYCODE);
+ } else if (k->get_physical_keycode() != Key::NONE) {
+ key_mode->select(KEYMODE_PHY_KEYCODE);
+ } else {
+ // Invalid key.
+ event = Ref<InputEvent>();
+ original_event = Ref<InputEvent>();
+ event_listener->clear_event();
+ event_as_text->set_text(TTR("No Event Configured"));
+
+ additional_options_container->hide();
+ input_list_tree->deselect_all();
+ _update_input_list();
+ return;
+ }
} else if (joyb.is_valid() || joym.is_valid() || mb.is_valid()) {
show_device = true;
_set_current_device(event->get_device());
@@ -84,11 +101,20 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, b
mod_container->set_visible(show_mods);
device_container->set_visible(show_device);
- physical_key_checkbox->set_visible(show_phys_key);
+ key_mode->set_visible(show_key);
additional_options_container->show();
+ // Update mode selector based on original key event.
+ Ref<InputEventKey> ko = p_original_event;
+ if (ko.is_valid()) {
+ key_mode->set_item_disabled(KEYMODE_KEYCODE, ko->get_keycode() == Key::NONE);
+ key_mode->set_item_disabled(KEYMODE_PHY_KEYCODE, ko->get_physical_keycode() == Key::NONE);
+ key_mode->set_item_disabled(KEYMODE_UNICODE, ko->get_key_label() == Key::NONE);
+ }
+
// Update selected item in input list.
if (p_update_input_list_selection && (k.is_valid() || joyb.is_valid() || joym.is_valid() || mb.is_valid())) {
+ in_tree_update = true;
TreeItem *category = input_list_tree->get_root()->get_first_child();
while (category) {
TreeItem *input_item = category->get_first_child();
@@ -97,6 +123,7 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, b
// input_type should always be > 0, unless the tree structure has been misconfigured.
int input_type = input_item->get_parent()->get_meta("__type", 0);
if (input_type == 0) {
+ in_tree_update = false;
return;
}
@@ -112,6 +139,7 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, b
category->set_collapsed(false);
input_item->select(0);
input_list_tree->ensure_cursor_is_visible();
+ in_tree_update = false;
return;
}
input_item = input_item->get_next();
@@ -122,10 +150,12 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, b
category->set_collapsed(true); // Event not in this category, so collapse;
category = category->get_next();
}
+ in_tree_update = false;
}
} else {
// Event is not valid, reset dialog
- event = p_event;
+ event = Ref<InputEvent>();
+ original_event = Ref<InputEvent>();
event_listener->clear_event();
event_as_text->set_text(TTR("No Event Configured"));
@@ -141,8 +171,10 @@ void InputEventConfigurationDialog::_on_listen_input_changed(const Ref<InputEven
return;
}
- // Create an editable reference
+ // Create an editable reference and a copy of full event.
Ref<InputEvent> received_event = p_event;
+ Ref<InputEvent> received_original_event = received_event->duplicate();
+
// Check what the type is and if it is allowed.
Ref<InputEventKey> k = received_event;
Ref<InputEventJoypadButton> joyb = received_event;
@@ -169,12 +201,16 @@ void InputEventConfigurationDialog::_on_listen_input_changed(const Ref<InputEven
}
if (k.is_valid()) {
- k->set_pressed(false); // To avoid serialization of 'pressed' property - doesn't matter for actions anyway.
- // Maintain physical keycode option state
- if (physical_key_checkbox->is_pressed()) {
+ k->set_pressed(false); // To avoid serialisation of 'pressed' property - doesn't matter for actions anyway.
+ if (key_mode->get_selected_id() == KEYMODE_KEYCODE) {
+ k->set_physical_keycode(Key::NONE);
+ k->set_key_label(Key::NONE);
+ } else if (key_mode->get_selected_id() == KEYMODE_PHY_KEYCODE) {
k->set_keycode(Key::NONE);
- } else {
+ k->set_key_label(Key::NONE);
+ } else if (key_mode->get_selected_id() == KEYMODE_UNICODE) {
k->set_physical_keycode(Key::NONE);
+ k->set_keycode(Key::NONE);
}
}
@@ -186,7 +222,7 @@ void InputEventConfigurationDialog::_on_listen_input_changed(const Ref<InputEven
// Maintain device selection.
received_event->set_device(_get_current_device());
- _set_event(received_event);
+ _set_event(received_event, received_original_event);
}
void InputEventConfigurationDialog::_on_listen_focus_changed() {
@@ -326,14 +362,14 @@ void InputEventConfigurationDialog::_mod_toggled(bool p_checked, int p_index) {
}
}
- _set_event(ie);
+ _set_event(ie, original_event);
}
void InputEventConfigurationDialog::_autoremap_command_or_control_toggled(bool p_checked) {
Ref<InputEventWithModifiers> ie = event;
if (ie.is_valid()) {
ie->set_command_or_control_autoremap(p_checked);
- _set_event(ie);
+ _set_event(ie, original_event);
}
if (p_checked) {
@@ -345,27 +381,38 @@ void InputEventConfigurationDialog::_autoremap_command_or_control_toggled(bool p
}
}
-void InputEventConfigurationDialog::_physical_keycode_toggled(bool p_checked) {
+void InputEventConfigurationDialog::_key_mode_selected(int p_mode) {
Ref<InputEventKey> k = event;
-
- if (k.is_null()) {
+ Ref<InputEventKey> ko = original_event;
+ if (k.is_null() || ko.is_null()) {
return;
}
- if (p_checked) {
- k->set_physical_keycode(k->get_keycode());
+ if (key_mode->get_selected_id() == KEYMODE_KEYCODE) {
+ k->set_keycode(ko->get_keycode());
+ k->set_physical_keycode(Key::NONE);
+ k->set_key_label(Key::NONE);
+ } else if (key_mode->get_selected_id() == KEYMODE_PHY_KEYCODE) {
k->set_keycode(Key::NONE);
- } else {
- k->set_keycode((Key)k->get_physical_keycode());
+ k->set_physical_keycode(ko->get_physical_keycode());
+ k->set_key_label(Key::NONE);
+ } else if (key_mode->get_selected_id() == KEYMODE_UNICODE) {
k->set_physical_keycode(Key::NONE);
+ k->set_keycode(Key::NONE);
+ k->set_key_label(ko->get_key_label());
}
- _set_event(k);
+ _set_event(k, original_event);
}
void InputEventConfigurationDialog::_input_list_item_selected() {
TreeItem *selected = input_list_tree->get_selected();
+ // Called form _set_event, do not update for a second time.
+ if (in_tree_update) {
+ return;
+ }
+
// Invalid tree selection - type only exists on the "category" items, which are not a valid selection.
if (selected->has_meta("__type")) {
return;
@@ -379,15 +426,11 @@ void InputEventConfigurationDialog::_input_list_item_selected() {
Ref<InputEventKey> k;
k.instantiate();
- if (physical_key_checkbox->is_pressed()) {
- k->set_physical_keycode(keycode);
- k->set_keycode(Key::NONE);
- } else {
- k->set_physical_keycode(Key::NONE);
- k->set_keycode(keycode);
- }
+ k->set_physical_keycode(keycode);
+ k->set_keycode(keycode);
+ k->set_key_label(keycode);
- // Maintain modifier state from checkboxes
+ // Maintain modifier state from checkboxes.
k->set_alt_pressed(mod_checkboxes[MOD_ALT]->is_pressed());
k->set_shift_pressed(mod_checkboxes[MOD_SHIFT]->is_pressed());
if (autoremap_command_or_control_checkbox->is_pressed()) {
@@ -397,7 +440,23 @@ void InputEventConfigurationDialog::_input_list_item_selected() {
k->set_meta_pressed(mod_checkboxes[MOD_META]->is_pressed());
}
- _set_event(k, false);
+ Ref<InputEventKey> ko = k->duplicate();
+
+ if (key_mode->get_selected_id() == KEYMODE_UNICODE) {
+ key_mode->select(KEYMODE_PHY_KEYCODE);
+ }
+
+ if (key_mode->get_selected_id() == KEYMODE_KEYCODE) {
+ k->set_physical_keycode(Key::NONE);
+ k->set_keycode(keycode);
+ k->set_key_label(Key::NONE);
+ } else if (key_mode->get_selected_id() == KEYMODE_PHY_KEYCODE) {
+ k->set_physical_keycode(keycode);
+ k->set_keycode(Key::NONE);
+ k->set_key_label(Key::NONE);
+ }
+
+ _set_event(k, ko, false);
} break;
case INPUT_MOUSE_BUTTON: {
MouseButton idx = (MouseButton)(int)selected->get_meta("__index");
@@ -417,7 +476,7 @@ void InputEventConfigurationDialog::_input_list_item_selected() {
// Maintain selected device
mb->set_device(_get_current_device());
- _set_event(mb, false);
+ _set_event(mb, mb, false);
} break;
case INPUT_JOY_BUTTON: {
JoyButton idx = (JoyButton)(int)selected->get_meta("__index");
@@ -426,7 +485,7 @@ void InputEventConfigurationDialog::_input_list_item_selected() {
// Maintain selected device
jb->set_device(_get_current_device());
- _set_event(jb, false);
+ _set_event(jb, jb, false);
} break;
case INPUT_JOY_MOTION: {
JoyAxis axis = (JoyAxis)(int)selected->get_meta("__axis");
@@ -440,7 +499,7 @@ void InputEventConfigurationDialog::_input_list_item_selected() {
// Maintain selected device
jm->set_device(_get_current_device());
- _set_event(jm, false);
+ _set_event(jm, jm, false);
} break;
}
}
@@ -470,7 +529,9 @@ void InputEventConfigurationDialog::_notification(int p_what) {
case NOTIFICATION_THEME_CHANGED: {
input_list_search->set_right_icon(input_list_search->get_theme_icon(SNAME("Search"), SNAME("EditorIcons")));
- physical_key_checkbox->set_icon(get_theme_icon(SNAME("KeyboardPhysical"), SNAME("EditorIcons")));
+ key_mode->set_item_icon(KEYMODE_KEYCODE, get_theme_icon(SNAME("Keyboard"), SNAME("EditorIcons")));
+ key_mode->set_item_icon(KEYMODE_PHY_KEYCODE, get_theme_icon(SNAME("KeyboardPhysical"), SNAME("EditorIcons")));
+ key_mode->set_item_icon(KEYMODE_UNICODE, get_theme_icon(SNAME("KeyboardLabel"), SNAME("EditorIcons")));
icon_cache.keyboard = get_theme_icon(SNAME("Keyboard"), SNAME("EditorIcons"));
icon_cache.mouse = get_theme_icon(SNAME("Mouse"), SNAME("EditorIcons"));
@@ -484,22 +545,22 @@ void InputEventConfigurationDialog::_notification(int p_what) {
void InputEventConfigurationDialog::popup_and_configure(const Ref<InputEvent> &p_event) {
if (p_event.is_valid()) {
- _set_event(p_event);
+ _set_event(p_event, p_event->duplicate());
} else {
// Clear Event
- _set_event(p_event);
+ _set_event(Ref<InputEvent>(), Ref<InputEvent>());
// Clear Checkbox Values
for (int i = 0; i < MOD_MAX; i++) {
mod_checkboxes[i]->set_pressed(false);
}
- // Enable the Physical Key checkbox by default to encourage its use.
+ // Enable the Physical Key by default to encourage its use.
// Physical Key should be used for most game inputs as it allows keys to work
// on non-QWERTY layouts out of the box.
// This is especially important for WASD movement layouts.
- physical_key_checkbox->set_pressed(true);
+ key_mode->select(KEYMODE_PHY_KEYCODE);
autoremap_command_or_control_checkbox->set_pressed(false);
// Select "All Devices" by default.
@@ -621,14 +682,15 @@ InputEventConfigurationDialog::InputEventConfigurationDialog() {
mod_container->hide();
additional_options_container->add_child(mod_container);
- // Physical Key Checkbox
+ // Key Mode Selection
- physical_key_checkbox = memnew(CheckBox);
- physical_key_checkbox->set_text(TTR("Use Physical Keycode"));
- physical_key_checkbox->set_tooltip_text(TTR("Stores the physical position of the key on the keyboard rather than the key's value. Used for compatibility with non-latin layouts.\nThis should generally be enabled for most game shortcuts, but not in non-game applications."));
- physical_key_checkbox->connect("toggled", callable_mp(this, &InputEventConfigurationDialog::_physical_keycode_toggled));
- physical_key_checkbox->hide();
- additional_options_container->add_child(physical_key_checkbox);
+ key_mode = memnew(OptionButton);
+ key_mode->add_item("Keycode (Latin equvialent)", KEYMODE_KEYCODE);
+ key_mode->add_item("Physical Keycode (poistion of US QWERTY keyboard)", KEYMODE_PHY_KEYCODE);
+ key_mode->add_item("Unicode (case-insencetive)", KEYMODE_UNICODE);
+ key_mode->connect("item_selected", callable_mp(this, &InputEventConfigurationDialog::_key_mode_selected));
+ key_mode->hide();
+ additional_options_container->add_child(key_mode);
main_vbox->add_child(additional_options_container);
}
diff --git a/editor/input_event_configuration_dialog.h b/editor/input_event_configuration_dialog.h
index 67906233dd..e7ab0da4d6 100644
--- a/editor/input_event_configuration_dialog.h
+++ b/editor/input_event_configuration_dialog.h
@@ -50,7 +50,10 @@ private:
Ref<Texture2D> joypad_axis;
} icon_cache;
- Ref<InputEvent> event = Ref<InputEvent>();
+ Ref<InputEvent> event;
+ Ref<InputEvent> original_event;
+
+ bool in_tree_update = false;
// Listening for input
EventListenerLineEdit *event_listener = nullptr;
@@ -88,9 +91,15 @@ private:
CheckBox *mod_checkboxes[MOD_MAX];
CheckBox *autoremap_command_or_control_checkbox = nullptr;
- CheckBox *physical_key_checkbox = nullptr;
+ enum KeyMode {
+ KEYMODE_KEYCODE,
+ KEYMODE_PHY_KEYCODE,
+ KEYMODE_UNICODE,
+ };
+
+ OptionButton *key_mode = nullptr;
- void _set_event(const Ref<InputEvent> &p_event, bool p_update_input_list_selection = true);
+ void _set_event(const Ref<InputEvent> &p_event, const Ref<InputEvent> &p_original_event, bool p_update_input_list_selection = true);
void _on_listen_input_changed(const Ref<InputEvent> &p_event);
void _on_listen_focus_changed();
@@ -100,7 +109,7 @@ private:
void _mod_toggled(bool p_checked, int p_index);
void _autoremap_command_or_control_toggled(bool p_checked);
- void _physical_keycode_toggled(bool p_checked);
+ void _key_mode_selected(int p_mode);
void _device_selection_changed(int p_option_button_index);
void _set_current_device(int p_device);
diff --git a/editor/plugins/lightmap_gi_editor_plugin.cpp b/editor/plugins/lightmap_gi_editor_plugin.cpp
index 1adcc2a3b4..519cfcaa94 100644
--- a/editor/plugins/lightmap_gi_editor_plugin.cpp
+++ b/editor/plugins/lightmap_gi_editor_plugin.cpp
@@ -35,12 +35,43 @@
void LightmapGIEditorPlugin::_bake_select_file(const String &p_file) {
if (lightmap) {
- LightmapGI::BakeError err;
+ LightmapGI::BakeError err = LightmapGI::BAKE_ERROR_OK;
const uint64_t time_started = OS::get_singleton()->get_ticks_msec();
- if (get_tree()->get_edited_scene_root() && get_tree()->get_edited_scene_root() == lightmap) {
- err = lightmap->bake(lightmap, p_file, bake_func_step);
+ if (get_tree()->get_edited_scene_root()) {
+ Ref<LightmapGIData> lightmapGIData = lightmap->get_light_data();
+
+ if (lightmapGIData.is_valid()) {
+ String path = lightmapGIData->get_path();
+ if (!path.is_resource_file()) {
+ int srpos = path.find("::");
+ if (srpos != -1) {
+ String base = path.substr(0, srpos);
+ if (ResourceLoader::get_resource_type(base) == "PackedScene") {
+ if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
+ err = LightmapGI::BAKE_ERROR_FOREIGN_DATA;
+ }
+ } else {
+ if (FileAccess::exists(base + ".import")) {
+ err = LightmapGI::BAKE_ERROR_FOREIGN_DATA;
+ }
+ }
+ }
+ } else {
+ if (FileAccess::exists(path + ".import")) {
+ err = LightmapGI::BAKE_ERROR_FOREIGN_DATA;
+ }
+ }
+ }
+
+ if (err == LightmapGI::BAKE_ERROR_OK) {
+ if (get_tree()->get_edited_scene_root() == lightmap) {
+ err = lightmap->bake(lightmap, p_file, bake_func_step);
+ } else {
+ err = lightmap->bake(lightmap->get_parent(), p_file, bake_func_step);
+ }
+ }
} else {
- err = lightmap->bake(lightmap->get_parent(), p_file, bake_func_step);
+ err = LightmapGI::BAKE_ERROR_NO_SCENE_ROOT;
}
bake_func_end(time_started);
@@ -59,16 +90,21 @@ void LightmapGIEditorPlugin::_bake_select_file(const String &p_file) {
file_dialog->set_current_path(scene_path);
file_dialog->popup_file_dialog();
-
} break;
- case LightmapGI::BAKE_ERROR_NO_MESHES:
+ case LightmapGI::BAKE_ERROR_NO_MESHES: {
EditorNode::get_singleton()->show_warning(TTR("No meshes to bake. Make sure they contain an UV2 channel and that the 'Bake Light' flag is on."));
- break;
- case LightmapGI::BAKE_ERROR_CANT_CREATE_IMAGE:
+ } break;
+ case LightmapGI::BAKE_ERROR_CANT_CREATE_IMAGE: {
EditorNode::get_singleton()->show_warning(TTR("Failed creating lightmap images, make sure path is writable."));
- break;
+ } break;
+ case LightmapGI::BAKE_ERROR_NO_SCENE_ROOT: {
+ EditorNode::get_singleton()->show_warning(TTR("No editor scene root found."));
+ } break;
+ case LightmapGI::BAKE_ERROR_FOREIGN_DATA: {
+ EditorNode::get_singleton()->show_warning(TTR("Lightmap data is not local to the scene."));
+ } break;
default: {
- }
+ } break;
}
}
}
diff --git a/editor/plugins/voxel_gi_editor_plugin.cpp b/editor/plugins/voxel_gi_editor_plugin.cpp
index a3ccf392e6..f9f72fee77 100644
--- a/editor/plugins/voxel_gi_editor_plugin.cpp
+++ b/editor/plugins/voxel_gi_editor_plugin.cpp
@@ -35,7 +35,9 @@
void VoxelGIEditorPlugin::_bake() {
if (voxel_gi) {
- if (voxel_gi->get_probe_data().is_null()) {
+ Ref<VoxelGIData> voxel_gi_data = voxel_gi->get_probe_data();
+
+ if (voxel_gi_data.is_null()) {
String path = get_tree()->get_edited_scene_root()->get_scene_file_path();
if (path.is_empty()) {
path = "res://" + voxel_gi->get_name() + "_data.res";
@@ -46,7 +48,32 @@ void VoxelGIEditorPlugin::_bake() {
probe_file->set_current_path(path);
probe_file->popup_file_dialog();
return;
+ } else {
+ String path = voxel_gi_data->get_path();
+ if (!path.is_resource_file()) {
+ int srpos = path.find("::");
+ if (srpos != -1) {
+ String base = path.substr(0, srpos);
+ if (ResourceLoader::get_resource_type(base) == "PackedScene") {
+ if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
+ EditorNode::get_singleton()->show_warning(TTR("Voxel GI data is not local to the scene."));
+ return;
+ }
+ } else {
+ if (FileAccess::exists(base + ".import")) {
+ EditorNode::get_singleton()->show_warning(TTR("Voxel GI data is part of an imported resource."));
+ return;
+ }
+ }
+ }
+ } else {
+ if (FileAccess::exists(path + ".import")) {
+ EditorNode::get_singleton()->show_warning(TTR("Voxel GI data is an imported resource."));
+ return;
+ }
+ }
}
+
voxel_gi->bake();
}
}