diff options
author | reduz <reduzio@gmail.com> | 2022-03-23 21:08:54 +0100 |
---|---|---|
committer | reduz <reduzio@gmail.com> | 2022-03-24 14:21:52 +0100 |
commit | 09b951b99b1a8e799b00adaa896c788949c18017 (patch) | |
tree | 4a81fb9584b90063c651758ba53b16462083fe66 | |
parent | d7d528c15f0e858b52bb0f510ff47e65c2341de1 (diff) |
Refactor Object metadata
* API kept the same (Although functions could be renamed to set_metadata/get_metadata in a later PR), so not much should change.
* Metadata now exposed as individual properties.
* Properties are editable in inspector (unless metadata name begins with _) under the metadata/ namespace.
* Added the ability to Add/Remove metadata properties to the inspector.
This is a functionality that was requested very often, that makes metadata work a bit more similar to custom properties in Blender.
-rw-r--r-- | core/object/object.cpp | 70 | ||||
-rw-r--r-- | core/object/object.h | 4 | ||||
-rw-r--r-- | core/variant/variant_construct.h | 6 | ||||
-rw-r--r-- | editor/editor_inspector.cpp | 104 | ||||
-rw-r--r-- | editor/editor_inspector.h | 12 | ||||
-rw-r--r-- | editor/inspector_dock.cpp | 1 | ||||
-rw-r--r-- | scene/gui/dialogs.cpp | 3 | ||||
-rw-r--r-- | tests/core/io/test_resource.h | 6 | ||||
-rw-r--r-- | tests/core/object/test_object.h | 2 |
9 files changed, 175 insertions, 33 deletions
diff --git a/core/object/object.cpp b/core/object/object.cpp index 096edd4e60..226d3ef0b8 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -416,12 +416,22 @@ void Object::set(const StringName &p_name, const Variant &p_value, bool *r_valid } return; - } else if (p_name == CoreStringNames::get_singleton()->_meta) { - metadata = p_value.duplicate(); - if (r_valid) { - *r_valid = true; + } else { + OrderedHashMap<StringName, Variant>::Element *E = metadata_properties.getptr(p_name); + if (E) { + E->get() = p_value; + if (r_valid) { + *r_valid = true; + } + return; + } else if (p_name.operator String().begins_with("metadata/")) { + // Must exist, otherwise duplicate() will not work. + set_meta(p_name.operator String().replace_first("metadata/", ""), p_value); + if (r_valid) { + *r_valid = true; + } + return; } - return; } // Something inside the object... :| @@ -496,9 +506,12 @@ Variant Object::get(const StringName &p_name, bool *r_valid) const { *r_valid = true; } return ret; + } + + const OrderedHashMap<StringName, Variant>::Element *E = metadata_properties.getptr(p_name); - } else if (p_name == CoreStringNames::get_singleton()->_meta) { - ret = metadata; + if (E) { + ret = E->get(); if (r_valid) { *r_valid = true; } @@ -648,13 +661,20 @@ void Object::get_property_list(List<PropertyInfo> *p_list, bool p_reversed) cons if (!is_class("Script")) { // can still be set, but this is for user-friendliness p_list->push_back(PropertyInfo(Variant::OBJECT, "script", PROPERTY_HINT_RESOURCE_TYPE, "Script", PROPERTY_USAGE_DEFAULT)); } - if (!metadata.is_empty()) { - p_list->push_back(PropertyInfo(Variant::DICTIONARY, "__meta__", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); - } + if (script_instance && !p_reversed) { p_list->push_back(PropertyInfo(Variant::NIL, "Script Variables", PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY)); script_instance->get_property_list(p_list); } + + for (OrderedHashMap<StringName, Variant>::ConstElement K = metadata.front(); K; K = K.next()) { + PropertyInfo pi = PropertyInfo(K.value().get_type(), "metadata/" + K.key().operator String()); + if (K.value().get_type() == Variant::OBJECT) { + pi.hint = PROPERTY_HINT_RESOURCE_TYPE; + pi.hint_string = "Resource"; + } + p_list->push_back(pi); + } } void Object::_validate_property(PropertyInfo &property) const { @@ -915,11 +935,23 @@ bool Object::has_meta(const StringName &p_name) const { void Object::set_meta(const StringName &p_name, const Variant &p_value) { if (p_value.get_type() == Variant::NIL) { - metadata.erase(p_name); + if (metadata.has(p_name)) { + metadata.erase(p_name); + metadata_properties.erase("metadata/" + p_name.operator String()); + notify_property_list_changed(); + } return; } - metadata[p_name] = p_value; + OrderedHashMap<StringName, Variant>::Element E = metadata.find(p_name); + if (E) { + E.value() = p_value; + } else { + ERR_FAIL_COND(!p_name.operator String().is_valid_identifier()); + E = metadata.insert(p_name, p_value); + metadata_properties["metadata/" + p_name.operator String()] = E; + notify_property_list_changed(); + } } Variant Object::get_meta(const StringName &p_name) const { @@ -928,7 +960,7 @@ Variant Object::get_meta(const StringName &p_name) const { } void Object::remove_meta(const StringName &p_name) { - metadata.erase(p_name); + set_meta(p_name, Variant()); } Array Object::_get_property_list_bind() const { @@ -954,20 +986,16 @@ Array Object::_get_method_list_bind() const { Vector<StringName> Object::_get_meta_list_bind() const { Vector<StringName> _metaret; - List<Variant> keys; - metadata.get_key_list(&keys); - for (const Variant &E : keys) { - _metaret.push_back(E); + for (OrderedHashMap<StringName, Variant>::ConstElement K = metadata.front(); K; K = K.next()) { + _metaret.push_back(K.key()); } return _metaret; } void Object::get_meta_list(List<StringName> *p_list) const { - List<Variant> keys; - metadata.get_key_list(&keys); - for (const Variant &E : keys) { - p_list->push_back(E); + for (OrderedHashMap<StringName, Variant>::ConstElement K = metadata.front(); K; K = K.next()) { + p_list->push_back(K.key()); } } diff --git a/core/object/object.h b/core/object/object.h index 6b4f1c81e6..41365cfe51 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -39,6 +39,7 @@ #include "core/templates/hash_map.h" #include "core/templates/list.h" #include "core/templates/map.h" +#include "core/templates/ordered_hash_map.h" #include "core/templates/safe_refcount.h" #include "core/templates/set.h" #include "core/templates/vmap.h" @@ -530,7 +531,8 @@ private: #endif ScriptInstance *script_instance = nullptr; Variant script; // Reference does not exist yet, store it in a Variant. - Dictionary metadata; + OrderedHashMap<StringName, Variant> metadata; + HashMap<StringName, OrderedHashMap<StringName, Variant>::Element> metadata_properties; mutable StringName _class_name; mutable const StringName *_class_ptr = nullptr; diff --git a/core/variant/variant_construct.h b/core/variant/variant_construct.h index 6027cb027e..ce2e9af04f 100644 --- a/core/variant/variant_construct.h +++ b/core/variant/variant_construct.h @@ -543,14 +543,12 @@ public: class VariantConstructNoArgsObject { public: static void construct(Variant &r_ret, const Variant **p_args, Callable::CallError &r_error) { - VariantInternal::clear(&r_ret); - VariantInternal::object_assign_null(&r_ret); + r_ret = (Object *)nullptr; // Must construct a TYPE_OBJECT containing nullptr. r_error.error = Callable::CallError::CALL_OK; } static inline void validated_construct(Variant *r_ret, const Variant **p_args) { - VariantInternal::clear(r_ret); - VariantInternal::object_assign_null(r_ret); + *r_ret = (Object *)nullptr; // Must construct a TYPE_OBJECT containing nullptr. } static void ptr_construct(void *base, const void **p_args) { PtrConstruct<Object *>::construct(nullptr, base); diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 9601eaf5f5..18c9a9f495 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -40,6 +40,7 @@ #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "multi_node_edit.h" +#include "scene/gui/center_container.h" #include "scene/property_utils.h" #include "scene/resources/packed_scene.h" @@ -2506,7 +2507,7 @@ void EditorInspector::update_tree() { List<PropertyInfo>::Element *N = E_property->next(); bool valid = true; while (N) { - if (N->get().usage & PROPERTY_USAGE_EDITOR && (!restrict_to_basic || (N->get().usage & PROPERTY_USAGE_EDITOR_BASIC_SETTING))) { + if (!N->get().name.begins_with("metadata/_") && N->get().usage & PROPERTY_USAGE_EDITOR && (!restrict_to_basic || (N->get().usage & PROPERTY_USAGE_EDITOR_BASIC_SETTING))) { break; } if (N->get().usage & PROPERTY_USAGE_CATEGORY) { @@ -2580,7 +2581,7 @@ void EditorInspector::update_tree() { continue; - } else if (!(p.usage & PROPERTY_USAGE_EDITOR) || _is_property_disabled_by_feature_profile(p.name) || (restrict_to_basic && !(p.usage & PROPERTY_USAGE_EDITOR_BASIC_SETTING))) { + } else if (p.name.begins_with("metadata/_") || !(p.usage & PROPERTY_USAGE_EDITOR) || _is_property_disabled_by_feature_profile(p.name) || (restrict_to_basic && !(p.usage & PROPERTY_USAGE_EDITOR_BASIC_SETTING))) { // Ignore properties that are not supposed to be in the inspector. continue; } @@ -2915,7 +2916,7 @@ void EditorInspector::update_tree() { ep->set_checked(checked); ep->set_keying(keying); ep->set_read_only(property_read_only); - ep->set_deletable(deletable_properties); + ep->set_deletable(deletable_properties || p.name.begins_with("metadata/")); } current_vbox->add_child(F.property_editor); @@ -2956,6 +2957,15 @@ void EditorInspector::update_tree() { } } + if (!hide_metadata) { + Button *add_md = memnew(Button); + add_md->set_text(TTR("Add Metadata")); + add_md->set_focus_mode(Control::FOCUS_NONE); + add_md->set_icon(get_theme_icon("Add", "EditorIcons")); + add_md->connect("pressed", callable_mp(this, &EditorInspector::_show_add_meta_dialog)); + main_vbox->add_child(add_md); + } + // Get the lists of to add at the end. for (Ref<EditorInspectorPlugin> &ped : valid_plugins) { ped->parse_end(object); @@ -3055,6 +3065,11 @@ void EditorInspector::set_hide_script(bool p_hide) { update_tree(); } +void EditorInspector::set_hide_metadata(bool p_hide) { + hide_metadata = p_hide; + update_tree(); +} + void EditorInspector::set_use_filter(bool p_use) { use_filter = p_use; update_tree(); @@ -3323,6 +3338,14 @@ void EditorInspector::_property_deleted(const String &p_path) { return; } + if (p_path.begins_with("metadata/")) { + String name = p_path.replace_first("metadata/", ""); + undo_redo->create_action(vformat(TTR("Remove metadata %s"), name)); + undo_redo->add_do_method(object, "remove_meta", name); + undo_redo->add_undo_method(object, "set_meta", name, object->get_meta(name)); + undo_redo->commit_action(); + } + emit_signal(SNAME("property_deleted"), p_path); } @@ -3650,6 +3673,81 @@ Variant EditorInspector::get_property_clipboard() const { return property_clipboard; } +void EditorInspector::_add_meta_confirm() { + String name = add_meta_name->get_text(); + + object->editor_set_section_unfold("metadata", true); // Ensure metadata is unfolded when adding a new metadata. + + Variant defval; + Callable::CallError ce; + Variant::construct(Variant::Type(add_meta_type->get_selected_id()), defval, nullptr, 0, ce); + undo_redo->create_action(vformat(TTR("Add metadata %s"), name)); + undo_redo->add_do_method(object, "set_meta", name, defval); + undo_redo->add_undo_method(object, "remove_meta", name); + undo_redo->commit_action(); +} + +void EditorInspector::_check_meta_name(String name) { + String error; + + if (name == "") { + error = TTR("Metadata can't be empty."); + } else if (!name.is_valid_identifier()) { + error = TTR("Invalid metadata identifier."); + } else if (object->has_meta(name)) { + error = TTR("Metadata already exists."); + } + + if (error != "") { + add_meta_error->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); + add_meta_error->set_text(error); + add_meta_dialog->get_ok_button()->set_disabled(true); + } else { + add_meta_error->add_theme_color_override("font_color", get_theme_color(SNAME("success_color"), SNAME("Editor"))); + add_meta_error->set_text(TTR("Metadata name is valid.")); + add_meta_dialog->get_ok_button()->set_disabled(false); + } +} + +void EditorInspector::_show_add_meta_dialog() { + if (!add_meta_dialog) { + add_meta_dialog = memnew(ConfirmationDialog); + add_meta_dialog->set_title(TTR("Add Metadata Property")); + VBoxContainer *vbc = memnew(VBoxContainer); + add_meta_dialog->add_child(vbc); + HBoxContainer *hbc = memnew(HBoxContainer); + vbc->add_child(hbc); + hbc->add_child(memnew(Label(TTR("Name:")))); + add_meta_name = memnew(LineEdit); + add_meta_name->set_custom_minimum_size(Size2(200 * EDSCALE, 1)); + hbc->add_child(add_meta_name); + hbc->add_child(memnew(Label(TTR("Type:")))); + add_meta_type = memnew(OptionButton); + for (int i = 0; i < Variant::VARIANT_MAX; i++) { + if (i == Variant::NIL || i == Variant::RID || i == Variant::CALLABLE || i == Variant::SIGNAL) { + continue; //not editable by inspector. + } + String type = i == Variant::OBJECT ? String("Resource") : Variant::get_type_name(Variant::Type(i)); + + add_meta_type->add_icon_item(get_theme_icon(type, "EditorIcons"), type, i); + } + hbc->add_child(add_meta_type); + add_meta_dialog->get_ok_button()->set_text(TTR("Add")); + add_child(add_meta_dialog); + add_meta_dialog->register_text_enter(add_meta_name); + add_meta_dialog->connect("confirmed", callable_mp(this, &EditorInspector::_add_meta_confirm)); + add_meta_error = memnew(Label); + vbc->add_child(add_meta_error); + + add_meta_name->connect("text_changed", callable_mp(this, &EditorInspector::_check_meta_name)); + } + + add_meta_dialog->popup_centered(); + add_meta_name->set_text(""); + _check_meta_name(""); + add_meta_name->grab_focus(); +} + void EditorInspector::_bind_methods() { ClassDB::bind_method("_edit_request_change", &EditorInspector::_edit_request_change); diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 3c482a07e7..d625a8043c 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -35,6 +35,7 @@ #include "scene/gui/button.h" #include "scene/gui/dialogs.h" #include "scene/gui/line_edit.h" +#include "scene/gui/option_button.h" #include "scene/gui/panel_container.h" #include "scene/gui/scroll_container.h" #include "scene/gui/texture_rect.h" @@ -445,6 +446,7 @@ class EditorInspector : public ScrollContainer { LineEdit *search_box; bool show_categories = false; bool hide_script = true; + bool hide_metadata = true; bool use_doc_hints = false; bool capitalize_paths = true; bool use_filter = false; @@ -511,6 +513,15 @@ class EditorInspector : public ScrollContainer { void _update_inspector_bg(); + ConfirmationDialog *add_meta_dialog = nullptr; + LineEdit *add_meta_name = nullptr; + OptionButton *add_meta_type = nullptr; + Label *add_meta_error = nullptr; + + void _add_meta_confirm(); + void _show_add_meta_dialog(); + void _check_meta_name(String name); + protected: static void _bind_methods(); void _notification(int p_what); @@ -541,6 +552,7 @@ public: void set_show_categories(bool p_show); void set_use_doc_hints(bool p_enable); void set_hide_script(bool p_hide); + void set_hide_metadata(bool p_hide); void set_use_filter(bool p_use); void register_text_enter(Node *p_line_edit); diff --git a/editor/inspector_dock.cpp b/editor/inspector_dock.cpp index 087e51b0cb..9ebea79435 100644 --- a/editor/inspector_dock.cpp +++ b/editor/inspector_dock.cpp @@ -679,6 +679,7 @@ InspectorDock::InspectorDock(EditorData &p_editor_data) { inspector->set_v_size_flags(Control::SIZE_EXPAND_FILL); inspector->set_use_doc_hints(true); inspector->set_hide_script(false); + inspector->set_hide_metadata(false); inspector->set_enable_capitalize_paths(bool(EDITOR_GET("interface/inspector/capitalize_properties"))); inspector->set_use_folding(!bool(EDITOR_GET("interface/inspector/disable_folding"))); inspector->register_text_enter(search); diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index e3744eedca..be57ca9084 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -93,6 +93,9 @@ void AcceptDialog::_notification(int p_what) { } void AcceptDialog::_text_submitted(const String &p_text) { + if (get_ok_button() && get_ok_button()->is_disabled()) { + return; // Do not allow submission if OK button is disabled. + } _ok_pressed(); } diff --git a/tests/core/io/test_resource.h b/tests/core/io/test_resource.h index b3983bb06d..84d651b63f 100644 --- a/tests/core/io/test_resource.h +++ b/tests/core/io/test_resource.h @@ -69,7 +69,7 @@ TEST_CASE("[Resource] Duplication") { TEST_CASE("[Resource] Saving and loading") { Ref<Resource> resource = memnew(Resource); resource->set_name("Hello world"); - resource->set_meta(" ExampleMetadata ", Vector2i(40, 80)); + resource->set_meta("ExampleMetadata", Vector2i(40, 80)); resource->set_meta("string", "The\nstring\nwith\nunnecessary\nline\n\t\\\nbreaks"); Ref<Resource> child_resource = memnew(Resource); child_resource->set_name("I'm a child resource"); @@ -84,7 +84,7 @@ TEST_CASE("[Resource] Saving and loading") { loaded_resource_binary->get_name() == "Hello world", "The loaded resource name should be equal to the expected value."); CHECK_MESSAGE( - loaded_resource_binary->get_meta(" ExampleMetadata ") == Vector2i(40, 80), + loaded_resource_binary->get_meta("ExampleMetadata") == Vector2i(40, 80), "The loaded resource metadata should be equal to the expected value."); CHECK_MESSAGE( loaded_resource_binary->get_meta("string") == "The\nstring\nwith\nunnecessary\nline\n\t\\\nbreaks", @@ -99,7 +99,7 @@ TEST_CASE("[Resource] Saving and loading") { loaded_resource_text->get_name() == "Hello world", "The loaded resource name should be equal to the expected value."); CHECK_MESSAGE( - loaded_resource_text->get_meta(" ExampleMetadata ") == Vector2i(40, 80), + loaded_resource_text->get_meta("ExampleMetadata") == Vector2i(40, 80), "The loaded resource metadata should be equal to the expected value."); CHECK_MESSAGE( loaded_resource_text->get_meta("string") == "The\nstring\nwith\nunnecessary\nline\n\t\\\nbreaks", diff --git a/tests/core/object/test_object.h b/tests/core/object/test_object.h index e44b93bb66..5b9d9cab53 100644 --- a/tests/core/object/test_object.h +++ b/tests/core/object/test_object.h @@ -133,7 +133,7 @@ TEST_CASE("[Object] Core getters") { } TEST_CASE("[Object] Metadata") { - const String meta_path = "hello/world complex métadata\n\n\t\tpath"; + const String meta_path = "complex_metadata_path"; Object object; object.set_meta(meta_path, Color(0, 1, 0)); |