Browse Source

Merge pull request #59452 from reduz/refactor-metadata

Rémi Verschelde 3 years ago
parent
commit
283246a9fe

+ 49 - 21
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());
 	}
 }
 

+ 3 - 1
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;
 

+ 2 - 4
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);

+ 101 - 3
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);
 

+ 12 - 0
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);

+ 1 - 0
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);

+ 3 - 0
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();
 }
 

+ 3 - 3
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",

+ 1 - 1
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));