From aa6867e6c95e80e003be38037d7149e6648ed32d Mon Sep 17 00:00:00 2001
From: David Snopek <dsnopek@gmail.com>
Date: Sat, 9 Sep 2023 12:52:31 -0500
Subject: [PATCH] Support `_validate_property()`

---
 gdextension/gdextension_interface.h      |  4 ++++
 include/godot_cpp/classes/wrapped.hpp    | 25 ++++++++++++++++++++++++
 include/godot_cpp/core/class_db.hpp      |  1 +
 include/godot_cpp/core/property_info.hpp | 12 ++++++++++++
 test/project/main.gd                     |  4 ++++
 test/src/example.cpp                     |  8 ++++++++
 test/src/example.h                       |  1 +
 7 files changed, 55 insertions(+)

diff --git a/gdextension/gdextension_interface.h b/gdextension/gdextension_interface.h
index ba0503d1f..9d9ae20c5 100644
--- a/gdextension/gdextension_interface.h
+++ b/gdextension/gdextension_interface.h
@@ -258,6 +258,7 @@ typedef const GDExtensionPropertyInfo *(*GDExtensionClassGetPropertyList)(GDExte
 typedef void (*GDExtensionClassFreePropertyList)(GDExtensionClassInstancePtr p_instance, const GDExtensionPropertyInfo *p_list);
 typedef GDExtensionBool (*GDExtensionClassPropertyCanRevert)(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name);
 typedef GDExtensionBool (*GDExtensionClassPropertyGetRevert)(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionVariantPtr r_ret);
+typedef GDExtensionBool (*GDExtensionClassValidateProperty)(GDExtensionClassInstancePtr p_instance, GDExtensionPropertyInfo *p_property);
 typedef void (*GDExtensionClassNotification)(GDExtensionClassInstancePtr p_instance, int32_t p_what); // Deprecated. Use GDExtensionClassNotification2 instead.
 typedef void (*GDExtensionClassNotification2)(GDExtensionClassInstancePtr p_instance, int32_t p_what, GDExtensionBool p_reversed);
 typedef void (*GDExtensionClassToString)(GDExtensionClassInstancePtr p_instance, GDExtensionBool *r_is_valid, GDExtensionStringPtr p_out);
@@ -298,6 +299,7 @@ typedef struct {
 	GDExtensionClassFreePropertyList free_property_list_func;
 	GDExtensionClassPropertyCanRevert property_can_revert_func;
 	GDExtensionClassPropertyGetRevert property_get_revert_func;
+	GDExtensionClassValidateProperty validate_property_func;
 	GDExtensionClassNotification2 notification_func;
 	GDExtensionClassToString to_string_func;
 	GDExtensionClassReference reference_func;
@@ -374,6 +376,7 @@ typedef GDExtensionBool (*GDExtensionScriptInstanceGet)(GDExtensionScriptInstanc
 typedef const GDExtensionPropertyInfo *(*GDExtensionScriptInstanceGetPropertyList)(GDExtensionScriptInstanceDataPtr p_instance, uint32_t *r_count);
 typedef void (*GDExtensionScriptInstanceFreePropertyList)(GDExtensionScriptInstanceDataPtr p_instance, const GDExtensionPropertyInfo *p_list);
 typedef GDExtensionVariantType (*GDExtensionScriptInstanceGetPropertyType)(GDExtensionScriptInstanceDataPtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionBool *r_is_valid);
+typedef GDExtensionBool (*GDExtensionScriptInstanceValidateProperty)(GDExtensionScriptInstanceDataPtr p_instance, GDExtensionPropertyInfo *p_property);
 
 typedef GDExtensionBool (*GDExtensionScriptInstancePropertyCanRevert)(GDExtensionScriptInstanceDataPtr p_instance, GDExtensionConstStringNamePtr p_name);
 typedef GDExtensionBool (*GDExtensionScriptInstancePropertyGetRevert)(GDExtensionScriptInstanceDataPtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionVariantPtr r_ret);
@@ -460,6 +463,7 @@ typedef struct {
 	GDExtensionScriptInstanceGetMethodList get_method_list_func;
 	GDExtensionScriptInstanceFreeMethodList free_method_list_func;
 	GDExtensionScriptInstanceGetPropertyType get_property_type_func;
+	GDExtensionScriptInstanceValidateProperty validate_property_func;
 
 	GDExtensionScriptInstanceHasMethod has_method_func;
 
diff --git a/include/godot_cpp/classes/wrapped.hpp b/include/godot_cpp/classes/wrapped.hpp
index aa2c9fc4a..c6e452cf6 100644
--- a/include/godot_cpp/classes/wrapped.hpp
+++ b/include/godot_cpp/classes/wrapped.hpp
@@ -60,6 +60,7 @@ class Wrapped {
 	void _get_property_list(List<PropertyInfo> *p_list) const {}
 	bool _property_can_revert(const StringName &p_name) const { return false; }
 	bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return false; }
+	void _validate_property(PropertyInfo &p_property) const {}
 	String _to_string() const { return "[" + String(get_class_static()) + ":" + itos(get_instance_id()) + "]"; }
 
 	static void notification_bind(GDExtensionClassInstancePtr p_instance, int32_t p_what, GDExtensionBool p_reversed) {}
@@ -69,6 +70,7 @@ class Wrapped {
 	static void free_property_list_bind(GDExtensionClassInstancePtr p_instance, const GDExtensionPropertyInfo *p_list) {}
 	static GDExtensionBool property_can_revert_bind(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name) { return false; }
 	static GDExtensionBool property_get_revert_bind(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionVariantPtr r_ret) { return false; }
+	static GDExtensionBool validate_property_bind(GDExtensionClassInstancePtr p_instance, GDExtensionPropertyInfo *p_property) { return false; }
 	static void to_string_bind(GDExtensionClassInstancePtr p_instance, GDExtensionBool *r_is_valid, GDExtensionStringPtr r_out) {}
 
 	// The only reason this has to be held here, is when we return results of `_get_property_list` to Godot, we pass
@@ -150,6 +152,10 @@ protected:
 		return (bool(::godot::Wrapped::*)(const ::godot::StringName &p_name, ::godot::Variant &) const) & m_class::_property_get_revert;                                               \
 	}                                                                                                                                                                                  \
                                                                                                                                                                                        \
+	static void (::godot::Wrapped::*_get_validate_property())(::godot::PropertyInfo & p_property) const {                                                                              \
+		return (void(::godot::Wrapped::*)(::godot::PropertyInfo & p_property) const) & m_class::_validate_property;                                                                    \
+	}                                                                                                                                                                                  \
+                                                                                                                                                                                       \
 	static ::godot::String (::godot::Wrapped::*_get_to_string())() const {                                                                                                             \
 		return (::godot::String(::godot::Wrapped::*)() const) & m_class::_to_string;                                                                                                   \
 	}                                                                                                                                                                                  \
@@ -267,6 +273,21 @@ public:
 		return false;                                                                                                                                                                  \
 	}                                                                                                                                                                                  \
                                                                                                                                                                                        \
+	static GDExtensionBool validate_property_bind(GDExtensionClassInstancePtr p_instance, GDExtensionPropertyInfo *p_property) {                                                       \
+		bool ret = false;                                                                                                                                                              \
+		if (p_instance && m_class::_get_validate_property()) {                                                                                                                         \
+			ret = m_inherits::validate_property_bind(p_instance, p_property);                                                                                                          \
+			if (m_class::_get_validate_property() != m_inherits::_get_validate_property()) {                                                                                           \
+				m_class *cls = reinterpret_cast<m_class *>(p_instance);                                                                                                                \
+				::godot::PropertyInfo info(p_property);                                                                                                                                \
+				cls->_validate_property(info);                                                                                                                                         \
+				info._update(p_property);                                                                                                                                              \
+				return true;                                                                                                                                                           \
+			}                                                                                                                                                                          \
+		}                                                                                                                                                                              \
+		return ret;                                                                                                                                                                    \
+	}                                                                                                                                                                                  \
+                                                                                                                                                                                       \
 	static void to_string_bind(GDExtensionClassInstancePtr p_instance, GDExtensionBool *r_is_valid, GDExtensionStringPtr r_out) {                                                      \
 		if (p_instance && m_class::_get_to_string()) {                                                                                                                                 \
 			if (m_class::_get_to_string() != m_inherits::_get_to_string()) {                                                                                                           \
@@ -345,6 +366,10 @@ protected:
 		return nullptr;                                                                                                    \
 	}                                                                                                                      \
                                                                                                                            \
+	static void (Wrapped::*_get_validate_property())(::godot::PropertyInfo & p_property) const {                           \
+		return nullptr;                                                                                                    \
+	}                                                                                                                      \
+                                                                                                                           \
 	static String (Wrapped::*_get_to_string())() const {                                                                   \
 		return nullptr;                                                                                                    \
 	}                                                                                                                      \
diff --git a/include/godot_cpp/core/class_db.hpp b/include/godot_cpp/core/class_db.hpp
index 65694453c..1a17b573d 100644
--- a/include/godot_cpp/core/class_db.hpp
+++ b/include/godot_cpp/core/class_db.hpp
@@ -186,6 +186,7 @@ void ClassDB::_register_class(bool p_virtual, bool p_exposed) {
 		T::free_property_list_bind, // GDExtensionClassFreePropertyList free_property_list_func;
 		T::property_can_revert_bind, // GDExtensionClassPropertyCanRevert property_can_revert_func;
 		T::property_get_revert_bind, // GDExtensionClassPropertyGetRevert property_get_revert_func;
+		T::validate_property_bind, // GDExtensionClassValidateProperty validate_property_func;
 		T::notification_bind, // GDExtensionClassNotification2 notification_func;
 		T::to_string_bind, // GDExtensionClassToString to_string_func;
 		nullptr, // GDExtensionClassReference reference_func;
diff --git a/include/godot_cpp/core/property_info.hpp b/include/godot_cpp/core/property_info.hpp
index 8146859f3..0ecfa322c 100644
--- a/include/godot_cpp/core/property_info.hpp
+++ b/include/godot_cpp/core/property_info.hpp
@@ -68,6 +68,18 @@ struct PropertyInfo {
 
 	PropertyInfo(GDExtensionVariantType p_type, const StringName &p_name, PropertyHint p_hint = PROPERTY_HINT_NONE, const String &p_hint_string = "", uint32_t p_usage = PROPERTY_USAGE_DEFAULT, const StringName &p_class_name = "") :
 			PropertyInfo((Variant::Type)p_type, p_name, p_hint, p_hint_string, p_usage, p_class_name) {}
+
+	PropertyInfo(const GDExtensionPropertyInfo *p_info) :
+			PropertyInfo(p_info->type, *reinterpret_cast<StringName *>(p_info->name), (PropertyHint)p_info->hint, *reinterpret_cast<String *>(p_info->hint_string), p_info->usage, *reinterpret_cast<StringName *>(p_info->class_name)) {}
+
+	void _update(GDExtensionPropertyInfo *p_info) {
+		p_info->type = (GDExtensionVariantType)type;
+		*(reinterpret_cast<StringName *>(p_info->name)) = name;
+		p_info->hint = hint;
+		*(reinterpret_cast<String *>(p_info->hint_string)) = hint_string;
+		p_info->usage = usage;
+		*(reinterpret_cast<StringName *>(p_info->class_name)) = class_name;
+	}
 };
 
 } // namespace godot
diff --git a/test/project/main.gd b/test/project/main.gd
index 473c15fe4..715b13e85 100644
--- a/test/project/main.gd
+++ b/test/project/main.gd
@@ -23,6 +23,10 @@ func _ready():
 	# Property list.
 	example.property_from_list = Vector3(100, 200, 300)
 	assert_equal(example.property_from_list, Vector3(100, 200, 300))
+	var prop_list = example.get_property_list()
+	for prop_info in prop_list:
+		if prop_info['name'] == 'mouse_filter':
+			assert_equal(prop_info['usage'], PROPERTY_USAGE_NO_EDITOR)
 
 	# Call simple methods.
 	example.simple_func()
diff --git a/test/src/example.cpp b/test/src/example.cpp
index 8a761a4c1..dc471dd25 100644
--- a/test/src/example.cpp
+++ b/test/src/example.cpp
@@ -117,6 +117,14 @@ bool Example::_property_get_revert(const StringName &p_name, Variant &r_property
 	}
 };
 
+void Example::_validate_property(PropertyInfo &p_property) const {
+	String name = p_property.name;
+	// Test hiding the "mouse_filter" property from the editor.
+	if (name == "mouse_filter") {
+		p_property.usage = PROPERTY_USAGE_NO_EDITOR;
+	}
+}
+
 void Example::_bind_methods() {
 	// Methods.
 	ClassDB::bind_method(D_METHOD("simple_func"), &Example::simple_func);
diff --git a/test/src/example.h b/test/src/example.h
index 6e00b7f5f..49d103e3b 100644
--- a/test/src/example.h
+++ b/test/src/example.h
@@ -65,6 +65,7 @@ class Example : public Control {
 	void _get_property_list(List<PropertyInfo> *p_list) const;
 	bool _property_can_revert(const StringName &p_name) const;
 	bool _property_get_revert(const StringName &p_name, Variant &r_property) const;
+	void _validate_property(PropertyInfo &p_property) const;
 
 	String _to_string() const;
 
