From 804188d320a1d6405d8f42c6277b511b51bd005e Mon Sep 17 00:00:00 2001 From: Sat <124409014+SatLess@users.noreply.github.com> Date: Tue, 4 Nov 2025 14:12:42 -0300 Subject: [PATCH] Add indicator to linked Resources --- editor/docks/inspector_dock.cpp | 3 +- editor/editor_node.cpp | 161 +++++++++++++++++++- editor/editor_node.h | 8 + editor/inspector/editor_inspector.cpp | 24 +++ editor/inspector/editor_resource_picker.cpp | 148 +++++++++++++++++- editor/inspector/editor_resource_picker.h | 4 + editor/inspector/multi_node_edit.cpp | 12 +- editor/scene/scene_tree_editor.cpp | 8 +- 8 files changed, 354 insertions(+), 14 deletions(-) diff --git a/editor/docks/inspector_dock.cpp b/editor/docks/inspector_dock.cpp index c358187f5bd..c968562a6d4 100644 --- a/editor/docks/inspector_dock.cpp +++ b/editor/docks/inspector_dock.cpp @@ -161,8 +161,7 @@ void InspectorDock::_menu_option_confirm(int p_option, bool p_confirmed) { unique_resources_confirmation->popup_centered(); } else { current_option = -1; - unique_resources_label->set_text(TTRC("This object has no resources.")); - unique_resources_confirmation->popup_centered(); + EditorNode::get_singleton()->show_warning(TTR("This object has no resources to duplicate.")); } } else { editor_data->apply_changes_in_editors(); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 3307de3c258..0c4a9e7de5d 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -1641,7 +1641,7 @@ void EditorNode::save_resource_in_path(const Ref &p_resource, const St saving_resources_in_path.erase(p_resource); _resource_saved(p_resource, path); - + clear_node_reference(p_resource); // // Check if Resource is saved to disk to potentially remove it from resource_count emit_signal(SNAME("resource_saved"), p_resource); editor_data.notify_resource_saved(p_resource); } @@ -1743,6 +1743,161 @@ void EditorNode::save_resource_as(const Ref &p_resource, const String file->popup_file_dialog(); } +bool EditorNode::is_resource_internal_to_scene(Ref p_resource) { + bool inside_scene = !get_edited_scene() || get_edited_scene()->get_scene_file_path() == p_resource->get_path().get_slice("::", 0); + return inside_scene || p_resource->get_path().is_empty(); +} + +void EditorNode::gather_resources(const Variant &p_variant, List> &r_list, bool p_subresources, bool p_allow_external) { + Variant::Type type = p_variant.get_type(); + if (type == Variant::OBJECT && p_variant.get_validated_object() == nullptr) { + return; + } + + if (type != Variant::OBJECT && type != Variant::ARRAY && type != Variant::DICTIONARY) { + return; + } + + if (type == Variant::ARRAY) { + Array arr = p_variant; + for (const Variant &v : arr) { + Ref res = v; + if (res.is_valid()) { + if (p_allow_external) { + r_list.push_back(res); + } else if (is_resource_internal_to_scene(res)) { + r_list.push_back(res); + } + } + gather_resources(v, r_list, p_subresources, p_allow_external); + } + return; + } + + if (type == Variant::DICTIONARY) { + Dictionary dict = p_variant; + for (const KeyValue &kv : dict) { + Ref res_key = kv.key; + Ref res_value = kv.value; + if (res_key.is_valid()) { + if (p_allow_external) { + r_list.push_back(res_key); + } else if (is_resource_internal_to_scene(res_key)) { + r_list.push_back(res_key); + } + } + if (res_value.is_valid()) { + if (p_allow_external) { + r_list.push_back(res_value); + } else if (is_resource_internal_to_scene(res_value)) { + r_list.push_back(res_value); + } + } + gather_resources(kv.key, r_list, p_subresources, p_allow_external); + gather_resources(kv.value, r_list, p_subresources, p_allow_external); + } + return; + } + + List pinfo; + p_variant.get_property_list(&pinfo); + + for (const PropertyInfo &E : pinfo) { + if (!(E.usage & PROPERTY_USAGE_EDITOR) || E.name == "script") { + continue; + } + + Variant property_value = p_variant.get(E.name); + Variant::Type property_type = property_value.get_type(); + + if (property_type == Variant::ARRAY || property_type == Variant::DICTIONARY) { + gather_resources(property_value, r_list, p_subresources, p_allow_external); + continue; + } + + Ref res = property_value; + if (res.is_null()) { + continue; + } + if (!p_allow_external) { + if (!res->is_built_in() || res->get_path().get_slice("::", 0) != get_edited_scene()->get_scene_file_path()) { + if (!res->get_path().is_empty()) { + continue; + } + } + } + r_list.push_back(res); + if (p_subresources) { + gather_resources(res, r_list, p_subresources, p_allow_external); + } + } +} + +void EditorNode::update_resource_count(Node *p_node, bool p_remove) { + if (!get_edited_scene()) { + return; + } + + List> res_list; + gather_resources(p_node, res_list, true); + + for (Ref &R : res_list) { + List::Element *E = resource_count[R].find(p_node); + if (E) { + if (p_remove) { + resource_count[R].erase(E); + } + } else { + resource_count[R].push_back(p_node); + } + } + + emit_signal(SNAME("resource_counter_changed")); +} + +int EditorNode::get_resource_count(Ref p_res) { + List *L = resource_count.getptr(p_res); + return L ? L->size() : 0; +} + +List EditorNode::get_resource_node_list(Ref p_res) { + List *L = resource_count.getptr(p_res); + return L == nullptr ? List() : *L; +} + +void EditorNode::update_node_reference(const Variant &p_value, Node *p_node, bool p_remove) { + List> list; + Ref res = p_value; + gather_resources(p_value, list, true); //Gather all Resources and their SubResources to remove p_node from their lists. + + if (res.is_valid() && is_resource_internal_to_scene(res)) { + // Avoid external Resources from being added in. + list.push_back(res); + } + + for (Ref &R : list) { + if (!p_remove) { + resource_count[R].push_back(p_node); + } else { + List::Element *E = resource_count[R].find(p_node); + if (E) { + resource_count[R].erase(E); + } + } + } + emit_signal(SNAME("resource_counter_changed")); +} + +void EditorNode::clear_node_reference(Ref p_res) { + if (is_resource_internal_to_scene(p_res)) { + return; + } + List *node_list = resource_count.getptr(p_res); + if (node_list != nullptr) { + node_list->clear(); + } +} + void EditorNode::_menu_option(int p_option) { _menu_option_confirm(p_option, false); } @@ -4311,6 +4466,7 @@ void EditorNode::_set_current_scene_nocheck(int p_idx) { Node *old_scene = get_editor_data().get_edited_scene_root(); + resource_count.clear(); editor_selection->clear(); SceneTreeDock::get_singleton()->clear_previous_node_selection(); editor_data.set_edited_scene(p_idx); @@ -7362,6 +7518,8 @@ void EditorNode::_bind_methods() { ClassDB::bind_method("stop_child_process", &EditorNode::stop_child_process); + ClassDB::bind_method(D_METHOD("update_node_reference", "value", "node", "remove"), &EditorNode::update_node_reference, DEFVAL(false)); + ADD_SIGNAL(MethodInfo("request_help_search")); ADD_SIGNAL(MethodInfo("script_add_function_request", PropertyInfo(Variant::OBJECT, "obj"), PropertyInfo(Variant::STRING, "function"), PropertyInfo(Variant::PACKED_STRING_ARRAY, "args"))); ADD_SIGNAL(MethodInfo("resource_saved", PropertyInfo(Variant::OBJECT, "obj"))); @@ -7369,6 +7527,7 @@ void EditorNode::_bind_methods() { ADD_SIGNAL(MethodInfo("scene_changed")); ADD_SIGNAL(MethodInfo("scene_closed", PropertyInfo(Variant::STRING, "path"))); ADD_SIGNAL(MethodInfo("preview_locale_changed")); + ADD_SIGNAL(MethodInfo("resource_counter_changed")); } static Node *_resource_get_edited_scene() { diff --git a/editor/editor_node.h b/editor/editor_node.h index 35a89c13c46..3c811cb9e7f 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -465,6 +465,7 @@ private: Ref saving_resource; HashSet> saving_resources_in_path; + HashMap, List> resource_count; // Keeps track of linked Resources from a Scene. uint64_t update_spinner_step_msec = 0; uint64_t update_spinner_step_frame = 0; @@ -809,6 +810,13 @@ public: void save_resource_in_path(const Ref &p_resource, const String &p_path); void save_resource(const Ref &p_resource); void save_resource_as(const Ref &p_resource, const String &p_at_path = String()); + bool is_resource_internal_to_scene(Ref p_resource); + void gather_resources(const Variant &p_variant, List> &r_list, bool p_subresources = false, bool p_allow_external = false); + void update_resource_count(Node *p_node, bool p_remove = false); + void update_node_reference(const Variant &p_value, Node *p_node, bool p_remove = false); + void clear_node_reference(Ref p_res); + int get_resource_count(Ref p_res); + List get_resource_node_list(Ref p_res); void show_about() { _menu_option_confirm(HELP_ABOUT, false); } diff --git a/editor/inspector/editor_inspector.cpp b/editor/inspector/editor_inspector.cpp index d2ac08f08e6..ee34936434f 100644 --- a/editor/inspector/editor_inspector.cpp +++ b/editor/inspector/editor_inspector.cpp @@ -5170,12 +5170,23 @@ void EditorInspector::_edit_set(const String &p_name, const Variant &p_value, bo undo_redo->add_do_property(object, p_name, p_value); bool valid = false; Variant value = object->get(p_name, &valid); + Variant::Type type = p_value.get_type(); if (valid) { if (Object::cast_to(object) && (p_name == "anchors_preset" || p_name == "layout_mode")) { undo_redo->add_undo_method(object, "_edit_set_state", Object::cast_to(object)->_edit_get_state()); } else { undo_redo->add_undo_property(object, p_name, value); } + // We'll use Editor Selection to get the currently edited Node. + Node *N = Object::cast_to(object); + if (N && (type == Variant::OBJECT || type == Variant::ARRAY || type == Variant::DICTIONARY) && value != p_value) { + undo_redo->add_do_method(EditorNode::get_singleton(), "update_node_reference", value, N, true); + undo_redo->add_do_method(EditorNode::get_singleton(), "update_node_reference", p_value, N, false); + // Perhaps an inefficient way of updating the resource count. + // We could go in depth and check which Resource values changed/got removed and which ones stayed the same, but this is more readable at the moment. + undo_redo->add_undo_method(EditorNode::get_singleton(), "update_node_reference", p_value, N, true); + undo_redo->add_undo_method(EditorNode::get_singleton(), "update_node_reference", value, N, false); + } } List linked_properties; @@ -5224,7 +5235,20 @@ void EditorInspector::_edit_set(const String &p_name, const Variant &p_value, bo } Resource *r = Object::cast_to(object); + if (r) { + //Setting a Subresource. Since there's possibly multiple Nodes referencing 'r', we need to link them to the Subresource. + List shared_nodes = EditorNode::get_singleton()->get_resource_node_list(r); + for (Node *N : shared_nodes) { + if ((type == Variant::OBJECT || type == Variant::ARRAY || type == Variant::DICTIONARY) && value != p_value) { + undo_redo->add_do_method(EditorNode::get_singleton(), "update_node_reference", value, N, true); + undo_redo->add_do_method(EditorNode::get_singleton(), "update_node_reference", p_value, N, false); + // Perhaps an inefficient way of updating the resource count. + // We could go in depth and check which Resource values changed/got removed and which ones stayed the same, but this is more readable at the moment. + undo_redo->add_undo_method(EditorNode::get_singleton(), "update_node_reference", p_value, N, true); + undo_redo->add_undo_method(EditorNode::get_singleton(), "update_node_reference", value, N, false); + } + } if (String(p_name) == "resource_local_to_scene") { bool prev = object->get(p_name); bool next = p_value; diff --git a/editor/inspector/editor_resource_picker.cpp b/editor/inspector/editor_resource_picker.cpp index 3e72f30ad28..bc12af2e37a 100644 --- a/editor/inspector/editor_resource_picker.cpp +++ b/editor/inspector/editor_resource_picker.cpp @@ -89,12 +89,45 @@ void EditorResourcePicker::_update_resource() { preview_rect->set_texture(Ref()); assign_button->set_custom_minimum_size(assign_button_min_size); - if (edited_resource == Ref()) { assign_button->set_button_icon(Ref()); assign_button->set_text(TTR("")); assign_button->set_tooltip_text(""); + make_unique_button->set_disabled(true); + make_unique_button->set_visible(false); } else { + Ref parent_res = _has_parent_resource(); + bool unique_enable = _is_uniqueness_enabled(); + bool unique_recursive_enabled = _is_uniqueness_enabled(true); + bool is_internal = EditorNode::get_singleton()->is_resource_internal_to_scene(edited_resource); + int num_of_copies = EditorNode::get_singleton()->get_resource_count(edited_resource); + make_unique_button->set_button_icon(get_editor_theme_icon(SNAME("Instance"))); + make_unique_button->set_visible((num_of_copies > 1 || !is_internal) && !Object::cast_to