Przeglądaj źródła

Merge branch 'databinding_improvements'

# Conflicts:
#	Source/Core/DataViewDefault.cpp
Michael Ragazzon 4 lat temu
rodzic
commit
6e0e3011b2

+ 1 - 0
CMake/FileList.cmake

@@ -138,6 +138,7 @@ set(Core_PUB_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ConvolutionFilter.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Core.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/DataModelHandle.h
+    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/DataStructHandle.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/DataTypeRegister.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/DataTypes.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/DataVariable.h

+ 73 - 10
Include/RmlUi/Core/DataModelHandle.h

@@ -34,6 +34,7 @@
 #include "Traits.h"
 #include "DataTypes.h"
 #include "DataTypeRegister.h"
+#include "DataStructHandle.h"
 
 namespace Rml {
 
@@ -66,13 +67,13 @@ public:
 	DataModelHandle GetModelHandle() const;
 
 	// Bind a data variable.
-	// @note For non-scalar types make sure they first have been registered with the appropriate 'Register...()' functions.
+	// @note For non-builtin types, make sure they first have been registered with the appropriate 'Register...()' functions.
 	template<typename T>
 	bool Bind(const String& name, T* ptr) {
 		RMLUI_ASSERTMSG(ptr, "Invalid pointer to data variable");
-		return BindVariable(name, DataVariable(type_register->GetOrAddScalar<T>(), ptr));
+		return BindVariable(name, DataVariable(type_register->GetDefinition<T>(), static_cast<void*>(ptr)));
 	}
-
+	
 	// Bind a get/set function pair.
 	bool BindFunc(const String& name, DataGetFunc get_func, DataSetFunc set_func = {});
 
@@ -93,22 +94,24 @@ 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 = {});
+
 	// 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.
 	template<typename T>
-	StructHandle<T> RegisterStruct() {
-		return type_register->RegisterStruct<T>();
-	}
+	StructHandle<T> RegisterStruct();
 
 	// Register an array type.
 	// @note The type applies to every data model associated with the current Context.
 	// @note If 'Container::value_type' represents a non-scalar type, that type must already have been registered with the appropriate 'Register...()' functions.
-	// @note Container requires the following functions to be implemented: size() and begin(). This is satisfied by several containers such as std::vector and std::array.
+	// @note Container requires the following functions to be implemented: size() and begin(). This is satisfied by several containers including std::vector and std::array.
 	template<typename Container>
-	bool RegisterArray() {
-		return type_register->RegisterArray<Container>();
-	}
+	bool RegisterArray();
 
 	// Register a transform function.
 	// A transform function modifies a variant with optional arguments. It can be called in data expressions using the pipe '|' operator.
@@ -126,6 +129,66 @@ private:
 	DataTypeRegister* type_register;
 };
 
+
+template<typename T>
+inline bool DataModelConstructor::RegisterScalar(DataTypeGetFunc<T> get_func, DataTypeSetFunc<T> set_func)
+{
+	static_assert(!is_builtin_data_scalar<T>::value, "Cannot register scalar data type function. Arithmetic types and String are handled internally and does not need to be registered.");
+	const FamilyId id = Family<T>::Id();
+
+	auto scalar_func_definition = MakeUnique<ScalarFuncDefinition<T>>(get_func, set_func);
+
+	const bool inserted = type_register->RegisterDefinition(id, std::move(scalar_func_definition));
+	if (!inserted)
+	{
+		RMLUI_LOG_TYPE_ERROR(T, "Scalar function type already registered.");
+		return false;
+	}
+
+	return true;
+}
+
+template<typename T>
+inline StructHandle<T> DataModelConstructor::RegisterStruct()
+{
+	static_assert(std::is_class<T>::value, "Type must be a struct or class type.");
+	const FamilyId id = Family<T>::Id();
+
+	auto struct_definition = MakeUnique<StructDefinition>();
+	StructDefinition* struct_variable_raw = struct_definition.get();
+
+	const bool inserted = type_register->RegisterDefinition(id, std::move(struct_definition));
+	if (!inserted)
+	{
+		RMLUI_LOG_TYPE_ERROR(T, "Struct type already declared");
+		return StructHandle<T>(nullptr, nullptr);
+	}
+
+	return StructHandle<T>(type_register, struct_variable_raw);
+}
+
+template<typename Container>
+inline bool DataModelConstructor::RegisterArray()
+{
+	using value_type = typename Container::value_type;
+	VariableDefinition* value_variable = type_register->GetDefinition<value_type>();
+	RMLUI_LOG_TYPE_ERROR_ASSERT(value_type, value_variable, "Underlying value type of array has not been registered.");
+	if (!value_variable)
+		return false;
+
+	const FamilyId container_id = Family<Container>::Id();
+	auto array_definition = MakeUnique<ArrayDefinition<Container>>(value_variable);
+
+	const bool inserted = type_register->RegisterDefinition(container_id, std::move(array_definition));
+	if (!inserted)
+	{
+		RMLUI_LOG_TYPE_ERROR(Container, "Array type already declared.");
+		return false;
+	}
+
+	return true;
+}
+
 } // namespace Rml
 
 #endif

+ 210 - 0
Include/RmlUi/Core/DataStructHandle.h

