Browse Source

Allow exporting variables of type Variant

kobewi 1 year ago
parent
commit
012d47b089

+ 91 - 3
editor/editor_properties.cpp

@@ -57,7 +57,7 @@
 #include "scene/resources/mesh.h"
 #include "scene/resources/mesh.h"
 #include "scene/resources/visual_shader_nodes.h"
 #include "scene/resources/visual_shader_nodes.h"
 
 
-///////////////////// Nil /////////////////////////
+///////////////////// NIL /////////////////////////
 
 
 void EditorPropertyNil::update_property() {
 void EditorPropertyNil::update_property() {
 }
 }
@@ -68,6 +68,91 @@ EditorPropertyNil::EditorPropertyNil() {
 	add_child(prop_label);
 	add_child(prop_label);
 }
 }
 
 
+//////////////////// VARIANT ///////////////////////
+
+void EditorPropertyVariant::_change_type(int p_to_type) {
+	new_type = Variant::Type(p_to_type);
+
+	Variant zero;
+	Callable::CallError ce;
+	Variant::construct(new_type, zero, nullptr, 0, ce);
+	emit_changed(get_edited_property(), zero);
+}
+
+void EditorPropertyVariant::_set_read_only(bool p_read_only) {
+	change_type->set_disabled(p_read_only);
+	if (sub_property) {
+		sub_property->set_read_only(p_read_only);
+	}
+}
+
+void EditorPropertyVariant::_notification(int p_what) {
+	if (p_what == NOTIFICATION_THEME_CHANGED) {
+		change_type->set_button_icon(get_editor_theme_icon("Edit"));
+
+		PopupMenu *popup = change_type->get_popup();
+		for (int i = 0; i < popup->get_item_count(); i++) {
+			popup->set_item_icon(i, get_editor_theme_icon(Variant::get_type_name(Variant::Type(popup->get_item_id(i)))));
+		}
+	}
+}
+
+void EditorPropertyVariant::update_property() {
+	const Variant &value = get_edited_property_value();
+	if (new_type == Variant::VARIANT_MAX) {
+		new_type = value.get_type();
+	}
+
+	if (new_type != current_type) {
+		current_type = new_type;
+
+		if (sub_property) {
+			memdelete(sub_property);
+			sub_property = nullptr;
+		}
+
+		if (current_type == Variant::OBJECT) {
+			sub_property = EditorInspector::instantiate_property_editor(nullptr, current_type, "", PROPERTY_HINT_RESOURCE_TYPE, "Resource", PROPERTY_USAGE_NONE);
+		} else {
+			sub_property = EditorInspector::instantiate_property_editor(nullptr, current_type, "", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE);
+		}
+		ERR_FAIL_NULL(sub_property);
+
+		sub_property->set_object_and_property(get_edited_object(), get_edited_property());
+		sub_property->set_name_split_ratio(0);
+		sub_property->set_selectable(false);
+		sub_property->set_use_folding(is_using_folding());
+		sub_property->set_read_only(is_read_only());
+		sub_property->set_h_size_flags(SIZE_EXPAND_FILL);
+		sub_property->connect(SNAME("property_changed"), callable_mp((EditorProperty *)this, &EditorProperty::emit_changed));
+		content->add_child(sub_property);
+		content->move_child(sub_property, 0);
+		sub_property->update_property();
+	} else if (sub_property) {
+		sub_property->update_property();
+	}
+	new_type = Variant::VARIANT_MAX;
+}
+
+EditorPropertyVariant::EditorPropertyVariant() {
+	content = memnew(HBoxContainer);
+	add_child(content);
+
+	change_type = memnew(MenuButton);
+	change_type->set_flat(false);
+
+	PopupMenu *popup = change_type->get_popup();
+	for (int i = 0; i < Variant::VARIANT_MAX; i++) {
+		if (i == Variant::CALLABLE || i == Variant::SIGNAL || i == Variant::RID) {
+			// These types can't be constructed or serialized properly, so skip them.
+			continue;
+		}
+		popup->add_item(Variant::get_type_name(Variant::Type(i)), i);
+	}
+	popup->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyVariant::_change_type));
+	content->add_child(change_type);
+}
+
 ///////////////////// TEXT /////////////////////////
 ///////////////////// TEXT /////////////////////////
 
 
 void EditorPropertyText::_set_read_only(bool p_read_only) {
 void EditorPropertyText::_set_read_only(bool p_read_only) {
@@ -3510,8 +3595,11 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_
 	switch (p_type) {
 	switch (p_type) {
 		// atomic types
 		// atomic types
 		case Variant::NIL: {
 		case Variant::NIL: {
-			EditorPropertyNil *editor = memnew(EditorPropertyNil);
-			return editor;
+			if (p_usage & PROPERTY_USAGE_NIL_IS_VARIANT) {
+				return memnew(EditorPropertyVariant);
+			} else {
+				return memnew(EditorPropertyNil);
+			}
 		} break;
 		} break;
 		case Variant::BOOL: {
 		case Variant::BOOL: {
 			EditorPropertyCheck *editor = memnew(EditorPropertyCheck);
 			EditorPropertyCheck *editor = memnew(EditorPropertyCheck);

+ 21 - 0
editor/editor_properties.h

@@ -55,6 +55,27 @@ public:
 	EditorPropertyNil();
 	EditorPropertyNil();
 };
 };
 
 
+class EditorPropertyVariant : public EditorProperty {
+	GDCLASS(EditorPropertyVariant, EditorProperty);
+
+	HBoxContainer *content = nullptr;
+	EditorProperty *sub_property = nullptr;
+	MenuButton *change_type = nullptr;
+
+	Variant::Type current_type = Variant::VARIANT_MAX;
+	Variant::Type new_type = Variant::VARIANT_MAX;
+
+	void _change_type(int p_to_type);
+
+protected:
+	virtual void _set_read_only(bool p_read_only) override;
+	void _notification(int p_what);
+
+public:
+	virtual void update_property() override;
+	EditorPropertyVariant();
+};
+
 class EditorPropertyText : public EditorProperty {
 class EditorPropertyText : public EditorProperty {
 	GDCLASS(EditorPropertyText, EditorProperty);
 	GDCLASS(EditorPropertyText, EditorProperty);
 	LineEdit *text = nullptr;
 	LineEdit *text = nullptr;

+ 1 - 1
editor/plugins/tiles/tile_set_editor.cpp

@@ -998,7 +998,7 @@ void TileSourceInspectorPlugin::_confirm_change_id() {
 }
 }
 
 
 bool TileSourceInspectorPlugin::can_handle(Object *p_object) {
 bool TileSourceInspectorPlugin::can_handle(Object *p_object) {
-	return p_object->is_class("TileSetAtlasSourceProxyObject") || p_object->is_class("TileSetScenesCollectionProxyObject");
+	return p_object && (p_object->is_class("TileSetAtlasSourceProxyObject") || p_object->is_class("TileSetScenesCollectionProxyObject"));
 }
 }
 
 
 bool TileSourceInspectorPlugin::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) {
 bool TileSourceInspectorPlugin::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) {

+ 9 - 8
modules/gdscript/gdscript_parser.cpp

@@ -4533,14 +4533,9 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta
 			return false;
 			return false;
 		}
 		}
 
 
-		if (export_type.is_variant() || export_type.has_no_type()) {
-			if (is_dict) {
-				// Dictionary allowed to have a variant key/value.
-				export_type.kind = GDScriptParser::DataType::BUILTIN;
-			} else {
-				push_error(R"(Cannot use simple "@export" annotation because the type of the initialized value can't be inferred.)", p_annotation);
-				return false;
-			}
+		if (export_type.has_no_type()) {
+			push_error(R"(Cannot use simple "@export" annotation because the type of the initialized value can't be inferred.)", p_annotation);
+			return false;
 		}
 		}
 
 
 		switch (export_type.kind) {
 		switch (export_type.kind) {
@@ -4591,6 +4586,12 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta
 					variable->export_info.class_name = String(export_type.native_type).replace("::", ".");
 					variable->export_info.class_name = String(export_type.native_type).replace("::", ".");
 				}
 				}
 			} break;
 			} break;
+			case GDScriptParser::DataType::VARIANT: {
+				if (export_type.is_variant()) {
+					variable->export_info.type = Variant::NIL;
+					variable->export_info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+				}
+			} break;
 			default:
 			default:
 				push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation);
 				push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation);
 				return false;
 				return false;

+ 5 - 0
modules/mono/editor/GodotTools/GodotTools/Inspector/InspectorPlugin.cs

@@ -10,6 +10,11 @@ namespace GodotTools.Inspector
     {
     {
         public override bool _CanHandle(GodotObject godotObject)
         public override bool _CanHandle(GodotObject godotObject)
         {
         {
+            if (godotObject == null)
+            {
+                return false;
+            }
+
             foreach (var script in EnumerateScripts(godotObject))
             foreach (var script in EnumerateScripts(godotObject))
             {
             {
                 if (script is CSharpScript)
                 if (script is CSharpScript)