Browse Source

Data bindings: Add 'RegisterScalar' function for registering custom types with getter and setter functions

Michael Ragazzon 4 years ago
parent
commit
cead4b40ec

+ 8 - 0
Include/RmlUi/Core/DataModelHandle.h

@@ -93,6 +93,14 @@ public:
 		return BindVariable(name, data_variable);
 	}
 
+	// Register a scalar type with associated get and set functions.
+	// @note This registers a type which can later be used as a normal data variable, while 'BindFunc' registers a named data variable with a specific getter and setter.
+	// @note The type applies to every data model associated with the current Context.
+	template<typename T>
+	bool RegisterScalar(DataTypeGetFunc<T> get_func, DataTypeSetFunc<T> set_func = {}) {
+		return type_register->RegisterScalar<T>(get_func, set_func);
+	}
+
 	// Register a struct type.
 	// @note The type applies to every data model associated with the current Context.
 	// @return A handle which can be used to register struct members.

+ 18 - 0
Include/RmlUi/Core/DataTypeRegister.h

@@ -128,6 +128,24 @@ public:
 		return true;
 	}
 
+	template<typename T>
+	bool RegisterScalar(DataTypeGetFunc<T> get_func, DataTypeSetFunc<T> set_func)
+	{
+		static_assert(!is_valid_data_scalar<T>::value, "Cannot register scalar data type function. Arithmetic types and String are handled internally and does not need to be registered.");
+		FamilyId id = Family<T>::Id();
+
+		auto scalar_func_definition = MakeUnique<ScalarFuncDefinition<T>>(get_func, set_func);
+
+		bool inserted = type_register.emplace(id, std::move(scalar_func_definition)).second;
+		if (!inserted)
+		{
+			RMLUI_LOG_TYPE_ERROR(T, "Scalar function type already registered.");
+			return false;
+		}
+
+		return true;
+	}
+
 	template<typename T>
 	VariableDefinition* RegisterMemberFunc(MemberGetFunc<T> get_func, MemberSetFunc<T> set_func)
 	{

+ 3 - 0
Include/RmlUi/Core/DataTypes.h

@@ -48,6 +48,9 @@ using DataEventFunc = Function<void(DataModelHandle, Event&, const VariantList&)
 template<typename T> using MemberGetFunc = void(T::*)(Variant&);
 template<typename T> using MemberSetFunc = void(T::*)(const Variant&);
 
+template<typename T> using DataTypeGetFunc = void(*)(const T*, Variant&);
+template<typename T> using DataTypeSetFunc = void(*)(T*, const Variant&);
+
 using DirtyVariables = SmallUnorderedSet<String>;
 
 struct DataAddressEntry {

+ 24 - 0
Include/RmlUi/Core/DataVariable.h

@@ -135,6 +135,30 @@ private:
 	DataSetFunc set;
 };
 
+template<typename T>
+class ScalarFuncDefinition final : public VariableDefinition {
+public:
+
+	ScalarFuncDefinition(DataTypeGetFunc<T> get, DataTypeSetFunc<T> set) : VariableDefinition(DataVariableType::Function), get(get), set(set) {}
+
+	bool Get(void* ptr, Variant& variant) override
+	{
+		if (!get)
+			return false;
+		get(static_cast<const T*>(ptr), variant);
+		return true;
+	}
+	bool Set(void* ptr, const Variant& variant) override
+	{
+		if (!set)
+			return false;
+		set(static_cast<T*>(ptr), variant);
+		return true;
+	}
+private:
+	DataTypeGetFunc<T> get;
+	DataTypeSetFunc<T> set;
+};
 
 template<typename Container>
 class ArrayDefinition final : public VariableDefinition {

+ 1 - 1
Samples/basic/databinding/data/databinding.rml

@@ -204,7 +204,7 @@ li {
 	<div data-for="invader : invaders">
 		<h1 data-class-red="invader.danger_rating > 70">{{invader.name}}</h1>
 		<p>Invader {{it_index + 1}} of {{ invaders.size }}.</p>
-		<img data-attr-sprite="invader.sprite" data-style-image-color="invader.color"/>
+		<img data-attr-sprite="invader.sprite" data-style-image-color="invader.color" data-event-click="invader.color = 'rgba(100,50,0,255)'"/>
 		<p>
 			Shots fired (damage): <span data-for="invader.damage"> {{it}} </span>
 		</p>

+ 18 - 14
Samples/basic/databinding/src/main.cpp

@@ -36,6 +36,8 @@
 
 namespace BasicExample {
 
+	using namespace Rml;
+
 	Rml::DataModelHandle model_handle;
 
 	struct MyData {
@@ -164,17 +166,6 @@ namespace InvadersExample {
 		Rml::Colourb color{ 255, 255, 255 };
 		Rml::Vector<int> damage;
 		float danger_rating = 50;
-
-		void GetColor(Rml::Variant& variant) {
-			variant = "rgba(" + Rml::ToString(color) + ')';
-		}
-		void SetColor(const Rml::Variant& variant) {
-			using namespace Rml;
-			String str = variant.Get<String>();
-			if (str.size() > 6)
-				str = str.substr(5, str.size() - 6);
-			color = Rml::FromString<Colourb>(variant.Get<String>());
-		}
 	};
 
 	struct InvadersData {
@@ -201,6 +192,21 @@ namespace InvadersExample {
 		if (!constructor)
 			return false;
 
+		// Register a custom getter/setter for the Colourb type.
+		constructor.RegisterScalar<Rml::Colourb>(
+			[](const Rml::Colourb* color, Rml::Variant& variant) {
+				variant = "rgba(" + Rml::ToString(*color) + ')';
+			},
+			[](Rml::Colourb* color, const Rml::Variant& variant) {
+				Rml::String str = variant.Get<Rml::String>();
+				bool success = false;
+				if (str.size() > 6 && str.substr(0, 5) == "rgba(")
+					success = Rml::TypeConverter<Rml::String, Rml::Colourb>::Convert(str.substr(5), *color);
+				if (!success)
+					Rml::Log::Message(Rml::Log::LT_WARNING, "Invalid color specified: '%s'. Use syntax rgba(R,G,B,A).", str.c_str());
+			}
+		);
+
 		// Since Invader::damage is an array type.
 		constructor.RegisterArray<Rml::Vector<int>>();
 
@@ -209,11 +215,9 @@ namespace InvadersExample {
 		{
 			invader_handle.RegisterMember("name", &Invader::name);
 			invader_handle.RegisterMember("sprite", &Invader::sprite);
+			invader_handle.RegisterMember("color", &Invader::color);
 			invader_handle.RegisterMember("damage", &Invader::damage);
 			invader_handle.RegisterMember("danger_rating", &Invader::danger_rating);
-
-			// Getter and setter functions can also be used.
-			invader_handle.RegisterMemberFunc("color", &Invader::GetColor);
 		}
 
 		// We can even have an Array of Structs, infinitely nested if we so desire.