diff options
-rw-r--r-- | core/string/string_name.cpp | 1 | ||||
-rw-r--r-- | core/string/string_name.h | 13 | ||||
-rw-r--r-- | core/string/ustring.cpp | 3 | ||||
-rw-r--r-- | doc/classes/Node.xml | 13 | ||||
-rw-r--r-- | editor/icons/SceneUniqueName.svg | 1 | ||||
-rw-r--r-- | editor/scene_tree_dock.cpp | 39 | ||||
-rw-r--r-- | editor/scene_tree_dock.h | 2 | ||||
-rw-r--r-- | editor/scene_tree_editor.cpp | 18 | ||||
-rw-r--r-- | editor/scene_tree_editor.h | 1 | ||||
-rw-r--r-- | scene/main/node.cpp | 94 | ||||
-rw-r--r-- | scene/main/node.h | 9 | ||||
-rw-r--r-- | scene/resources/packed_scene.cpp | 3 |
12 files changed, 190 insertions, 7 deletions
diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp index 2e941b8037..9c4fc4e1b7 100644 --- a/core/string/string_name.cpp +++ b/core/string/string_name.cpp @@ -247,6 +247,7 @@ StringName::StringName(const char *p_name, bool p_static) { _data->cname = nullptr; _data->next = _table[idx]; _data->prev = nullptr; + #ifdef DEBUG_ENABLED if (unlikely(debug_stringname)) { // Keep in memory, force static. diff --git a/core/string/string_name.h b/core/string/string_name.h index f4233854ac..ff4c41af94 100644 --- a/core/string/string_name.h +++ b/core/string/string_name.h @@ -35,6 +35,8 @@ #include "core/string/ustring.h" #include "core/templates/safe_refcount.h" +#define UNIQUE_NODE_PREFIX "%" + class Main; struct StaticCString { @@ -100,6 +102,17 @@ 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 (char32_t)_data->cname[0] == (char32_t)UNIQUE_NODE_PREFIX[0]; + } else { + return (char32_t)_data->name[0] == (char32_t)UNIQUE_NODE_PREFIX[0]; + } + } _FORCE_INLINE_ bool operator<(const StringName &p_name) const { return _data < p_name._data; } diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 7cfd34b53e..7199121932 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -35,6 +35,7 @@ #include "core/math/math_funcs.h" #include "core/os/memory.h" #include "core/string/print_string.h" +#include "core/string/string_name.h" #include "core/string/translation.h" #include "core/string/ucaps.h" #include "core/variant/variant.h" @@ -4357,7 +4358,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 = ". : @ / \" " UNIQUE_NODE_PREFIX; String String::validate_node_name() const { Vector<String> chars = String::invalid_node_name_characters.split(" "); diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 7079036879..3efa49b309 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -492,6 +492,12 @@ Returns [code]true[/code] if the node is processing unhandled key input (see [method set_process_unhandled_key_input]). </description> </method> + <method name="is_unique_name_in_owner" qualifiers="const"> + <return type="bool" /> + <description> + Returns whether the node is an unique name for all the other nodes owned by its [member owner]. + </description> + </method> <method name="move_child"> <return type="void" /> <argument index="0" name="child_node" type="Node" /> @@ -719,6 +725,13 @@ Sets whether this is an instance load placeholder. See [InstancePlaceholder]. </description> </method> + <method name="set_unique_name_in_owner"> + <return type="void" /> + <argument index="0" name="enable" type="bool" /> + <description> + Sets this node's name as the 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. + </description> + </method> <method name="update_configuration_warnings"> <return type="void" /> <description> diff --git a/editor/icons/SceneUniqueName.svg b/editor/icons/SceneUniqueName.svg new file mode 100644 index 0000000000..34279a14a6 --- /dev/null +++ b/editor/icons/SceneUniqueName.svg @@ -0,0 +1 @@ +<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M4.378 2.224q1.235 0 2.084.866.865.85.865 2.083 0 1.17-.881 2.036-.866.85-2.068.85-1.218 0-2.083-.85-.866-.866-.866-2.068t.866-2.051q.865-.866 2.083-.866zm.962 1.988q-.4-.4-.962-.4-.56 0-.961.4-.401.384-.401.93 0 .56.4.961.401.385.962.385.561 0 .962-.385.4-.4.4-.946 0-.56-.4-.945zm5.45-2.116h1.218L5.677 13.78H4.442Zm1.17 5.722q1.234 0 2.083.866.866.849.866 2.1 0 1.17-.882 2.035-.865.85-2.068.85-1.218 0-2.083-.85-.866-.866-.866-2.084 0-1.202.866-2.051.865-.866 2.083-.866zm.961 1.987q-.4-.4-.962-.4-.56 0-.961.4-.4.385-.4.946 0 .561.4.962.4.384.961.384.561 0 .962-.384.4-.4.4-.946 0-.56-.4-.962z" aria-label="%" style="font-weight:600;font-size:16.0277px;font-family:FreeSans;-inkscape-font-specification:'FreeSans Semi-Bold';letter-spacing:0;word-spacing:0;fill:#e0e0e0;fill-opacity:.996078;stroke-width:.400692"/></svg> diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 03f65cdf52..c63471720b 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -1061,6 +1061,28 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { } } } break; + case TOOL_TOGGLE_SCENE_UNIQUE_NAME: { + List<Node *> selection = editor_selection->get_selected_node_list(); + List<Node *>::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: @@ -1309,8 +1331,17 @@ 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: { @@ -2774,11 +2805,19 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) { menu->add_separator(); menu->add_icon_shortcut(get_theme_icon(SNAME("CreateNewSceneFrom"), SNAME("EditorIcons")), ED_GET_SHORTCUT("scene_tree/save_branch_as_scene"), TOOL_NEW_SCENE_FROM); } + if (full_selection.size() == 1) { menu->add_separator(); menu->add_icon_shortcut(get_theme_icon(SNAME("CopyNodePath"), SNAME("EditorIcons")), ED_GET_SHORTCUT("scene_tree/copy_node_path"), TOOL_COPY_NODE_PATH); } + 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_theme_icon(SNAME("SceneUniqueName"), SNAME("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()); + } + bool is_external = (!selection[0]->get_scene_file_path().is_empty()); if (is_external) { bool is_inherited = selection[0]->get_scene_inherited_state() != nullptr; diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index 599fb01203..842995dfe3 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -91,7 +91,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, TOOL_CREATE_USER_INTERFACE, diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp index 44eb5c670d..aa48395152 100644 --- a/editor/scene_tree_editor.cpp +++ b/editor/scene_tree_editor.cpp @@ -148,6 +148,13 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i TabContainer *tab_container = Object::cast_to<TabContainer>(NodeDock::get_singleton()->get_parent()); NodeDock::get_singleton()->get_parent()->call("set_current_tab", tab_container->get_tab_idx_from_control(NodeDock::get_singleton())); NodeDock::get_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(); } } @@ -260,6 +267,10 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll item->add_button(0, get_theme_icon(SNAME("NodeWarning"), SNAME("EditorIcons")), BUTTON_WARNING, false, TTR("Node configuration warning:") + "\n" + warning); } + if (p_node->is_unique_name_in_owner()) { + item->add_button(0, get_theme_icon(SNAME("SceneUniqueName"), SNAME("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(); @@ -832,6 +843,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 547a5b57ca..3d88081ab1 100644 --- a/editor/scene_tree_editor.h +++ b/editor/scene_tree_editor.h @@ -53,6 +53,7 @@ class SceneTreeEditor : public Control { BUTTON_SIGNALS = 6, BUTTON_GROUPS = 7, BUTTON_PIN = 8, + BUTTON_UNIQUE = 9, }; Tree *tree = nullptr; diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 34bb1cde05..1cfe54155a 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -257,6 +257,9 @@ void Node::_propagate_after_exit_tree() { } if (!found) { + if (data.unique_name_in_owner) { + _release_unique_name_in_owner(); + } data.owner->data.owned.erase(data.OW); data.owner = nullptr; } @@ -917,12 +920,20 @@ void Node::set_name(const String &p_name) { String name = p_name.validate_node_name(); ERR_FAIL_COND(name.is_empty()); + + 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, true); } + if (data.unique_name_in_owner && data.owner) { + _acquire_unique_name_in_owner(); + } + propagate_notification(NOTIFICATION_PATH_RENAMED); if (is_inside_tree()) { @@ -1303,6 +1314,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; @@ -1498,8 +1527,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(UNIQUE_NODE_PREFIX + 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(UNIQUE_NODE_PREFIX + 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_warnings(); +} + +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; @@ -1526,6 +1601,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 { @@ -2585,16 +2664,16 @@ void Node::clear_internal_tree_resource_paths() { } TypedArray<String> Node::get_configuration_warnings() const { + TypedArray<String> ret; + Vector<String> warnings; if (GDVIRTUAL_CALL(_get_configuration_warnings, warnings)) { - TypedArray<String> ret; - ret.resize(warnings.size()); for (int i = 0; i < warnings.size(); i++) { - ret[i] = warnings[i]; + ret.push_back(warnings[i]); } - return ret; } - return Array(); + + return ret; } String Node::get_configuration_warnings_as_string() const { @@ -2790,6 +2869,11 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_import_path", "import_path"), &Node::set_import_path); ClassDB::bind_method(D_METHOD("_get_import_path"), &Node::get_import_path); + 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); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "unique_name_in_owner", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_unique_name_in_owner", "is_unique_name_in_owner"); + #ifdef TOOLS_ENABLED ClassDB::bind_method(D_METHOD("_set_property_pinned", "property", "pinned"), &Node::set_property_pinned); #endif diff --git a/scene/main/node.h b/scene/main/node.h index 57b150e29a..0973baf793 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -99,6 +99,9 @@ private: Node *parent = nullptr; Node *owner = nullptr; Vector<Node *> children; + HashMap<StringName, Node *> owned_unique_nodes; + bool unique_name_in_owner = false; + int internal_children_front = 0; int internal_children_back = 0; int pos = -1; @@ -193,6 +196,9 @@ private: _FORCE_INLINE_ bool _can_process(bool p_paused) const; _FORCE_INLINE_ bool _is_enabled() const; + void _release_unique_name_in_owner(); + void _acquire_unique_name_in_owner(); + protected: void _block() { data.blocked++; } void _unblock() { data.blocked--; } @@ -345,6 +351,9 @@ public: Node *get_owner() const; void get_owned_by(Node *p_by, List<Node *> *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(bool p_include_internal = true) const; diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index b1c2702a1e..b991cb1abe 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -310,6 +310,9 @@ Node *SceneState::instantiate(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(); + } } } |