Ported: Implement Scene Unique Nodes - KoBeWi

e1c74ae83f
This commit is contained in:
Relintai 2022-07-27 00:45:48 +02:00
parent 720fac314f
commit d1108dc8bc
11 changed files with 235 additions and 38 deletions

View File

@ -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;
}

View File

@ -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<String> chars = String::invalid_node_name_characters.split(" ");

View File

@ -732,6 +732,10 @@
<member name="process_priority" type="int" setter="set_process_priority" getter="get_process_priority" default="0">
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.
</member>
<member name="unique_name_in_owner" type="bool" setter="set_unique_name_in_owner" getter="is_unique_name_in_owner" default="false">
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.
</member>
</members>
<signals>
<signal name="child_entered_tree">

View File

@ -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>

After

Width:  |  Height:  |  Size: 897 B

View File

@ -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<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:
@ -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);

View File

@ -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<Node *, NodePath> *p_renames);
void perform_node_renames(Node *p_base, Map<Node *, NodePath> *p_renames, Map<Ref<Animation>, Set<int>> *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();

View File

@ -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());

View File

@ -66,6 +66,7 @@ class SceneTreeEditor : public Control {
BUTTON_SIGNALS = 6,
BUTTON_GROUPS = 7,
BUTTON_PIN = 8,
BUTTON_UNIQUE = 9,
};
Tree *tree;

View File

@ -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");

View File

@ -93,6 +93,9 @@ private:
Node *parent;
Node *owner;
Vector<Node *> children; // list of children
HashMap<StringName, Node *> 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<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() 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;

View File

@ -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();
}
}
}