diff --git a/core/string_name.h b/core/string_name.h index 782fed9a6..f35b54338 100644 --- a/core/string_name.h +++ b/core/string_name.h @@ -91,6 +91,18 @@ public: bool operator==(const String &p_name) const; bool operator==(const char *p_name) const; bool operator!=(const String &p_name) const; + + _FORCE_INLINE_ bool is_node_unique_name() const { + if (!_data) { + return false; + } + if (_data->cname != nullptr) { + return _data->cname[0] == '%'; + } else { + return _data->name[0] == '%'; + } + } + _FORCE_INLINE_ bool operator<(const StringName &p_name) const { return _data < p_name._data; } diff --git a/core/ustring.cpp b/core/ustring.cpp index 6063083a5..3e20d525d 100644 --- a/core/ustring.cpp +++ b/core/ustring.cpp @@ -4496,7 +4496,7 @@ String String::property_name_encode() const { } // Changes made to the set of invalid characters must also be reflected in the String documentation. -const String String::invalid_node_name_characters = ". : @ / \""; +const String String::invalid_node_name_characters = ". : @ / \" %"; String String::validate_node_name() const { Vector chars = String::invalid_node_name_characters.split(" "); diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 694e0cc9f..fd48e9a13 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -732,6 +732,10 @@ The node's priority in the execution order of the enabled processing callbacks (i.e. [constant NOTIFICATION_PROCESS], [constant NOTIFICATION_PHYSICS_PROCESS] and their internal counterparts). Nodes whose process priority value is [i]lower[/i] will have their processing callbacks executed first. + + Sets this node's name as a unique name in its [member owner]. This allows the node to be accessed as [code]%Name[/code] instead of the full path, from any node within that scene. + If another node with the same owner already had that name declared as unique, that other node's name will no longer be set as having a unique name. + diff --git a/editor/icons/icon_scene_unique_name.svg b/editor/icons/icon_scene_unique_name.svg new file mode 100644 index 000000000..34279a14a --- /dev/null +++ b/editor/icons/icon_scene_unique_name.svg @@ -0,0 +1 @@ + diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 340980339..8d67c5db2 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -30,35 +30,21 @@ #include "scene_tree_dock.h" -#include "core/io/resource_saver.h" -#include "core/os/input.h" -#include "core/os/keyboard.h" -#include "core/project_settings.h" -#include "editor/editor_node.h" -#include "editor/editor_scale.h" -#include "editor/editor_settings.h" -#include "editor/multi_node_edit.h" -#include "editor/plugins/animation_player_editor_plugin.h" -#include "editor/plugins/canvas_item_editor_plugin.h" -#include "editor/plugins/script_editor_plugin.h" -#include "editor/plugins/spatial_editor_plugin.h" -#include "editor/script_editor_debugger.h" -#include "scene/2d/node_2d.h" -#include "scene/main/viewport.h" -#include "scene/property_utils.h" -#include "scene/resources/packed_scene.h" -#include "modules/modules_enabled.gen.h" // For regex. #include "core/array.h" #include "core/class_db.h" #include "core/dictionary.h" #include "core/error_list.h" #include "core/error_macros.h" #include "core/io/resource_loader.h" +#include "core/io/resource_saver.h" #include "core/math/vector2.h" #include "core/os/file_access.h" +#include "core/os/input.h" #include "core/os/input_event.h" +#include "core/os/keyboard.h" #include "core/os/memory.h" #include "core/os/os.h" +#include "core/project_settings.h" #include "core/ref_ptr.h" #include "core/script_language.h" #include "core/string_name.h" @@ -71,15 +57,26 @@ #include "editor/editor_file_dialog.h" #include "editor/editor_file_system.h" #include "editor/editor_inspector.h" +#include "editor/editor_node.h" #include "editor/editor_plugin.h" +#include "editor/editor_scale.h" +#include "editor/editor_settings.h" #include "editor/editor_sub_scene.h" #include "editor/inspector_dock.h" +#include "editor/multi_node_edit.h" +#include "editor/plugins/animation_player_editor_plugin.h" +#include "editor/plugins/canvas_item_editor_plugin.h" +#include "editor/plugins/script_editor_plugin.h" +#include "editor/plugins/spatial_editor_plugin.h" #include "editor/quick_open.h" #include "editor/rename_dialog.h" #include "editor/reparent_dialog.h" #include "editor/scene_tree_editor.h" #include "editor/script_create_dialog.h" +#include "editor/script_editor_debugger.h" +#include "modules/modules_enabled.gen.h" // For regex. #include "scene/2d/canvas_item.h" +#include "scene/2d/node_2d.h" #include "scene/3d/spatial.h" #include "scene/animation/animation_player.h" #include "scene/gui/base_button.h" @@ -94,7 +91,10 @@ #include "scene/gui/tree.h" #include "scene/main/node.h" #include "scene/main/scene_tree.h" +#include "scene/main/viewport.h" +#include "scene/property_utils.h" #include "scene/resources/animation.h" +#include "scene/resources/packed_scene.h" #include "scene/resources/texture.h" void SceneTreeDock::_nodes_drag_begin() { @@ -1168,6 +1168,28 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { } } } break; + case TOOL_TOGGLE_SCENE_UNIQUE_NAME: { + List selection = editor_selection->get_selected_node_list(); + List::Element *e = selection.front(); + if (e) { + UndoRedo *undo_redo = &editor_data->get_undo_redo(); + Node *node = e->get(); + bool enabled = node->is_unique_name_in_owner(); + if (!enabled && get_tree()->get_edited_scene_root()->get_node_or_null(UNIQUE_NODE_PREFIX + String(node->get_name())) != nullptr) { + accept->set_text(TTR("Another node already uses this unique name in the scene.")); + accept->popup_centered(); + return; + } + if (!enabled) { + undo_redo->create_action(TTR("Enable Scene Unique Name")); + } else { + undo_redo->create_action(TTR("Disable Scene Unique Name")); + } + undo_redo->add_do_method(node, "set_unique_name_in_owner", !enabled); + undo_redo->add_undo_method(node, "set_unique_name_in_owner", enabled); + undo_redo->commit_action(); + } + } break; case TOOL_CREATE_2D_SCENE: case TOOL_CREATE_3D_SCENE: case TOOL_CREATE_USER_INTERFACE: @@ -1397,9 +1419,20 @@ void SceneTreeDock::_node_replace_owner(Node *p_base, Node *p_node, Node *p_root UndoRedo *undo_redo = &editor_data->get_undo_redo(); switch (p_mode) { case MODE_BIDI: { + bool is_unique = p_node->is_unique_name_in_owner() && p_base->get_node_or_null(UNIQUE_NODE_PREFIX + String(p_node->get_name())) != nullptr; + if (is_unique) { + // Will create a unique name conflict. Disable before setting owner. + undo_redo->add_do_method(p_node, "set_unique_name_in_owner", false); + } + undo_redo->add_do_method(p_node, "set_owner", p_root); undo_redo->add_undo_method(p_node, "set_owner", p_base); + if (is_unique) { + // Will create a unique name conflict. Enable after setting owner. + undo_redo->add_undo_method(p_node, "set_unique_name_in_owner", true); + } + } break; case MODE_DO: { undo_redo->add_do_method(p_node, "set_owner", p_root); @@ -2797,6 +2830,14 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) { menu->add_icon_shortcut(get_icon("ScriptExtend", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/extend_script"), TOOL_EXTEND_SCRIPT); } } + + if (selection[0]->get_owner() == EditorNode::get_singleton()->get_edited_scene()) { + // Only for nodes owned by the edited scene root. + menu->add_separator(); + menu->add_icon_check_item(get_icon("SceneUniqueName", "EditorIcons"), TTR("Access as Scene Unique Name"), TOOL_TOGGLE_SCENE_UNIQUE_NAME); + menu->set_item_checked(menu->get_item_index(TOOL_TOGGLE_SCENE_UNIQUE_NAME), selection[0]->is_unique_name_in_owner()); + } + if (existing_script.is_valid() && exisiting_script_removable) { add_separator = true; menu->add_icon_shortcut(get_icon("ScriptRemove", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/detach_script"), TOOL_DETACH_SCRIPT); diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index d6e88b5a3..ab76bab42 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -112,6 +112,7 @@ class SceneTreeDock : public VBoxContainer { TOOL_SCENE_CLEAR_INHERITANCE, TOOL_SCENE_CLEAR_INHERITANCE_CONFIRM, TOOL_SCENE_OPEN_INHERITED, + TOOL_TOGGLE_SCENE_UNIQUE_NAME, TOOL_CREATE_2D_SCENE, TOOL_CREATE_3D_SCENE, @@ -297,8 +298,12 @@ public: void set_selected(Node *p_node, bool p_emit_selected = false); void fill_path_renames(Node *p_node, Node *p_new_parent, Map *p_renames); void perform_node_renames(Node *p_base, Map *p_renames, Map, Set> *r_rem_anims = nullptr); - SceneTreeEditor *get_tree_editor() { return scene_tree; } - EditorData *get_editor_data() { return editor_data; } + SceneTreeEditor *get_tree_editor() { + return scene_tree; + } + EditorData *get_editor_data() { + return editor_data; + } void add_remote_tree_editor(Control *p_remote); void show_remote_tree(); @@ -314,7 +319,9 @@ public: void open_add_child_dialog(); void open_instance_child_dialog(); - ScriptCreateDialog *get_script_create_dialog() { return script_create_dialog; } + ScriptCreateDialog *get_script_create_dialog() { + return script_create_dialog; + } SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSelection *p_editor_selection, EditorData &p_editor_data); ~SceneTreeDock(); diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp index fc99da826..bee3fa806 100644 --- a/editor/scene_tree_editor.cpp +++ b/editor/scene_tree_editor.cpp @@ -30,13 +30,6 @@ #include "scene_tree_editor.h" -#include "core/message_queue.h" -#include "editor/editor_node.h" -#include "editor/node_dock.h" -#include "editor/plugins/animation_player_editor_plugin.h" -#include "editor/plugins/canvas_item_editor_plugin.h" -#include "scene/gui/label.h" -#include "scene/resources/packed_scene.h" #include "core/array.h" #include "core/class_db.h" #include "core/color.h" @@ -45,6 +38,7 @@ #include "core/hashfuncs.h" #include "core/math/math_defs.h" #include "core/math/transform_2d.h" +#include "core/message_queue.h" #include "core/os/memory.h" #include "core/ref_ptr.h" #include "core/reference.h" @@ -53,16 +47,22 @@ #include "core/undo_redo.h" #include "editor/editor_data.h" #include "editor/editor_file_system.h" +#include "editor/editor_node.h" +#include "editor/node_dock.h" +#include "editor/plugins/animation_player_editor_plugin.h" +#include "editor/plugins/canvas_item_editor_plugin.h" #include "editor/plugins/script_editor_plugin.h" #include "scene/2d/canvas_item.h" #include "scene/animation/animation_player.h" #include "scene/gui/box_container.h" +#include "scene/gui/label.h" #include "scene/gui/line_edit.h" #include "scene/gui/texture_rect.h" #include "scene/gui/tree.h" #include "scene/main/node.h" #include "scene/main/scene_tree.h" #include "scene/main/timer.h" +#include "scene/resources/packed_scene.h" #include "scene/resources/texture.h" Node *SceneTreeEditor::get_scene_node() { @@ -170,6 +170,13 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i NodeDock::singleton->get_parent()->call("set_current_tab", NodeDock::singleton->get_index()); NodeDock::singleton->show_groups(); + } else if (p_id == BUTTON_UNIQUE) { + undo_redo->create_action(TTR("Disable Scene Unique Name")); + undo_redo->add_do_method(n, "set_unique_name_in_owner", false); + undo_redo->add_undo_method(n, "set_unique_name_in_owner", true); + undo_redo->add_do_method(this, "_update_tree"); + undo_redo->add_undo_method(this, "_update_tree"); + undo_redo->commit_action(); } } void SceneTreeEditor::_toggle_visible(Node *p_node) { @@ -288,6 +295,10 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll item->add_button(0, get_icon("NodeWarning", "EditorIcons"), BUTTON_WARNING, false, TTR("Node configuration warning:") + "\n" + p_node->get_configuration_warning()); } + if (p_node->is_unique_name_in_owner()) { + item->add_button(0, get_icon("SceneUniqueName", "EditorIcons"), BUTTON_UNIQUE, false, vformat(TTR("This node can be accessed from within anywhere in the scene by preceding it with the '%s' prefix in a node path.\nClick to disable this."), UNIQUE_NODE_PREFIX)); + } + int num_connections = p_node->get_persistent_signal_connection_count(); int num_groups = p_node->get_persistent_group_count(); @@ -848,6 +859,13 @@ void SceneTreeEditor::_renamed() { // Trim leading/trailing whitespace to prevent node names from containing accidental whitespace, which would make it more difficult to get the node via `get_node()`. new_name = new_name.strip_edges(); + if (n->is_unique_name_in_owner() && get_tree()->get_edited_scene_root()->get_node_or_null("%" + new_name) != nullptr) { + error->set_text(TTR("Another node already uses this unique name in the scene.")); + error->popup_centered(); + which->set_text(0, n->get_name()); + return; + } + if (!undo_redo) { n->set_name(new_name); which->set_metadata(0, n->get_path()); diff --git a/editor/scene_tree_editor.h b/editor/scene_tree_editor.h index 720f24497..85bd398ac 100644 --- a/editor/scene_tree_editor.h +++ b/editor/scene_tree_editor.h @@ -66,6 +66,7 @@ class SceneTreeEditor : public Control { BUTTON_SIGNALS = 6, BUTTON_GROUPS = 7, BUTTON_PIN = 8, + BUTTON_UNIQUE = 9, }; Tree *tree; diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 954e77a87..50d4de708 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -272,6 +272,9 @@ void Node::_propagate_after_exit_branch(bool p_exiting_tree) { } if (!found) { + if (data.unique_name_in_owner) { + _release_unique_name_in_owner(); + } data.owner->data.owned.erase(data.OW); data.owner = nullptr; } @@ -982,12 +985,21 @@ void Node::set_name(const String &p_name) { String name = p_name.validate_node_name(); ERR_FAIL_COND(name == ""); + + if (data.unique_name_in_owner && data.owner) { + _release_unique_name_in_owner(); + } + data.name = name; if (data.parent) { data.parent->_validate_child_name(this); } + if (data.unique_name_in_owner && data.owner) { + _acquire_unique_name_in_owner(); + } + propagate_notification(NOTIFICATION_PATH_CHANGED); if (is_inside_tree()) { @@ -1335,6 +1347,24 @@ Node *Node::get_node_or_null(const NodePath &p_path) const { next = root; } + } else if (name.is_node_unique_name()) { + if (current->data.owned_unique_nodes.size()) { + // Has unique nodes in ownership + Node **unique = current->data.owned_unique_nodes.getptr(name); + if (!unique) { + return nullptr; + } + next = *unique; + } else if (current->data.owner) { + Node **unique = current->data.owner->data.owned_unique_nodes.getptr(name); + if (!unique) { + return nullptr; + } + next = *unique; + } else { + return nullptr; + } + } else { next = nullptr; @@ -1511,8 +1541,54 @@ void Node::_set_owner_nocheck(Node *p_owner) { data.OW = data.owner->data.owned.back(); } +void Node::_release_unique_name_in_owner() { + ERR_FAIL_NULL(data.owner); // Sanity check. + StringName key = StringName("%" + data.name.operator String()); + Node **which = data.owner->data.owned_unique_nodes.getptr(key); + if (which == nullptr || *which != this) { + return; // Ignore. + } + data.owner->data.owned_unique_nodes.erase(key); +} + +void Node::_acquire_unique_name_in_owner() { + ERR_FAIL_NULL(data.owner); // Sanity check. + StringName key = StringName("%" + data.name.operator String()); + Node **which = data.owner->data.owned_unique_nodes.getptr(key); + if (which != nullptr && *which != this) { + WARN_PRINT(vformat(RTR("Setting node name '%s' to be unique within scene for '%s', but it's already claimed by '%s'. This node is no longer set unique."), get_name(), is_inside_tree() ? get_path() : data.owner->get_path_to(this), is_inside_tree() ? (*which)->get_path() : data.owner->get_path_to(*which))); + data.unique_name_in_owner = false; + return; + } + data.owner->data.owned_unique_nodes[key] = this; +} + +void Node::set_unique_name_in_owner(bool p_enabled) { + if (data.unique_name_in_owner == p_enabled) { + return; + } + + if (data.unique_name_in_owner && data.owner != nullptr) { + _release_unique_name_in_owner(); + } + data.unique_name_in_owner = p_enabled; + + if (data.unique_name_in_owner && data.owner != nullptr) { + _acquire_unique_name_in_owner(); + } + + update_configuration_warning(); +} + +bool Node::is_unique_name_in_owner() const { + return data.unique_name_in_owner; +} + void Node::set_owner(Node *p_owner) { if (data.owner) { + if (data.unique_name_in_owner) { + _release_unique_name_in_owner(); + } data.owner->data.owned.erase(data.OW); data.OW = nullptr; data.owner = nullptr; @@ -1539,6 +1615,10 @@ void Node::set_owner(Node *p_owner) { ERR_FAIL_COND(!owner_valid); _set_owner_nocheck(p_owner); + + if (data.unique_name_in_owner) { + _acquire_unique_name_in_owner(); + } } Node *Node::get_owner() const { return data.owner; @@ -2913,6 +2993,9 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_property_pinned", "property", "pinned"), &Node::set_property_pinned); #endif + ClassDB::bind_method(D_METHOD("set_unique_name_in_owner", "enable"), &Node::set_unique_name_in_owner); + ClassDB::bind_method(D_METHOD("is_unique_name_in_owner"), &Node::is_unique_name_in_owner); + { MethodInfo mi; @@ -2997,6 +3080,7 @@ void Node::_bind_methods() { #endif ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "name", PROPERTY_HINT_NONE, "", 0), "set_name", "get_name"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "unique_name_in_owner", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_unique_name_in_owner", "is_unique_name_in_owner"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "filename", PROPERTY_HINT_NONE, "", 0), "set_filename", "get_filename"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "owner", PROPERTY_HINT_RESOURCE_TYPE, "Node", 0), "set_owner", "get_owner"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiplayer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerAPI", 0), "", "get_multiplayer"); diff --git a/scene/main/node.h b/scene/main/node.h index 0bc6cc071..6b27f7c0e 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -93,6 +93,9 @@ private: Node *parent; Node *owner; Vector children; // list of children + HashMap owned_unique_nodes; + bool unique_name_in_owner = false; + int pos; int depth; int blocked; // safeguard that throws an error when attempting to modify the tree in a harmful way while being traversed. @@ -199,10 +202,16 @@ private: friend class SceneTree; void _set_tree(SceneTree *p_tree); + void _release_unique_name_in_owner(); + void _acquire_unique_name_in_owner(); protected: - void _block() { data.blocked++; } - void _unblock() { data.blocked--; } + void _block() { + data.blocked++; + } + void _unblock() { + data.blocked--; + } void _notification(int p_notification); @@ -223,9 +232,13 @@ protected: void _set_owner_nocheck(Node *p_owner); void _set_name_nocheck(const StringName &p_name); void _set_physics_interpolated_client_side(bool p_enable); - bool _is_physics_interpolated_client_side() const { return data.physics_interpolated_client_side; } + bool _is_physics_interpolated_client_side() const { + return data.physics_interpolated_client_side; + } void _set_use_identity_transform(bool p_enable); - bool _is_using_identity_transform() const { return data.use_identity_transform; } + bool _is_using_identity_transform() const { + return data.use_identity_transform; + } public: enum { @@ -294,7 +307,9 @@ public: return data.tree; } - _FORCE_INLINE_ bool is_inside_tree() const { return data.inside_tree; } + _FORCE_INLINE_ bool is_inside_tree() const { + return data.inside_tree; + } bool is_a_parent_of(const Node *p_node) const; bool is_greater_than(const Node *p_node) const; @@ -322,6 +337,9 @@ public: Node *get_owner() const; void get_owned_by(Node *p_by, List *p_owned); + void set_unique_name_in_owner(bool p_enabled); + bool is_unique_name_in_owner() const; + void remove_and_skip(); int get_index() const; @@ -411,8 +429,12 @@ public: bool can_process_notification(int p_what) const; void set_physics_interpolated(bool p_interpolated); - _FORCE_INLINE_ bool is_physics_interpolated() const { return data.physics_interpolated; } - _FORCE_INLINE_ bool is_physics_interpolated_and_enabled() const { return is_inside_tree() && get_tree()->is_physics_interpolation_enabled() && is_physics_interpolated(); } + _FORCE_INLINE_ bool is_physics_interpolated() const { + return data.physics_interpolated; + } + _FORCE_INLINE_ bool is_physics_interpolated_and_enabled() const { + return is_inside_tree() && get_tree()->is_physics_interpolation_enabled() && is_physics_interpolated(); + } void reset_physics_interpolation(); void request_ready(); @@ -429,7 +451,9 @@ public: static void set_human_readable_collision_renaming(bool p_enabled); static void init_node_hrcr(); - void force_parent_owned() { data.parent_owned = true; } //hack to avoid duplicate nodes + void force_parent_owned() { + data.parent_owned = true; + } //hack to avoid duplicate nodes void set_import_path(const NodePath &p_import_path); //path used when imported, used by scene editors to keep tracking NodePath get_import_path() const; @@ -440,7 +464,9 @@ public: void clear_internal_tree_resource_paths(); - _FORCE_INLINE_ Viewport *get_viewport() const { return data.viewport; } + _FORCE_INLINE_ Viewport *get_viewport() const { + return data.viewport; + } virtual String get_configuration_warning() const; diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index 19a79b0ef..307de3be1 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -310,6 +310,9 @@ Node *SceneState::instance(GenEditState p_edit_state) const { NODE_FROM_ID(owner, n.owner); if (owner) { node->_set_owner_nocheck(owner); + if (node->data.unique_name_in_owner) { + node->_acquire_unique_name_in_owner(); + } } }