@@ -0,0 +1,210 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef RMLUI_CORE_DATASTRUCTHANDLE_H
+#define RMLUI_CORE_DATASTRUCTHANDLE_H
+
+#include "Header.h"
+#include "Types.h"
+#include "Traits.h"
+#include "DataTypes.h"
+#include "DataVariable.h"
+
+
+namespace Rml {
+
+template<typename Object>
+class StructHandle {
+public:
+	StructHandle(DataTypeRegister* type_register, StructDefinition* struct_definition) : type_register(type_register), struct_definition(struct_definition) {}
+
+	/// Register a member object.
+	/// @note Underlying type must be registered before it is used as a member.
+	/// @note Getter functions can return by reference, raw pointer, or by value. If returned by value,
+	///       the returned type must be a scalar data type, otherwise any data type can be used.
+	/// @example
+	///		struct Invader {
+	///			int health;
+	/// 	};
+	///		struct_handle.RegisterMember("health", &Invader::health);
+	template <typename MemberType>
+	bool RegisterMember(const String& name, MemberType Object::* member_object_ptr)
+	{
+		return CreateMemberObjectDefinition(name, member_object_ptr);
+	}
+
+	/// Register a member getter function.
+	/// @note Underlying type must be registered before it is used as a member.
+	/// @note Getter functions can return by reference, raw pointer, or by value. If returned by value,
+	///       the returned type must be a scalar data type, otherwise any data type can be used.
+	/// @example
+	///		struct Invader {
+	///			std::vector<Weapon>& GetWeapons();
+	///			/* ... */
+	/// 	};
+	///		struct_handle.RegisterMember("weapons", &Invader::GetWeapons);
+	template <typename ReturnType>
+	bool RegisterMember(const String& name, ReturnType(Object::* member_get_func_ptr)())
+	{
+		return RegisterMemberGetter(name, member_get_func_ptr);
+	}
+
+	/// Register member getter and setter functions. The getter and setter functions must return and assign scalar value types.
+	/// @note Underlying type must be registered before it is used as a member.
+	/// @note Getter and setter functions can return by reference, raw pointer, or by value.
+	/// @example
+	///		struct Invader {
+	///			MyColorType GetColor();
+	///			void SetColor(MyColorType color);
+	///			/* ... */
+	/// 	};
+	///		struct_handle.RegisterMember("color", &Invader::GetColor, &Invader::SetColor);
+	template <typename ReturnType, typename AssignType>
+	bool RegisterMember(const String& name, ReturnType(Object::* member_get_func_ptr)(), void(Object::* member_set_func_ptr)(AssignType))
+	{
+		using BasicReturnType = typename std::remove_reference<ReturnType>::type;
+		using BasicAssignType = typename std::remove_const<typename std::remove_reference<AssignType>::type>::type;
+		using UnderlyingType = typename std::conditional<IsVoidMemberFunc<ReturnType>::value, BasicAssignType, BasicReturnType>::type;
+
+		static_assert(IsVoidMemberFunc<ReturnType>::value || IsVoidMemberFunc<AssignType>::value || std::is_same<BasicReturnType, BasicAssignType>::value, "Provided getter and setter functions must get and set the same type.");
+
+		return CreateMemberScalarGetSetFuncDefinition<UnderlyingType>(name, member_get_func_ptr, member_set_func_ptr);
+	}
+
+	explicit operator bool() const {
+		return type_register && struct_definition;
+	}
+
+private:
+
+	// Member getter with reference return type.
+	template <typename ReturnType>
+	bool RegisterMemberGetter(const String& name, ReturnType& (Object::* member_get_func_ptr)()) {
+		return CreateMemberGetFuncDefinition<ReturnType>(name, member_get_func_ptr);
+	}
+
+	// Member getter with pointer return type.
+	template <typename ReturnType>
+	bool RegisterMemberGetter(const String& name, ReturnType* (Object::* member_get_func_ptr)()) {
+		return CreateMemberGetFuncDefinition<ReturnType>(name, member_get_func_ptr);
+	}
+
+	// Member getter with value return type, only valid for scalar return types.
+	template <typename ReturnType>
+	bool RegisterMemberGetter(const String& name, ReturnType (Object::* member_get_func_ptr)()) {
+		using SetType = VoidMemberFunc Object::*;
+		return CreateMemberScalarGetSetFuncDefinition<ReturnType>(name, member_get_func_ptr, SetType{});
+	}
+
+	template<typename MemberType>
+	bool CreateMemberObjectDefinition(const String& name, MemberType Object::* member_ptr);
+
+	template<typename BasicReturnType, typename MemberType>
+	bool CreateMemberGetFuncDefinition(const String& name, MemberType Object::* member_get_func_ptr);
+
+	template<typename UnderlyingType, typename MemberGetType, typename MemberSetType>
+	bool CreateMemberScalarGetSetFuncDefinition(const String& name, MemberGetType Object::* member_get_func_ptr, MemberSetType Object::* member_set_func_ptr);
+
+	DataTypeRegister* type_register;
+	StructDefinition* struct_definition;
+};
+
+
+
+template<typename Object>
+template<typename MemberType>
+bool StructHandle<Object>::CreateMemberObjectDefinition(const String& name, MemberType Object::* member_ptr)
+{
+	using MemberObjectPtr = MemberType Object::*;
+
+	// If the member function signature doesn't match the getter function signature, it will end up calling this function. Emit a compile error in that case.
+	static_assert(!std::is_member_function_pointer<MemberObjectPtr>::value, "Illegal data member getter function signature. Make sure it takes no arguments and is not const qualified.");
+	static_assert(!std::is_const<MemberType>::value, "Data member objects cannot be const qualified.");
+
+	VariableDefinition* underlying_definition = type_register->GetDefinition<MemberType>();
+	if (!underlying_definition)
+		return false;
+	struct_definition->AddMember(
+		name,
+		MakeUnique<MemberObjectDefinition<Object, MemberType>>(underlying_definition, member_ptr)
+	);
+	return true;
+}
+
+template<typename Object>
+template<typename BasicReturnType, typename MemberType>
+bool StructHandle<Object>::CreateMemberGetFuncDefinition(const String& name, MemberType Object::* member_get_func_ptr)
+{
+	static_assert(!std::is_const<BasicReturnType>::value, "Returned type from data member function cannot be const qualified.");
+
+	VariableDefinition* underlying_definition = type_register->GetDefinition<BasicReturnType>();
+	if (!underlying_definition)
+		return false;
+
+	struct_definition->AddMember(
+		name,
+		MakeUnique<MemberGetFuncDefinition<Object, MemberType, BasicReturnType>>(underlying_definition, member_get_func_ptr)
+	);
+	return true;
+}
+
+template<typename Object>
+template<typename UnderlyingType, typename MemberGetType, typename MemberSetType>
+bool StructHandle<Object>::CreateMemberScalarGetSetFuncDefinition(const String& name, MemberGetType Object::* member_get_func_ptr, MemberSetType Object::* member_set_func_ptr)
+{
+	static_assert(std::is_default_constructible<UnderlyingType>::value, "Struct member getter/setter functions must return/assign a type that is default constructible.");
+	static_assert(!std::is_const<UnderlyingType>::value, "Const qualified type illegal in data member getter functions.");
+
+	if (!IsVoidMemberFunc<MemberGetType>::value)
+	{
+		RMLUI_ASSERTMSG(member_get_func_ptr, "Expected member getter function, but none provided.");
+	}
+	if (!IsVoidMemberFunc<MemberSetType>::value)
+	{
+		RMLUI_ASSERTMSG(member_get_func_ptr, "Expected member setter function, but none provided.");
+	}
+
+	VariableDefinition* underlying_definition = type_register->GetDefinition<UnderlyingType>();
+	if (!underlying_definition)
+		return false;
+
+	if (underlying_definition->Type() != DataVariableType::Scalar)
+	{
+		RMLUI_LOG_TYPE_ERROR(UnderlyingType, "Only scalar data variables are allowed here. Data member functions require scalar types when returning by value, or using getter/setter function pairs.");
+		return false;
+	}
+
+	struct_definition->AddMember(
+		name,
+		MakeUnique<MemberScalarGetSetFuncDefinition<Object, MemberGetType, MemberSetType, UnderlyingType>>(underlying_definition, member_get_func_ptr, member_set_func_ptr)
+	);
+	return true;
+}
+
+} // namespace Rml
+#endif

+ 51 - 106
Include/RmlUi/Core/DataTypeRegister.h

@@ -32,47 +32,22 @@
 #include "Header.h"
 #include "Types.h"
 #include "Traits.h"
