diff options
Diffstat (limited to 'editor')
| -rw-r--r-- | editor/action_map_editor.cpp | 8 | ||||
| -rw-r--r-- | editor/editor_data.h | 1 | ||||
| -rw-r--r-- | editor/editor_node.cpp | 479 | ||||
| -rw-r--r-- | editor/editor_node.h | 35 | ||||
| -rw-r--r-- | editor/event_listener_line_edit.cpp | 36 | ||||
| -rw-r--r-- | editor/export/editor_export_platform.cpp | 4 | ||||
| -rw-r--r-- | editor/icons/Keyboard.svg | 2 | ||||
| -rw-r--r-- | editor/icons/KeyboardError.svg | 1 | ||||
| -rw-r--r-- | editor/icons/KeyboardLabel.svg | 1 | ||||
| -rw-r--r-- | editor/icons/KeyboardPhysical.svg | 2 | ||||
| -rw-r--r-- | editor/input_event_configuration_dialog.cpp | 156 | ||||
| -rw-r--r-- | editor/input_event_configuration_dialog.h | 17 | ||||
| -rw-r--r-- | editor/plugins/lightmap_gi_editor_plugin.cpp | 56 | ||||
| -rw-r--r-- | editor/plugins/voxel_gi_editor_plugin.cpp | 29 |
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(); } } |