Browse Source

Implement data types

Michael Ragazzon 6 years ago
parent
commit
8e8bbee5a5

+ 88 - 12
Include/RmlUi/Core/DataBinding.h

@@ -192,44 +192,121 @@ private:
 
 
 
-class DataModel {
+// -- Data members using inheritance --
+
+class DataMemberGetSet {
+public:
+	virtual ~DataMemberGetSet() = default;
+	virtual bool Get(const void* object, Rml::Core::Variant& out_value) = 0;
+	virtual bool Set(void* object, const Rml::Core::Variant& in_value) = 0;
+};
+
+template <typename Object, typename MemberType>
+class DataMemberGetSetDefault : public DataMemberGetSet {
 public:
-	using Type = Variant::Type;
+	DataMemberGetSetDefault(MemberType Object::* member_ptr) : member_ptr(member_ptr) {}
+
+	bool Get(const void* object, Rml::Core::Variant& out_value) override {
+		out_value = static_cast<const Object*>(object)->*member_ptr;
+		return true;
+	}
+	bool Set(void* object, const Rml::Core::Variant& in_value) override {
+		MemberType& target = static_cast<Object*>(object)->*member_ptr;
+		return in_value.GetInto<MemberType>(target);
+	}
+
+private:
+	MemberType Object::* member_ptr;
+};
+
 
+
+
+
+
+enum class ValueType { None, String, Int, Bool, Color, Array, Type };
+
+
+class DataModel {
+public:
 	struct Binding {
-		Type type = Type::NONE;
+		ValueType type = ValueType::None;
 		void* ptr = nullptr;
 		bool writable = false;
+		String data_type_name;
 	};
 
-	bool GetValue(const String& name, String& out_value) const;
-	bool GetValue(const String& name, bool& out_value) const;
-	bool SetValue(const String& name, const String& value) const;
+	bool GetValue(const String& name, Variant& out_value) const;
+	bool GetValue(const String& name, String& out_string) const {
+		Variant variant;
+		return GetValue(name, variant) && variant.GetInto(out_string);
+	}
+	bool SetValue(const String& name, const Variant& value) const;
 	bool IsWritable(const String& name) const;
 
-	using Bindings = Rml::Core::UnorderedMap<Rml::Core::String, Binding>;
-
+	using Bindings = UnorderedMap<String, Binding>;
 	Bindings bindings;
 
 	DataControllers controllers;
 	DataViews views;
+
+	using DataTypeMembers = SmallUnorderedMap<String, UniquePtr<DataMemberGetSet>>;
+	using DataTypes = UnorderedMap<String, DataTypeMembers>;
+
+	DataTypes data_types;
 };
 
 
-class DataModelHandle {
+
+
+class DataTypeHandle {
 public:
-	using Type = Variant::Type;
+	DataTypeHandle(DataModel::DataTypeMembers* members) : members(members) {}
+
+	template <typename Object, typename MemberType>
+	DataTypeHandle& BindMember(String name, MemberType Object::* member_ptr)
+	{
+		RMLUI_ASSERT(members);
+		members->emplace(name, std::make_unique<DataMemberGetSetDefault<Object, MemberType>>(member_ptr));
+		return *this;
+	}
+
+private:
+	DataModel::DataTypeMembers* members;
+};
+
 
+class DataModelHandle {
+public:
 	DataModelHandle() : model(nullptr) {}
 	DataModelHandle(DataModel* model) : model(model) {}
 
-	DataModelHandle& BindData(String name, Type type, void* ptr, bool writable = false)
+	DataModelHandle& BindValue(String name, ValueType type, void* ptr, bool writable = false)
 	{
 		RMLUI_ASSERT(model);
 		model->bindings.emplace(name, DataModel::Binding{ type, ptr, writable });
 		return *this;
 	}
 
+	DataModelHandle& BindDataTypeValue(String name, String type_name, void* ptr, bool writable = false)
+	{
+		// Todo: We can make this type safe, removing the need for type_name.
+		//   Make this a templated function, create another templated "family" class which assigns
+		//   a unique id for each new type encountered, look up the type name there. Or use the ID as
+		//   the look-up key.
+		RMLUI_ASSERT(model);
+		model->bindings.emplace(name, DataModel::Binding{ ValueType::Type, ptr, writable, type_name });
+		return *this;
+	}
+
+	DataTypeHandle RegisterType(String name)
+	{
+		RMLUI_ASSERT(model);
+		auto result = model->data_types.emplace(name, DataModel::DataTypeMembers() );
+		return DataTypeHandle(&result.first->second);
+	}
+
+
 	void UpdateControllers() {
 		RMLUI_ASSERT(model);
 		model->controllers.Update(*model);
@@ -246,7 +323,6 @@ private:
 	DataModel* model;
 };
 
-
 }
 }
 

+ 4 - 0
Samples/basic/databinding/data/databinding.rml

@@ -167,6 +167,10 @@ form h2
 	<p>Data binding demo. We rate this a good old {{rating}}!</p>
 	<input type="range" name="rating" min="0" max="100" step="1" value="50" data-attr-value="rating"/>
 	<div data-if="good_rating">Thanks for the awesome rating!</div>
+	<div data-for="invader : invaders">
+		<h1>{{invader.name}}</h1>
+		<img data-attr-sprite="invader.sprite" data-style-image-color="invader.color"/>
+	</div>
 </panel>
 <tab>Decorators</tab>
 <panel id="decorators">

+ 91 - 4
Samples/basic/databinding/src/main.cpp

@@ -103,25 +103,112 @@ private:
 
 
 
+
+
+struct Invader {
+	Rml::Core::String name;
+	Rml::Core::String sprite;
+	Rml::Core::Colourf color;
+};
+
+
+/*
+
+// -- Data members using std::function --
+
+using GetterFnc = std::function<void(const void*, Rml::Core::Variant&)>;
+using SetterFnc = std::function<void(void*, const Rml::Core::Variant&)>;
+
+struct DataBinding {
+	void* ptr;
+	GetterFnc getter;
+	SetterFnc setter;
+};
+
+template <typename Object, typename MemberType>
+SetterFnc CreateMemberSetter(MemberType Object::* member_ptr)
+{
+	return [member_ptr](void* object, const Rml::Core::Variant& input) {
+		MemberType& target = static_cast<Object*>(object)->*member_ptr;
+		input.GetInto<MemberType>(target);
+	};
+}
+
+void TestDataType()
+{
+	std::vector<SetterFnc> setter;
+	auto fnc = CreateMemberSetter(&Invader::name);
+
+	setter.push_back(fnc);
+
+	Invader invader;
+	Rml::Core::String new_name = "Hello there!";
+	fnc(&invader, Rml::Core::Variant(new_name));
+
+	return;
+}
+*/
+
+
+template <typename Object, typename MemberType>
+auto MakeMemberDefault(MemberType Object::* member_ptr) {
+	return std::make_unique<Rml::Core::DataMemberGetSetDefault<Object, MemberType>>(member_ptr);
+}
+
+void TestDataType()
+{
+	Rml::Core::DataModel::DataTypeMembers members;
+
+	auto fnc = MakeMemberDefault(&Invader::name);
+	members.emplace("name", std::move(fnc));
+
+	Invader invader{ "Delightful invader" };
+	Rml::Core::Variant old_value;
+	members["name"]->Get(&invader, old_value);
+	auto old_name = old_value.Get<Rml::Core::String>();
+	RMLUI_ASSERT(old_name == invader.name);
+
+	const Rml::Core::String new_name = "Evil invader";
+	members["name"]->Set(&invader, Rml::Core::Variant(new_name));
+	RMLUI_ASSERT(new_name == invader.name);
+
+	return;
+}
+
+
+
 struct MyData {
 	Rml::Core::String hello_world = "Hello World!";
 	int rating = 99;
 	int good_rating = 1;
+
+	Invader invader{ "Delightful invader", "icon-invader", Rml::Core::Colourf{} };
+
+	std::vector<Invader> invaders;
 } my_data;
 
 Rml::Core::DataModelHandle my_model;
 
 bool SetupDataBinding(Rml::Core::Context* context)
 {
-	using Type = Rml::Core::Variant::Type;
+	using Rml::Core::ValueType;
 
 	my_model = context->CreateDataModel("my_model");
 	if (!my_model)
 		return false;
 
-	my_model.BindData("hello_world", Type::STRING, &my_data.hello_world);
-	my_model.BindData("rating", Type::INT, &my_data.rating, true);
-	my_model.BindData("good_rating", Type::INT, &my_data.good_rating);
+	my_model.BindValue("hello_world", ValueType::String, &my_data.hello_world);
+	my_model.BindValue("rating", ValueType::Int, &my_data.rating, true);
+	my_model.BindValue("good_rating", ValueType::Int, &my_data.good_rating);
+
+	TestDataType();
+
+	auto invader_type = my_model.RegisterType("Invader");
+	invader_type.BindMember("name", &Invader::name);
+	invader_type.BindMember("sprite", &Invader::sprite);
+	invader_type.BindMember("color", &Invader::color);
+
+	my_model.BindDataTypeValue("invader", "Invader", &my_data.invader);
 
 	return true;
 }

+ 43 - 41
Source/Core/DataBinding.cpp

@@ -155,9 +155,9 @@ bool DataViewAttribute::Update(const DataModel& model)
 	String value;
 	if (model.GetValue(value_name, value))
 	{
-		Variant* variant = element->GetAttribute(attribute_name);
+		Variant* attribute = element->GetAttribute(attribute_name);
 
-		if (!variant || (variant && variant->Get<String>() != value))
+		if (!attribute || (attribute && attribute->Get<String>() != value))
 		{
 			element->SetAttribute(attribute_name, value);
 			result = true;
@@ -177,9 +177,12 @@ DataViewIf::DataViewIf(const DataModel& model, Element* element, const String& b
 bool DataViewIf::Update(const DataModel& model)
 {
 	bool result = false;
-	bool value = false;
-	if (model.GetValue(binding_name, value))
+	Variant variant;
+	int int_value = 0;
+
+	if (model.GetValue(binding_name, variant) && variant.GetInto(int_value))
 	{
+		bool value = (int_value != 0);
 		bool is_visible = (element->GetLocalStyleProperties().count(PropertyId::Display) == 0);
 		if(is_visible != value)
 		{
@@ -195,8 +198,6 @@ bool DataViewIf::Update(const DataModel& model)
 
 DataControllerAttribute::DataControllerAttribute(const DataModel& model, const String& in_attribute_name, const String& in_value_name) : attribute_name(in_attribute_name), value_name(in_value_name)
 {
-	String value;
-	bool result = model.GetValue(value_name, value);
 	if (!model.IsWritable(value_name))
 	{
 		attribute_name.clear();
@@ -209,54 +210,55 @@ bool DataControllerAttribute::Update(Element* element, const DataModel& model)
 	bool result = false;
 	if (dirty)
 	{
-		result = model.SetValue(value_name, element->GetAttribute<String>(attribute_name, ""));
+		if(Variant* value = element->GetAttribute(attribute_name))
+			result = model.SetValue(value_name, *value);
 		dirty = false;
 	}
 	return result;
 }
 
 
-bool DataModel::GetValue(const String& name, String& out_value) const
+bool DataModel::GetValue(const String& in_name, Variant& out_value) const
 {
 	bool success = true;
 
-	auto it = bindings.find(name);
-	if (it != bindings.end())
-	{
-		const Binding& binding = it->second;
+	String name = in_name;
+	String member;
 
-		if (binding.type == Type::STRING)
-			out_value = *static_cast<const String*>(binding.ptr);
-		else if (binding.type == Type::INT)
-			success = TypeConverter<int, String>::Convert(*static_cast<const int*>(binding.ptr), out_value);
-		else
-		{
-			RMLUI_ERRORMSG("TODO: Implementation for the provided binding type has not been made yet.");
-			success = false;
-		}
-	}
-	else
+	size_t i_dot = name.find('.');
+	if (i_dot != String::npos)
 	{
-		Log::Message(Log::LT_WARNING, "Could not find value named '%s' in data model.", name.c_str());
-		success = false;
+		name = in_name.substr(0, i_dot);
+		member = in_name.substr(i_dot + 1);
 	}
 
-	return success;
-}
-
-bool DataModel::GetValue(const String& name, bool& out_value) const
-{
-	bool success = true;
-
 	auto it = bindings.find(name);
 	if (it != bindings.end())
 	{
 		const Binding& binding = it->second;
 
-		if (binding.type == Type::STRING)
-			success = TypeConverter<String, bool>::Convert(*static_cast<const String*>(binding.ptr), out_value);
-		else if (binding.type == Type::INT)
-			success = TypeConverter<int, bool>::Convert(*static_cast<const int*>(binding.ptr), out_value);
+		if (binding.type == ValueType::String)
+			out_value = *static_cast<const String*>(binding.ptr);
+		else if (binding.type == ValueType::Int)
+			out_value = *static_cast<const int*>(binding.ptr);
+		else if (binding.type == ValueType::Type)
+		{
+			success = false;
+			auto it_type = data_types.find(binding.data_type_name);
+			if(it_type != data_types.end())
+			{
+				const auto& members = it_type->second;
+				auto it_member = members.find(member);
+				if (it_member != members.end())
+				{
+					auto member_getset_ptr = it_member->second.get();
+					RMLUI_ASSERT(member_getset_ptr);
+					success = member_getset_ptr->Get(binding.ptr, out_value);
+				}
+			}
+			if(!success)
+				Log::Message(Log::LT_WARNING, "Could not get value from member '%s' in value named '%s' in data model.", member.c_str(), name.c_str());
+		}
 		else
 		{
 			RMLUI_ERRORMSG("TODO: Implementation for the provided binding type has not been made yet.");
@@ -273,7 +275,7 @@ bool DataModel::GetValue(const String& name, bool& out_value) const
 }
 
 
-bool DataModel::SetValue(const String& name, const String& value) const
+bool DataModel::SetValue(const String& name, const Variant& value) const
 {
 	bool result = true;
 
@@ -284,10 +286,10 @@ bool DataModel::SetValue(const String& name, const String& value) const
 
 		if (binding.writable)
 		{
-			if (binding.type == Type::STRING)
-				*static_cast<String*>(binding.ptr) = value;
-			else if (binding.type == Type::INT)
-				result = TypeConverter<String, int>::Convert(value, *static_cast<int*>(binding.ptr));
+			if (binding.type == ValueType::String)
+				result = value.GetInto(*static_cast<String*>(binding.ptr));
+			else if (binding.type == ValueType::Int)
+				result = value.GetInto(*static_cast<int*>(binding.ptr));
 			else
 			{
 				RMLUI_ERRORMSG("TODO: Implementation for the provided binding type has not been made yet.");