-#include "Variant.h"
 #include "DataTypes.h"
 #include "DataVariable.h"
 
 
 namespace Rml {
 
-#define RMLUI_LOG_TYPE_ERROR(T, msg) RMLUI_ERRORMSG((String(msg) + String(" T: ") + String(rmlui_type_name<T>())).c_str())
-#define RMLUI_LOG_TYPE_ERROR_ASSERT(T, val, msg) RMLUI_ASSERTMSG(val, (String(msg) + String(" T: ") + String(rmlui_type_name<T>())).c_str())
-
 template<typename T>
-struct is_valid_data_scalar {
+struct is_builtin_data_scalar {
 	static constexpr bool value = std::is_arithmetic<T>::value
-		|| std::is_same<typename std::remove_cv<T>::type, String>::value;
-};
-
-
-template<typename Object>
-class StructHandle {
-public:
-	StructHandle(DataTypeRegister* type_register, StructDefinition* struct_definition) : type_register(type_register), struct_definition(struct_definition) {}
-	
-	template <typename MemberType>
-	StructHandle<Object>& RegisterMember(const String& name, MemberType Object::* member_ptr);
-
-	StructHandle<Object>& RegisterMemberFunc(const String& name, MemberGetFunc<Object> get_func, MemberSetFunc<Object> set_func = nullptr);
-
-	explicit operator bool() const {
-		return type_register && struct_definition;
-	}
-
-private:
-	DataTypeRegister* type_register;
-	StructDefinition* struct_definition;
+		|| std::is_same<typename std::remove_const<T>::type, String>::value;
 };
 
 
 class RMLUICORE_API TransformFuncRegister {
 public:
 	void Register(const String& name, DataTransformFunc transform_func);
-
 	bool Call(const String& name, Variant& inout_result, const VariantList& arguments) const;
 
 private:
@@ -80,72 +55,34 @@ private:
 };
 
 
-
-class RMLUICORE_API DataTypeRegister : NonCopyMoveable {
+class RMLUICORE_API DataTypeRegister final : NonCopyMoveable {
 public:
 	DataTypeRegister();
 	~DataTypeRegister();
 
-	template<typename T>
-	StructHandle<T> RegisterStruct()
-	{
-		static_assert(std::is_class<T>::value, "Type must be a struct or class type.");
-		FamilyId id = Family<T>::Id();
-
-		auto struct_variable = MakeUnique<StructDefinition>();
-		StructDefinition* struct_variable_raw = struct_variable.get();
-
-		bool inserted = type_register.emplace(id, std::move(struct_variable)).second;
-		if (!inserted)
-		{
-			RMLUI_LOG_TYPE_ERROR(T, "Type already declared");
-			return StructHandle<T>(nullptr, nullptr);
-		}
-		
-		return StructHandle<T>(this, struct_variable_raw);
-	}
-
-	template<typename Container>
-	bool RegisterArray()
+	inline bool RegisterDefinition(FamilyId id, UniquePtr<VariableDefinition> definition)
 	{
-		using value_type = typename Container::value_type;
-		VariableDefinition* value_variable = GetOrAddScalar<value_type>();
-		RMLUI_LOG_TYPE_ERROR_ASSERT(value_type, value_variable, "Underlying value type of array has not been registered.");
-		if (!value_variable)
-			return false;
-
-		FamilyId container_id = Family<Container>::Id();
-
-		auto array_variable = MakeUnique<ArrayDefinition<Container>>(value_variable);
-
-		bool inserted = type_register.emplace(container_id, std::move(array_variable)).second;
-		if (!inserted)
-		{
-			RMLUI_LOG_TYPE_ERROR(Container, "Array type already declared.");
-			return false;
-		}
-
-		return true;
+		const bool inserted = type_register.emplace(id, std::move(definition)).second;
+		return inserted;
 	}
 
 	template<typename T>
-	VariableDefinition* RegisterMemberFunc(MemberGetFunc<T> get_func, MemberSetFunc<T> set_func)
+	VariableDefinition* GetDefinition()
 	{
-		FamilyId id = Family<MemberGetFunc<T>>::Id();
-
-		auto result = type_register.emplace(id, nullptr);
-		auto& it = result.first;
-		bool inserted = result.second;
-
-		if (inserted)
-			it->second = MakeUnique<MemberFuncDefinition<T>>(get_func, set_func);
+		return GetDefinitionDetail<T>();
+	}
 
-		return it->second.get();
+	inline TransformFuncRegister* GetTransformFuncRegister() {
+		return &transform_register;
 	}
 
-	template<typename T, typename std::enable_if<is_valid_data_scalar<T>::value, int>::type = 0>
-	VariableDefinition* GetOrAddScalar()
+private:
+	// Get definition for scalar types that can be assigned to and from Rml::Variant.
+	// We automatically register these when needed, so users don't have to register trivial types manually.
+	template<typename T, typename std::enable_if<!PointerTraits<T>::is_pointer::value && is_builtin_data_scalar<T>::value, int>::type = 0>
+	VariableDefinition* GetDefinitionDetail()
 	{
+		static_assert(!std::is_const<T>::value, "Data binding variables cannot point to constant variables.");
 		FamilyId id = Family<T>::Id();
 
 		auto result = type_register.emplace(id, nullptr);
@@ -158,14 +95,10 @@ public:
 		return definition.get();
 	}
 
-	template<typename T, typename std::enable_if<!is_valid_data_scalar<T>::value, int>::type = 0>
-	VariableDefinition* GetOrAddScalar()
-	{
-		return Get<T>();
-	}
-
-	template<typename T>
-	VariableDefinition* Get()
+	// Get definition for types that are not a built-in scalar.
+	// These must already have been registered by the user.
+	template<typename T, typename std::enable_if<!PointerTraits<T>::is_pointer::value && !is_builtin_data_scalar<T>::value, int>::type = 0>
+	VariableDefinition* GetDefinitionDetail()
 	{
 		FamilyId id = Family<T>::Id();
 		auto it = type_register.find(id);
@@ -178,30 +111,42 @@ public:
 		return it->second.get();
 	}
 
-	TransformFuncRegister* GetTransformFuncRegister() {
-		return &transform_register;
+	// Get definition for pointer types, or create one as needed.
+	// This will create a wrapper definition that forwards the call to the definition of the underlying type.
+	template<typename T, typename std::enable_if<PointerTraits<T>::is_pointer::value, int>::type = 0>
+	VariableDefinition* GetDefinitionDetail()
+	{
+		static_assert(PointerTraits<T>::is_pointer::value, "Invalid pointer type provided.");
+
+		using UnderlyingType = typename PointerTraits<T>::element_type;
+		static_assert(!PointerTraits<UnderlyingType>::is_pointer::value, "Recursive pointer types (pointer to pointer) to data variables are disallowed.");
+		static_assert(!std::is_const<UnderlyingType>::value, "Pointer to a const data variable is not supported.");
+
+		// Get the underlying definition.
+		VariableDefinition* underlying_definition = GetDefinitionDetail<UnderlyingType>();
+		if (!underlying_definition)
+		{
+			RMLUI_LOG_TYPE_ERROR(T, "Underlying type of pointer not registered.");
+			return nullptr;
+		}
+
+		// Get or create the pointer wrapper definition.
+		FamilyId id = Family<T>::Id();
+
+		auto result = type_register.emplace(id, nullptr);
+		bool inserted = result.second;
+		UniquePtr<VariableDefinition>& definition = result.first->second;
+
+		if (inserted)
+			definition = MakeUnique<PointerDefinition<T>>(underlying_definition);
+
+		return definition.get();
 	}
 
-private:
 	UnorderedMap<FamilyId, UniquePtr<VariableDefinition>> type_register;
-
 	TransformFuncRegister transform_register;
-
 };
 
-template<typename Object>
-template<typename MemberType>
-inline StructHandle<Object>& StructHandle<Object>::RegisterMember(const String& name, MemberType Object::* member_ptr) {
-	VariableDefinition* member_type = type_register->GetOrAddScalar<MemberType>();
-	struct_definition->AddMember(name, MakeUnique<StructMemberObject<Object, MemberType>>(member_type, member_ptr));
-	return *this;
-}
-template<typename Object>
-inline StructHandle<Object>& StructHandle<Object>::RegisterMemberFunc(const String& name, MemberGetFunc<Object> get_func, MemberSetFunc<Object> set_func) {
-	VariableDefinition* definition = type_register->RegisterMemberFunc<Object>(get_func, set_func);
-	struct_definition->AddMember(name, MakeUnique<StructMemberFunc>(definition));
-	return *this;
-}
 
 } // namespace Rml
 #endif

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

@@ -31,6 +31,7 @@
 
 #include "Header.h"
 #include "Types.h"
+#include <type_traits>
 
 namespace Rml {
 
@@ -48,6 +49,12 @@ 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&);
+
+template<typename Object, typename ReturnType> using MemberGetterFunc = ReturnType(Object::*)();
+template<typename Object, typename AssignType> using MemberSetterFunc = void(Object::*)(AssignType);
+
 using DirtyVariables = SmallUnorderedSet<String>;
 
 struct DataAddressEntry {
@@ -58,5 +65,46 @@ struct DataAddressEntry {
 };
 using DataAddress = Vector<DataAddressEntry>;
 
+template<class T>
+struct PointerTraits {
+	using is_pointer = std::false_type;
+	using element_type = T;
+	static void* Dereference(void* ptr) {
+		return ptr;
+	}
+};
+template<class T>
+struct PointerTraits<T*> {
+	using is_pointer = std::true_type;
+	using element_type = T;
+	static void* Dereference(void* ptr) {
+		return static_cast<void*>(*static_cast<T**>(ptr));
+	}
+};
+template<class T>
+struct PointerTraits<UniquePtr<T>> {
+	using is_pointer = std::true_type;
+	using element_type = T;
+	static void* Dereference(void* ptr) {
+		return static_cast<void*>(static_cast<UniquePtr<T>*>(ptr)->get());
+	}
+};
+template<class T>
+struct PointerTraits<SharedPtr<T>> {
+	using is_pointer = std::true_type;
+	using element_type = T;
+	static void* Dereference(void* ptr) {
+		return static_cast<void*>(static_cast<SharedPtr<T>*>(ptr)->get());
+	}
+};
+
+struct VoidMemberFunc {};
+template<typename T> using IsVoidMemberFunc = std::is_same<T, VoidMemberFunc>;
+
+
+#define RMLUI_LOG_TYPE_ERROR(T, msg) RMLUI_ERRORMSG((String(msg) + String("\nT: ") + String(rmlui_type_name<T>())).c_str())
+#define RMLUI_LOG_TYPE_ERROR_ASSERT(T, val, msg) RMLUI_ASSERTMSG((val), (String(msg) + String("\nT: ") + String(rmlui_type_name<T>())).c_str())
+
+
 } // namespace Rml
 #endif

+ 125 - 73
Include/RmlUi/Core/DataVariable.h

@@ -38,7 +38,7 @@
 
 namespace Rml {
 
-enum class DataVariableType { Scalar, Array, Struct, Function, MemberFunction };
+enum class DataVariableType { Scalar, Array, Struct };
 
 
 /*
@@ -72,7 +72,7 @@ private:
 *   Generally, Scalar types can set and get values, while Array and Struct types can retrieve children based on data addresses.
 */
 
-class RMLUICORE_API VariableDefinition {
+class RMLUICORE_API VariableDefinition : public NonCopyMoveable {
 public:
 	virtual ~VariableDefinition() = default;
 	DataVariableType Type() const { return type; }
@@ -90,7 +90,7 @@ private:
 	DataVariableType type;
 };
 
-
+// Literal data variable constructor
 RMLUICORE_API DataVariable MakeLiteralIntVariable(int value);
 
 
@@ -101,7 +101,7 @@ public:
 
 	bool Get(void* ptr, Variant& variant) override
 	{
-		variant = *static_cast<T*>(ptr);
+		variant = *static_cast<const T*>(ptr);
 		return true;
 	}
 	bool Set(void* ptr, const Variant& variant) override
@@ -111,35 +111,62 @@ public:
 };
 
 
-class FuncDefinition final : public VariableDefinition {
+class RMLUICORE_API FuncDefinition final : public VariableDefinition {
 public:
+	FuncDefinition(DataGetFunc get, DataSetFunc set);
+
+	bool Get(void* ptr, Variant& variant) override;
+	bool Set(void* ptr, const Variant& variant) override;
+
+private:
+	DataGetFunc get;
+	DataSetFunc set;
+};
+
 
-	FuncDefinition(DataGetFunc get, DataSetFunc set) : VariableDefinition(DataVariableType::Function), get(std::move(get)), set(std::move(set)) {}
+template<typename T>
+class ScalarFuncDefinition final : public VariableDefinition {
+public:
+	ScalarFuncDefinition(DataTypeGetFunc<T> get, DataTypeSetFunc<T> set) : VariableDefinition(DataVariableType::Scalar), get(get), set(set) {}
 
-	bool Get(void* /*ptr*/, Variant& variant) override
+	bool Get(void* ptr, Variant& variant) override
 	{
 		if (!get)
 			return false;
-		get(variant);
+		get(*static_cast<const T*>(ptr), variant);
 		return true;
 	}
-	bool Set(void* /*ptr*/, const Variant& variant) override
+	bool Set(void* ptr, const Variant& variant) override
 	{
 		if (!set)
 			return false;
-		set(variant);
+		set(*static_cast<T*>(ptr), variant);
 		return true;
 	}
+
 private:
-	DataGetFunc get;
-	DataSetFunc set;
+	DataTypeGetFunc<T> get;
+	DataTypeSetFunc<T> set;
+};
+
+
+class RMLUICORE_API StructDefinition final : public VariableDefinition {
+public:
+	StructDefinition();
+
+	DataVariable Child(void* ptr, const DataAddressEntry& address) override;
+
+	void AddMember(const String& name, UniquePtr<VariableDefinition> member);
+
+private:
+	SmallUnorderedMap<String, UniquePtr<VariableDefinition>> members;
 };
 
 
 template<typename Container>
 class ArrayDefinition final : public VariableDefinition {
 public:
-	ArrayDefinition(VariableDefinition* underlying_definition) : VariableDefinition(DataVariableType::Array), underlying_definition(underlying_definition) {}
+	ArrayDefinition(VariableDefinition* underlying_definition) : VariableDefinition(DataVariableType::Array) , underlying_definition(underlying_definition) {}
 
 	int Size(void* ptr) override {
 		return int(static_cast<Container*>(ptr)->size());
@@ -173,25 +200,40 @@ private:
 };
 
 
-class StructMember {
+class RMLUICORE_API BasePointerDefinition : public VariableDefinition {
 public:
-	StructMember(VariableDefinition* definition) : definition(definition) {}
-	virtual ~StructMember() = default;
+	BasePointerDefinition(VariableDefinition* underlying_definition);
 
-	VariableDefinition* GetDefinition() const { return definition; }
+	bool Get(void* ptr, Variant& variant) override;
+	bool Set(void* ptr, const Variant& variant) override;
+	int Size(void* ptr) override;
+	DataVariable Child(void* ptr, const DataAddressEntry& address) override;
 
-	virtual void* GetPointer(void* base_ptr) = 0;
+protected:
+	virtual void* DereferencePointer(void* ptr) = 0;
 
 private:
-	VariableDefinition* definition;
+	VariableDefinition* underlying_definition;
 };
 
-template <typename Object, typename MemberType>
-class StructMemberObject final : public StructMember {
+template<typename T>
+class PointerDefinition final : public BasePointerDefinition {
 public:
-	StructMemberObject(VariableDefinition* definition, MemberType Object::* member_ptr) : StructMember(definition), member_ptr(member_ptr) {}
+	PointerDefinition(VariableDefinition* underlying_definition) : BasePointerDefinition(underlying_definition) {}
 
-	void* GetPointer(void* base_ptr) override {
+protected:
+	void* DereferencePointer(void* ptr) override {
+		return PointerTraits<T>::Dereference(ptr);
+	}
+};
+
+template<typename Object, typename MemberType>
+class MemberObjectDefinition final : public BasePointerDefinition {
+public:
+	MemberObjectDefinition(VariableDefinition* underlying_definition, MemberType Object::* member_ptr) : BasePointerDefinition(underlying_definition), member_ptr(member_ptr) {}
+
+protected:
+	void* DereferencePointer(void* base_ptr) override {
 		return &(static_cast<Object*>(base_ptr)->*member_ptr);
 	}
 
@@ -199,78 +241,88 @@ private:
 	MemberType Object::* member_ptr;
 };
 
-class StructMemberFunc final : public StructMember {
+
+template<typename Object, typename MemberType, typename BasicReturnType>
+class MemberGetFuncDefinition final : public BasePointerDefinition {
 public:
-	StructMemberFunc(VariableDefinition* definition) : StructMember(definition) {}
-	void* GetPointer(void* base_ptr) override {
-		return base_ptr;
+
+	MemberGetFuncDefinition(VariableDefinition* underlying_definition, MemberType Object::* member_get_func_ptr)
+		: BasePointerDefinition(underlying_definition), member_get_func_ptr(member_get_func_ptr)
+	{}
+
+protected:
+	void* DereferencePointer(void* base_ptr) override {
+		return static_cast<void*>(Extract((static_cast<Object*>(base_ptr)->*member_get_func_ptr)()));
 	}
+
+private:
+	BasicReturnType* Extract(BasicReturnType* value) {
+		return value;
+	}
+	BasicReturnType* Extract(BasicReturnType& value) {
+		return &value;
+	}
+
+	MemberType Object::* member_get_func_ptr;
 };
 
 
-class StructDefinition final : public VariableDefinition {
+template<typename Object, typename MemberGetType, typename MemberSetType, typename UnderlyingType>
+class MemberScalarGetSetFuncDefinition final : public VariableDefinition {
 public:
-	StructDefinition() : VariableDefinition(DataVariableType::Struct)
+	MemberScalarGetSetFuncDefinition(VariableDefinition* underlying_definition, MemberGetType Object::* member_get_func_ptr, MemberSetType Object::* member_set_func_ptr)
+		: VariableDefinition(underlying_definition->Type()), underlying_definition(underlying_definition), member_get_func_ptr(member_get_func_ptr), member_set_func_ptr(member_set_func_ptr)
 	{}
 
-	DataVariable Child(void* ptr, const DataAddressEntry& address) override
-	{
-		const String& name = address.name;
-		if (name.empty())
-		{
-			Log::Message(Log::LT_WARNING, "Expected a struct member name but none given.");
-			return DataVariable();
-		}
-
-		auto it = members.find(name);
-		if (it == members.end())
-		{
-			Log::Message(Log::LT_WARNING, "Member %s not found in data struct.", name.c_str());
-			return DataVariable();
-		}
-
-		void* next_ptr = it->second->GetPointer(ptr);
-		VariableDefinition* next_definition = it->second->GetDefinition();
-
-		return DataVariable(next_definition, next_ptr);
+	bool Get(void* ptr, Variant& variant) override {
+		return GetDetail(ptr, variant);
 	}
-
-	void AddMember(const String& name, UniquePtr<StructMember> member)
-	{
-		RMLUI_ASSERT(member);
-		bool inserted = members.emplace(name, std::move(member)).second;
-		RMLUI_ASSERTMSG(inserted, "Member name already exists.");
-		(void)inserted;
+	bool Set(void* ptr, const Variant& variant) override {
+		return SetDetail(ptr, variant);
 	}
 
 private:
-	SmallUnorderedMap<String, UniquePtr<StructMember>> members;
-};
+	template<typename T = MemberGetType, typename std::enable_if<IsVoidMemberFunc<T>::value, int>::type = 0>
+	bool GetDetail(void* /*ptr*/, Variant& /*variant*/)
+	{
+		return false;
+	}
 
+	template<typename T = MemberGetType, typename std::enable_if<!IsVoidMemberFunc<T>::value, int>::type = 0>
+	bool GetDetail(void* ptr, Variant& variant)
+	{
+		RMLUI_ASSERT(member_get_func_ptr);
 
-template<typename T>
-class MemberFuncDefinition final : public VariableDefinition {
-public:
-	MemberFuncDefinition(MemberGetFunc<T> get, MemberSetFunc<T> set) : VariableDefinition(DataVariableType::MemberFunction), get(get), set(set) {}
+		auto&& value = (static_cast<Object*>(ptr)->*member_get_func_ptr)();
+		bool result = underlying_definition->Get(static_cast<void*>(&value), variant);
+		return result;
+	}
 
-	bool Get(void* ptr, Variant& variant) override
+	template<typename T = MemberSetType, typename std::enable_if<IsVoidMemberFunc<T>::value, int>::type = 0>
+	bool SetDetail(void* /*ptr*/, const Variant& /*variant*/)
 	{
-		if (!get)
-			return false;
-		(static_cast<T*>(ptr)->*get)(variant);
-		return true;
+		return false;
 	}
-	bool Set(void* ptr, const Variant& variant) override
+
+	template<typename T = MemberSetType, typename std::enable_if<!IsVoidMemberFunc<T>::value, int>::type = 0>
+	bool SetDetail(void* ptr, const Variant& variant)
 	{
-		if (!set)
+		RMLUI_ASSERT(member_set_func_ptr);
+
+		UnderlyingType result;
+		if (!underlying_definition->Set(static_cast<void*>(&result), variant))
 			return false;
-		(static_cast<T*>(ptr)->*set)(variant);
+
+		(static_cast<Object*>(ptr)->*member_set_func_ptr)(result);
+
 		return true;
 	}
-private:
-	MemberGetFunc<T> get;
-	MemberSetFunc<T> set;
+
+	VariableDefinition* underlying_definition;
+	MemberGetType Object::* member_get_func_ptr;
+	MemberSetType Object::* member_set_func_ptr;
 };
 
+
 } // namespace Rml
 #endif

+ 2 - 6
Include/RmlUi/Core/Traits.h

@@ -80,11 +80,6 @@ protected:
 		static int id = 0;
 		return id++;
 	}
-	template<typename T>
-	static FamilyId GetId() {
-		static int id = GetNewId();
-		return static_cast<FamilyId>(id);
-	}
 };
 
 template<typename T>
@@ -93,7 +88,8 @@ public:
 	// Get a unique ID for a given type.
 	// Note: IDs will be unique across DLL-boundaries even for the same type.
 	static FamilyId Id() {
-		return GetId< typename std::remove_cv< typename std::remove_reference< T >::type >::type >();
+		static int id = GetNewId();
+		return static_cast<FamilyId>(id);
 	}
 };
 

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

@@ -206,7 +206,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>

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

@@ -164,17 +164,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 +190,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 +213,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.

+ 1 - 1
Samples/invaders/src/HighScores.cpp

@@ -48,7 +48,7 @@ HighScores::HighScores(Rml::Context* context)
 	{
 		score_handle.RegisterMember("name_required", &Score::name_required);
 		score_handle.RegisterMember("name", &Score::name);
-		score_handle.RegisterMemberFunc("colour", &Score::GetColour);
+		score_handle.RegisterMember("colour", &Score::GetColour);
 		score_handle.RegisterMember("wave", &Score::wave);
 		score_handle.RegisterMember("score", &Score::score);
 	}

+ 2 - 2
Samples/invaders/src/HighScores.h

@@ -66,8 +66,8 @@ private:
 		int score;
 		int wave;
 
-		void GetColour(Rml::Variant& variant) {
-			variant = "rgba(" + Rml::ToString(colour) + ')';
+		Rml::String GetColour() {
+			return "rgba(" + Rml::ToString(colour) + ')';
 		}
 	};
 	using ScoreList = Rml::Vector< Score >;

+ 1 - 1
Samples/luainvaders/src/HighScores.cpp

@@ -48,7 +48,7 @@ HighScores::HighScores(Rml::Context* context)
 	{
 		score_handle.RegisterMember("name_required", &Score::name_required);
 		score_handle.RegisterMember("name", &Score::name);
-		score_handle.RegisterMemberFunc("colour", &Score::GetColour);
+		score_handle.RegisterMember("colour", &Score::GetColour);
 		score_handle.RegisterMember("wave", &Score::wave);
 		score_handle.RegisterMember("score", &Score::score);
 	}

+ 2 - 2
Samples/luainvaders/src/HighScores.h

@@ -66,8 +66,8 @@ private:
 		int score;
 		int wave;
 
-		void GetColour(Rml::Variant& variant) {
-			variant = "rgba(" + Rml::ToString(colour) + ')';
+		Rml::String GetColour() {
+			return "rgba(" + Rml::ToString(colour) + ')';
 		}
 	};
 	using ScoreList = Rml::Vector< Score >;

+ 2 - 1
Source/Core/DataModel.h

@@ -34,13 +34,14 @@
 #include "../../Include/RmlUi/Core/Traits.h"
 #include "../../Include/RmlUi/Core/DataModelHandle.h"
 #include "../../Include/RmlUi/Core/DataTypes.h"
-#include "../../Include/RmlUi/Core/DataVariable.h"
 
 namespace Rml {
 
 class DataViews;
 class DataControllers;
+class DataVariable;
 class Element;
+class FuncDefinition;
 
 
 class DataModel : NonCopyMoveable {

+ 2 - 0
Source/Core/DataTypeRegister.cpp

@@ -27,6 +27,8 @@
  */
 
 #include "../../Include/RmlUi/Core/DataTypeRegister.h"
+#include "../../Include/RmlUi/Core/StringUtilities.h"
+#include "../../Include/RmlUi/Core/Variant.h"
 
 namespace Rml {
 

+ 74 - 0
Source/Core/DataVariable.cpp

@@ -85,4 +85,78 @@ DataVariable MakeLiteralIntVariable(int value)
     return DataVariable(&literal_int_definition, reinterpret_cast<void*>(static_cast<intptr_t>(value)));
 }
 
+StructDefinition::StructDefinition() : VariableDefinition(DataVariableType::Struct)
+{}
+
+DataVariable StructDefinition::Child(void* ptr, const DataAddressEntry& address)
+{
+    const String& name = address.name;
+    if (name.empty())
+    {
+        Log::Message(Log::LT_WARNING, "Expected a struct member name but none given.");
+        return DataVariable();
+    }
+
+    auto it = members.find(name);
+    if (it == members.end())
+    {
+        Log::Message(Log::LT_WARNING, "Member %s not found in data struct.", name.c_str());
+        return DataVariable();
+    }
+
+    VariableDefinition* next_definition = it->second.get();
+
+    return DataVariable(next_definition, ptr);
+}
+
+void StructDefinition::AddMember(const String& name, UniquePtr<VariableDefinition> member)
+{
+    RMLUI_ASSERT(member);
+    bool inserted = members.emplace(name, std::move(member)).second;
+    RMLUI_ASSERTMSG(inserted, "Member name already exists.");
+    (void)inserted;
+}
+
+FuncDefinition::FuncDefinition(DataGetFunc get, DataSetFunc set)
+    : VariableDefinition(DataVariableType::Scalar), get(std::move(get)), set(std::move(set)) {}
+
+bool FuncDefinition::Get(void* /*ptr*/, Variant& variant)
+{
+    if (!get)
+        return false;
+    get(variant);
+    return true;
+}
+
+bool FuncDefinition::Set(void* /*ptr*/, const Variant& variant)
+{
+    if (!set)
+        return false;
+    set(variant);
+    return true;
+}
+
+BasePointerDefinition::BasePointerDefinition(VariableDefinition* underlying_definition)
+    : VariableDefinition(underlying_definition->Type()), underlying_definition(underlying_definition) {}
+
+bool BasePointerDefinition::Get(void* ptr, Variant& variant)
+{
+    return underlying_definition->Get(DereferencePointer(ptr), variant);
+}
+
+bool BasePointerDefinition::Set(void* ptr, const Variant& variant)
+{
+    return underlying_definition->Set(DereferencePointer(ptr), variant);
+}
+
+int BasePointerDefinition::Size(void* ptr)
+{
+    return underlying_definition->Size(DereferencePointer(ptr));
+}
+
+DataVariable BasePointerDefinition::Child(void* ptr, const DataAddressEntry& address)
+{
+    return underlying_definition->Child(DereferencePointer(ptr), address);
+}
+
 } // namespace Rml

+ 1 - 0
Source/Core/DataViewDefault.cpp

@@ -30,6 +30,7 @@
 #include "DataExpression.h"
 #include "DataModel.h"
 #include "../../Include/RmlUi/Core/Core.h"
+#include "../../Include/RmlUi/Core/DataVariable.h"
 #include "../../Include/RmlUi/Core/Element.h"
 #include "../../Include/RmlUi/Core/ElementText.h"
 #include "../../Include/RmlUi/Core/Factory.h"

+ 395 - 0
Tests/Source/UnitTests/DataBinding.cpp

@@ -0,0 +1,395 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "../Common/TestsShell.h"
+#include <RmlUi/Core/Context.h>
+#include <RmlUi/Core/DataModelHandle.h>
+#include <RmlUi/Core/Element.h>
+#include <RmlUi/Core/ElementDocument.h>
+#include <doctest.h>
+#include <map>
+
+using namespace Rml;
+
+namespace {
+
+
+static const String document_rml = R"(
+<rml>
+<head>
+	<title>Test</title>
+	<link type="text/rcss" href="/assets/rml.rcss"/>
+	<link type="text/template" href="/assets/window.rml"/>
+	<style>
+		body.window
+		{
+			left: 50px;
+			right: 50px;
+			top: 30px;
+			bottom: 30px;
+			max-width: -1px;
+			max-height: -1px;
+		}
+		div#content
+		{
+			text-align: left;
+			padding: 50px;
+			box-sizing: border-box;
+		}
+	</style>
+</head>
+
+<body template="window">
+<div data-model="basics">
+
+<input type="text" data-value="i0"/>
+
+<h1>Globals</h1>
+<p>{{ i0 }}</p>
+<p>{{ i1 }}</p>
+<p>{{ i2 }}</p>
+<p>{{ i3 }}</p>
+
+<p>{{ s0 }}</p>
+<p>{{ s1 }}</p>
+<p>{{ s2.val }}</p>
+<p>{{ s3.val }}</p>
+<p>{{ s4.val }}</p>
+<p>{{ s5.val }}</p>
+
+<h1>Basic</h1>
+<p>{{ basic.a }}</p>
+<p>{{ basic.b }}</p>
+<p>{{ basic.c }}</p>
+<p>{{ basic.d }}</p>
+<p>{{ basic.e }}</p>
+<p>{{ basic.f }}</p>
+
+<h1>Wrapped</h1>
+<p>{{ wrapped.a.val }}</p>
+<p>{{ wrapped.b.val }}</p>
+<p>{{ wrapped.c.val }}</p>
+<p>{{ wrapped.d.val }}</p>
+<p>{{ wrapped.e.val }}</p>
+
+<h1>Pointed</h1>
+<p>{{ pointed.a.val }}</p>
+<p>{{ pointed.b.val }}</p>
+<p>{{ pointed.c.val }}</p>
+
+<h1>Arrays</h1>
+<p><span data-for="arrays.a">{{ it }} </span></p>
+<p><span data-for="arrays.b">{{ it }} </span></p>
+<p><span data-for="arrays.c">{{ it.val }} </span></p>
+<p><span data-for="arrays.d">{{ it.val }} </span></p>
+<p><span data-for="arrays.e">{{ it.val }} </span></p>
+
+</div>
+</body>
+</rml>
+)";
+
+
+struct StringWrap
+{
+	StringWrap(String val = "wrap_default") : val(val) {}
+	String val;
+};
+
+struct Globals
+{
+	int i0 = 0;
+	int* i1 = new int(1);
+	UniquePtr<int> i2 = MakeUnique<int>(2);
+	SharedPtr<int> i3 = MakeShared<int>(3);
+
+	String s0 = "s0";
+	String* s1 = new String("s1");
+	StringWrap s2 = StringWrap("s2");
+	StringWrap* s3 = new StringWrap("s3");
+	UniquePtr<StringWrap> s4 = MakeUnique<StringWrap>("s4");
+	SharedPtr<StringWrap> s5 = MakeShared<StringWrap>("s5");
+
+	// Invalid
+	const int x0 = 100;                                            // Invalid: const variable
+	const int* x1 = new int(101);                                  // Invalid: const pointer
+	UniquePtr<const int> x2 = MakeUnique<int>(102);                // Invalid: const pointer
+	const StringWrap* x3 = new StringWrap("x2");                   // Invalid: const pointer
+	UniquePtr<const StringWrap> x4 = MakeUnique<StringWrap>("x3"); // Invalid: const pointer
+} globals;
+
+struct Basic
+{
+	int a = 1;
+	int* b = new int(2);
+
+	int GetC() {
+		static int v = 5;
+		return v;
+	}
+	int& GetD() {
+		static int v = 5;
+		return v;
+	}
+	int* GetE() {
+		static int v = 6;
+		return &v;
+	}
+	UniquePtr<int> GetF() {
+		return MakeUnique<int>(7);
+	}
+
+	// Invalid: const member
+	const int x0 = 2;
+	// Invalid: const pointer
+	const int* x1 = new int(3);
+	// Invalid: const qualified member function
+	int GetX2() const {
+		return 4;
+	}
+	// Invalid: const reference return
+	const int& GetX3() {
+		static int g = 7;
+		return g;
+	}
+	// Invalid: const pointer return
+	const int* GetX4() {
+		static int h = 8;
+		return &h;
+	}
+	// Invalid: Illegal signature
+	int GetX5(int) {
+		return 9;
+	}
+};
+
+struct Wrapped
+{
+	StringWrap a = { "a" };
+	StringWrap* b = new StringWrap("b");
+	UniquePtr<StringWrap> c = MakeUnique<StringWrap>("c");
+
+	StringWrap& GetD() {
+		static StringWrap v = { "e" };
+		return v;
+	}
+	StringWrap* GetE() {
+		static StringWrap v = { "f" };
+		return &v;
+	}
+	
+	// Invalid: const pointer
+	const StringWrap* x0 = new StringWrap("x0");
+	// Invalid (run-time): Returning non-scalar variable by value.
+	StringWrap GetX1() {
+		return { "x1" };
+	}
+	// Invalid (run-time): Returning non-scalar variable by value.
+	UniquePtr<StringWrap> GetX2() {
+		return MakeUnique<StringWrap>("x2");
+	}
+};
+
+using StringWrapPtr = UniquePtr<StringWrap>;
+
+struct Pointed
+{
+	StringWrapPtr a = MakeUnique<StringWrap>("a");
+
+	StringWrapPtr& GetB() {
+		static StringWrapPtr v = MakeUnique<StringWrap>("b");
+		return v;
+	}
+	StringWrapPtr* GetC() {
+		static StringWrapPtr v = MakeUnique<StringWrap>("c");
+		return &v;
+	}
+	
+	// Invalid: We disallow recursive pointer types (pointer to pointer)
+	StringWrapPtr* x0 = new StringWrapPtr(new StringWrap("x0"));
+
+	// Invalid (run-time error): Only scalar data members can be returned by value
+	StringWrapPtr GetX1() {
+		return MakeUnique<StringWrap>("x1");
+	}
+
+};
+
+struct Arrays
+{
+	Vector<int> a = { 10, 11, 12 };
+	Vector<int*> b = { new int(20), new int(21), new int(22) };
+	Vector<StringWrap> c = { StringWrap("c1"), StringWrap("c2"), StringWrap("c3") };
+	Vector<StringWrap*> d = { new StringWrap("d1"), new StringWrap("d2"), new StringWrap("d3") };
+	Vector<StringWrapPtr> e;
+	
+	// Invalid: const pointer
+	Vector<const int*> x0 = { new int(30), new int(31), new int(32) };
+	// Invalid: const pointer
+	Vector<UniquePtr<const StringWrap>> x1;
+	
+	Arrays() {
+		e.emplace_back(MakeUnique<StringWrap>("e1"));
+		e.emplace_back(MakeUnique<StringWrap>("e2"));
+		e.emplace_back(MakeUnique<StringWrap>("e3"));
+		x1.emplace_back(MakeUnique<StringWrap>("x1_1"));
+		x1.emplace_back(MakeUnique<StringWrap>("x1_2"));
+		x1.emplace_back(MakeUnique<StringWrap>("x1_3"));
+	}
+};
+
+DataModelHandle model_handle;
+
+
+
+bool InitializeDataBindings(Context* context)
+{
+	Rml::DataModelConstructor constructor = context->CreateDataModel("basics");
+	if (!constructor)
+		return false;
+
+	if (auto handle = constructor.RegisterStruct<StringWrap>())
+	{
+		handle.RegisterMember("val", &StringWrap::val);
+	}
+
+	{
+		// Globals
+		constructor.Bind("i0", &globals.i0);
+		constructor.Bind("i1", &globals.i1);
+		constructor.Bind("i2", &globals.i2);
+		constructor.Bind("i3", &globals.i3);
+
+		constructor.Bind("s0", &globals.s0);
+		constructor.Bind("s1", &globals.s1);
+		constructor.Bind("s2", &globals.s2);
+		constructor.Bind("s3", &globals.s3);
+		constructor.Bind("s4", &globals.s4);
+		constructor.Bind("s5", &globals.s5);
+
+		// Invalid: Each of the following should give a compile-time failure.
+		//constructor.Bind("x0", &globals.x0);
+		//constructor.Bind("x1", &globals.x1);
+		//constructor.Bind("x2", &globals.x2);
+		//constructor.Bind("x3", &globals.x3);
+		//constructor.Bind("x4", &globals.x4);
+	}
+
+	if (auto handle = constructor.RegisterStruct<Basic>())
+	{
+		handle.RegisterMember("a", &Basic::a);
+		handle.RegisterMember("b", &Basic::b);
+		handle.RegisterMember("c", &Basic::GetC);
+		handle.RegisterMember("d", &Basic::GetD);
+		handle.RegisterMember("e", &Basic::GetE);
+		handle.RegisterMember("f", &Basic::GetF);
+
+		//handle.RegisterMember("x0", &Basic::x0);
+		//handle.RegisterMember("x1", &Basic::x1);
+		//handle.RegisterMember("x2", &Basic::GetX2);
+		//handle.RegisterMember("x3", &Basic::GetX3);
+		//handle.RegisterMember("x4", &Basic::GetX4);
+		//handle.RegisterMember("x5", &Basic::GetX5);
+	}
+	constructor.Bind("basic", new Basic);
+	
+	if (auto handle = constructor.RegisterStruct<Wrapped>())
+	{
+		handle.RegisterMember("a", &Wrapped::a);
+		handle.RegisterMember("b", &Wrapped::b);
+		handle.RegisterMember("c", &Wrapped::c);
+		handle.RegisterMember("d", &Wrapped::GetD);
+		handle.RegisterMember("e", &Wrapped::GetE);
+
+		//handle.RegisterMember("x0", &Wrapped::x0);
+		//handle.RegisterMember("x1", &Wrapped::GetX1);
+		//handle.RegisterMember("x2", &Wrapped::GetX2);
+	}
+	constructor.Bind("wrapped", new Wrapped);
+	
+	if (auto handle = constructor.RegisterStruct<Pointed>())
+	{
+		handle.RegisterMember("a", &Pointed::a);
+		handle.RegisterMember("b", &Pointed::GetB);
+		handle.RegisterMember("c", &Pointed::GetC);
+
+		//handle.RegisterMember("x0", &Pointed::x0);
+		//handle.RegisterMember("x1", &Pointed::GetX1);
+	}
+	constructor.Bind("pointed", new Pointed);
+
+	constructor.RegisterArray<decltype(Arrays::a)>();
+	constructor.RegisterArray<decltype(Arrays::b)>();
+	constructor.RegisterArray<decltype(Arrays::c)>();
+	constructor.RegisterArray<decltype(Arrays::d)>();
+	constructor.RegisterArray<decltype(Arrays::e)>();
+
+	//constructor.RegisterArray<decltype(Arrays::x0)>();
+	//constructor.RegisterArray<decltype(Arrays::x1)>();
+
+	if (auto handle = constructor.RegisterStruct<Arrays>())
+	{
+		handle.RegisterMember("a", &Arrays::a);
+		handle.RegisterMember("b", &Arrays::b);
+		handle.RegisterMember("c", &Arrays::c);
+		handle.RegisterMember("d", &Arrays::d);
+		handle.RegisterMember("e", &Arrays::e);
+
+		//handle.RegisterMember("x0", &Arrays::x0);
+		//handle.RegisterMember("x1", &Arrays::x1);
+	}
+	constructor.Bind("arrays", new Arrays);
+	
+	model_handle = constructor.GetModelHandle();
+
+	return true;
+}
+
+} // Anonymous namespace
+
+
+TEST_CASE("databinding")
+{
+	Context* context = TestsShell::GetContext();
+	REQUIRE(context);
+
+	REQUIRE(InitializeDataBindings(context));
+
+	ElementDocument* document = context->LoadDocumentFromMemory(document_rml);
+	REQUIRE(document);
+	document->Show();
+
+	context->Update();
+	context->Render();
+
+	TestsShell::RenderLoop();
+
+	document->Close();
+
+	TestsShell::ShutdownShell();
+}