Bläddra i källkod

Refactor data bindings

Michael Ragazzon 6 år sedan
förälder
incheckning
e9610d33bb

+ 6 - 2
CMake/FileList.cmake

@@ -108,7 +108,9 @@ set(Core_PUB_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ContextInstancer.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ConvolutionFilter.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Core.h
-    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/DataBinding.h
+    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/DataController.h
+    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/DataModel.h
+    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/DataView.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Debug.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Decorator.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/DecoratorInstancer.h
@@ -193,7 +195,9 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/ContextInstancerDefault.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ConvolutionFilter.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Core.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/DataBinding.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/DataController.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/DataModel.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/DataView.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Decorator.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorGradient.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorInstancer.cpp

+ 3 - 1
Include/RmlUi/Core.h

@@ -39,7 +39,9 @@
 #include "Core/ComputedValues.h"
 #include "Core/Context.h"
 #include "Core/ContextInstancer.h"
-#include "Core/DataBinding.h"
+#include "Core/DataController.h"
+#include "Core/DataModel.h"
+#include "Core/DataView.h"
 #include "Core/Decorator.h"
 #include "Core/DecoratorInstancer.h"
 #include "Core/Element.h"

+ 1 - 1
Include/RmlUi/Core/Context.h

@@ -34,7 +34,7 @@
 #include "Traits.h"
 #include "Input.h"
 #include "ScriptInterface.h"
-#include "DataBinding.h"
+#include "DataModel.h"
 
 namespace Rml {
 namespace Core {

+ 0 - 329
Include/RmlUi/Core/DataBinding.h

@@ -1,329 +0,0 @@
-/*
- * 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 RMLUICOREDATABINDING_H
-#define RMLUICOREDATABINDING_H
-
-#include "Header.h"
-#include "Types.h"
-#include "Variant.h"
-#include "StringUtilities.h"
-
-namespace Rml {
-namespace Core {
-
-class Element;
-class DataModel;
-class ElementText;
-
-
-class DataViewText {
-public:
-	DataViewText(const DataModel& model, ElementText* in_element, const String& in_text, size_t index_begin_search = 0);
-
-	inline operator bool() const {
-		return !data_entries.empty() && element;
-	}
-
-	bool Update(const DataModel& model);
-
-private:
-	String BuildText() const;
-
-	struct DataEntry {
-		size_t index = 0; // Index into 'text'
-		String name;
-		String value;
-	};
-
-	ObserverPtr<Element> element;
-	String text;
-	std::vector<DataEntry> data_entries;
-};
-
-
-
-class DataViewAttribute {
-public:
-	DataViewAttribute(const DataModel& model, Element* element, const String& attribute_name, const String& value_name);
-
-	inline operator bool() const {
-		return !attribute_name.empty() && element;
-	}
-	bool Update(const DataModel& model);
-
-private:
-	ObserverPtr<Element> element;
-	String attribute_name;
-	String value_name;
-};
-
-
-class DataViewIf {
-public:
-	DataViewIf(const DataModel& model, Element* element, const String& binding_name);
-
-	inline operator bool() const {
-		return !binding_name.empty() && element;
-	}
-	bool Update(const DataModel& model);
-
-private:
-	ObserverPtr<Element> element;
-	String binding_name;
-};
-
-
-class DataViews {
-public:
-
-	void AddView(DataViewText&& view) {
-		text_views.push_back(std::move(view));
-	}
-	void AddView(DataViewAttribute&& view) {
-		attribute_views.push_back(std::move(view));
-	}
-	void AddView(DataViewIf&& view) {
-		if_views.push_back(std::move(view));
-	}
-
-	bool Update(const DataModel& model)
-	{
-		bool result = false;
-		for (auto& view : text_views)
-			result |= view.Update(model);
-		for (auto& view : attribute_views)
-			result |= view.Update(model);
-		for (auto& view : if_views)
-			result |= view.Update(model);
-		return result;
-	}
-
-private:
-	std::vector<DataViewText> text_views;
-	std::vector<DataViewAttribute> attribute_views;
-	std::vector<DataViewIf> if_views;
-};
-
-
-class DataControllerAttribute {
-public:
-	DataControllerAttribute(const DataModel& model, const String& in_attribute_name, const String& in_value_name);
-
-	inline operator bool() const {
-		return !attribute_name.empty();
-	}
-	bool Update(Element* element, const DataModel& model);
-
-
-	bool OnAttributeChange( const ElementAttributes& changed_attributes)
-	{
-		bool result = false;
-		if (changed_attributes.count(attribute_name) > 0)
-		{
-			dirty = true;
-			result = true;
-		}
-		return result;
-	}
-
-private:
-	bool dirty = false;
-	String attribute_name;
-	String value_name;
-};
-
-
-class DataControllers {
-public:
-
-	void AddController(Element* element, DataControllerAttribute&& controller) {
-		// TODO: Enable multiple controllers per element
-		bool inserted = attribute_controllers.emplace(element, std::move(controller)).second;
-		RMLUI_ASSERT(inserted);
-	}
-
-	bool Update(const DataModel& model)
-	{
-		bool result = false;
-		for (auto& controller : attribute_controllers)
-			result |= controller.second.Update(controller.first, model);
-		return result;
-	}
-
-
-	void OnAttributeChange(DataModel& model, Element* element, const ElementAttributes& changed_attributes)
-	{
-		auto it = attribute_controllers.find(element);
-		if (it != attribute_controllers.end())
-		{
-			it->second.OnAttributeChange(changed_attributes);
-		}
-	}
-
-private:
-	UnorderedMap<Element*, DataControllerAttribute> attribute_controllers;
-};
-
-
-
-// -- 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:
-	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 {
-		ValueType type = ValueType::None;
-		void* ptr = nullptr;
-		bool writable = false;
-		String data_type_name;
-	};
-
-	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 = UnorderedMap<String, Binding>;
-	Bindings bindings;
-
-	DataControllers controllers;
-	DataViews views;
-
-	using DataTypeMembers = SmallUnorderedMap<String, UniquePtr<DataMemberGetSet>>;
-	using DataTypes = UnorderedMap<String, DataTypeMembers>;
-
-	DataTypes data_types;
-};
-
-
-
-
-class DataTypeHandle {
-public:
-	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& 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);
-	}
-
-	void UpdateViews() {
-		RMLUI_ASSERT(model);
-		model->views.Update(*model);
-	}
-
-	operator bool() { return model != nullptr; }
-
-private:
-	DataModel* model;
-};
-
-}
-}
-
-#endif

+ 107 - 0
Include/RmlUi/Core/DataController.h

@@ -0,0 +1,107 @@
+/*
+ * 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 RMLUICOREDATACONTROLLER_H
+#define RMLUICOREDATACONTROLLER_H
+
+#include "Header.h"
+#include "Types.h"
+#include "Variant.h"
+#include "StringUtilities.h"
+
+namespace Rml {
+namespace Core {
+
+class Element;
+class DataModel;
+
+
+class DataControllerAttribute {
+public:
+	DataControllerAttribute(const DataModel& model, const String& in_attribute_name, const String& in_value_name);
+
+	inline operator bool() const {
+		return !attribute_name.empty();
+	}
+	bool Update(Element* element, const DataModel& model);
+
+
+	bool OnAttributeChange( const ElementAttributes& changed_attributes)
+	{
+		bool result = false;
+		if (changed_attributes.count(attribute_name) > 0)
+		{
+			dirty = true;
+			result = true;
+		}
+		return result;
+	}
+
+private:
+	bool dirty = false;
+	String attribute_name;
+	String value_name;
+};
+
+
+class DataControllers {
+public:
+
+	void AddController(Element* element, DataControllerAttribute&& controller) {
+		// TODO: Enable multiple controllers per element
+		bool inserted = attribute_controllers.emplace(element, std::move(controller)).second;
+		RMLUI_ASSERT(inserted);
+	}
+
+	bool Update(const DataModel& model)
+	{
+		bool result = false;
+		for (auto& controller : attribute_controllers)
+			result |= controller.second.Update(controller.first, model);
+		return result;
+	}
+
+
+	void OnAttributeChange(DataModel& model, Element* element, const ElementAttributes& changed_attributes)
+	{
+		auto it = attribute_controllers.find(element);
+		if (it != attribute_controllers.end())
+		{
+			it->second.OnAttributeChange(changed_attributes);
+		}
+	}
+
+private:
+	UnorderedMap<Element*, DataControllerAttribute> attribute_controllers;
+};
+
+
+}
+}
+
+#endif

+ 232 - 0
Include/RmlUi/Core/DataModel.h

@@ -0,0 +1,232 @@
+/*
+ * 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 RMLUICOREDATAMODEL_H
+#define RMLUICOREDATAMODEL_H
+
+#include "Header.h"
+#include "Types.h"
+#include "Variant.h"
+#include "StringUtilities.h"
+#include "DataView.h"
+#include "DataController.h"
+
+namespace Rml {
+namespace Core {
+
+class DataBinding;
+class DataMember;
+
+
+class DataModel {
+public:
+	bool GetValue(const String& name, Variant& out_value) const;
+	bool SetValue(const String& name, const Variant& value) const;
+
+	template<typename T>
+	bool GetValue(const String& name, T& out_value) const {
+		Variant variant;
+		return GetValue(name, variant) && variant.GetInto<T>(out_value);
+	}
+
+	using Bindings = UnorderedMap<String, UniquePtr<DataBinding>>;
+	Bindings bindings;
+
+	DataControllers controllers;
+	DataViews views;
+
+	using DataMembers = SmallUnorderedMap<String, UniquePtr<DataMember>>;
+	using DataTypes = UnorderedMap<String, DataMembers>;
+
+	DataTypes data_types;
+};
+
+
+
+class DataBinding {
+public:
+	DataBinding(void* ptr) : ptr(ptr) {}
+	virtual ~DataBinding() = default;
+
+	inline bool Get(Variant& out_value) {
+		return Get(ptr, out_value);
+	}
+	inline bool Set(const Variant& in_value) {
+		return Set(ptr, in_value);
+	}
+
+protected:
+	virtual bool Get(const void* object, Variant& out_value) = 0;
+	virtual bool Set(void* object, const Variant& in_value) = 0;
+
+private:
+	void* ptr;
+};
+
+
+class DataMember {
+public:
+	virtual ~DataMember() = default;
+	virtual bool Get(const void* object, Variant& out_value) = 0;
+	virtual bool Set(void* object, const Variant& in_value) = 0;
+};
+
+
+
+template<typename T>
+class DataBindingDefault : public DataBinding {
+public:
+	DataBindingDefault(void* ptr) : DataBinding(ptr) {}
+
+private:
+	bool Get(const void* object, Variant& out_value) override {
+		out_value = *static_cast<const T*>(object);
+		return true;
+	}
+	bool Set(void* object, const Variant& in_value) override {
+		T& target = *static_cast<T*>(object);
+		return in_value.GetInto<T>(target);
+	}
+};
+
+class DataBindingMember : public DataBinding {
+public:
+	DataBindingMember(void* object, DataMember* member) : DataBinding(object), member(member) {}
+
+private:
+	bool Get(const void* object, Variant& out_value) override {
+		return member->Get(object, out_value);
+	}
+	bool Set(void* object, const Variant& in_value) override {
+		return member->Set(object, in_value);
+	}
+
+	DataMember* member;
+};
+
+
+template <typename Object, typename MemberType>
+class DataMemberDefault : public DataMember {
+public:
+	DataMemberDefault(MemberType Object::* member_ptr) : member_ptr(member_ptr) {}
+
+	bool Get(const void* object, Variant& out_value) override {
+		out_value = static_cast<const Object*>(object)->*member_ptr;
+		return true;
+	}
+	bool Set(void* object, const Variant& in_value) override {
+		MemberType& target = static_cast<Object*>(object)->*member_ptr;
+		return in_value.GetInto<MemberType>(target);
+	}
+
+private:
+	MemberType Object::* member_ptr;
+};
+
+
+class DataTypeHandle {
+public:
+	DataTypeHandle(DataModel::DataMembers* members) : members(members) {}
+
+	template <typename Object, typename MemberType>
+	DataTypeHandle& RegisterMember(String name, MemberType Object::* member_ptr)
+	{
+		RMLUI_ASSERT(members);
+		members->emplace(name, std::make_unique<DataMemberDefault<Object, MemberType>>(member_ptr));
+		return *this;
+	}
+
+private:
+	DataModel::DataMembers* members;
+};
+
+
+class DataModelHandle {
+public:
+	DataModelHandle() : model(nullptr) {}
+	DataModelHandle(DataModel* model) : model(model) {}
+
+	template <typename T>
+	DataModelHandle& BindValue(String name, T* object)
+	{
+		RMLUI_ASSERT(model);
+		model->bindings.emplace(name, std::make_unique<DataBindingDefault<T>>( object ));
+		return *this;
+	}
+
+	DataModelHandle& BindTypeValue(String name, String type_name, void* object)
+	{
+		RMLUI_ASSERT(model);
+		// 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.
+
+		auto it = model->data_types.find(type_name);
+		if (it != model->data_types.end())
+		{
+			auto& members = it->second;
+			for (auto& pair : members)
+			{
+				const String full_name = name + '.' + pair.first;
+				DataMember* member = pair.second.get();
+				bool inserted = model->bindings.emplace(full_name, std::make_unique<DataBindingMember>(object, member)).second;
+				RMLUI_ASSERT(inserted);
+			}
+		}
+
+		return *this;
+	}
+
+	DataTypeHandle RegisterType(String name)
+	{
+		RMLUI_ASSERT(model);
+		auto result = model->data_types.emplace(name, DataModel::DataMembers() );
+		return DataTypeHandle(&result.first->second);
+	}
+
+	void UpdateControllers() {
+		RMLUI_ASSERT(model);
+		model->controllers.Update(*model);
+	}
+
+	void UpdateViews() {
+		RMLUI_ASSERT(model);
+		model->views.Update(*model);
+	}
+
+	operator bool() { return model != nullptr; }
+
+private:
+	DataModel* model;
+};
+
+}
+}
+
+#endif

+ 136 - 0
Include/RmlUi/Core/DataView.h

@@ -0,0 +1,136 @@
+/*
+ * 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 RMLUICOREDATAVIEW_H
+#define RMLUICOREDATAVIEW_H
+
+#include "Header.h"
+#include "Types.h"
+#include "Variant.h"
+#include "StringUtilities.h"
+
+namespace Rml {
+namespace Core {
+
+class Element;
+class ElementText;
+class DataModel;
+
+
+class DataViewText {
+public:
+	DataViewText(const DataModel& model, ElementText* in_element, const String& in_text, size_t index_begin_search = 0);
+
+	inline operator bool() const {
+		return !data_entries.empty() && element;
+	}
+
+	bool Update(const DataModel& model);
+
+private:
+	String BuildText() const;
+
+	struct DataEntry {
+		size_t index = 0; // Index into 'text'
+		String name;
+		String value;
+	};
+
+	ObserverPtr<Element> element;
+	String text;
+	std::vector<DataEntry> data_entries;
+};
+
+
+
+class DataViewAttribute {
+public:
+	DataViewAttribute(const DataModel& model, Element* element, const String& attribute_name, const String& value_name);
+
+	inline operator bool() const {
+		return !attribute_name.empty() && element;
+	}
+	bool Update(const DataModel& model);
+
+private:
+	ObserverPtr<Element> element;
+	String attribute_name;
+	String value_name;
+};
+
+
+class DataViewIf {
+public:
+	DataViewIf(const DataModel& model, Element* element, const String& binding_name);
+
+	inline operator bool() const {
+		return !binding_name.empty() && element;
+	}
+	bool Update(const DataModel& model);
+
+private:
+	ObserverPtr<Element> element;
+	String binding_name;
+};
+
+
+class DataViews {
+public:
+
+	void AddView(DataViewText&& view) {
+		text_views.push_back(std::move(view));
+	}
+	void AddView(DataViewAttribute&& view) {
+		attribute_views.push_back(std::move(view));
+	}
+	void AddView(DataViewIf&& view) {
+		if_views.push_back(std::move(view));
+	}
+
+	bool Update(const DataModel& model)
+	{
+		bool result = false;
+		for (auto& view : text_views)
+			result |= view.Update(model);
+		for (auto& view : attribute_views)
+			result |= view.Update(model);
+		for (auto& view : if_views)
+			result |= view.Update(model);
+		return result;
+	}
+
+private:
+	std::vector<DataViewText> text_views;
+	std::vector<DataViewAttribute> attribute_views;
+	std::vector<DataViewIf> if_views;
+};
+
+}
+}
+
+#endif

+ 8 - 79
Samples/basic/databinding/src/main.cpp

@@ -103,8 +103,6 @@ private:
 
 
 
-
-
 struct Invader {
 	Rml::Core::String name;
 	Rml::Core::String sprite;
@@ -112,75 +110,10 @@ struct Invader {
 };
 
 
-/*
-
-// -- 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;
+	bool good_rating = 1;
 
 	Invader invader{ "Delightful invader", "icon-invader", Rml::Core::Colourf{} };
 
@@ -191,24 +124,20 @@ Rml::Core::DataModelHandle my_model;
 
 bool SetupDataBinding(Rml::Core::Context* context)
 {
-	using Rml::Core::ValueType;
-
 	my_model = context->CreateDataModel("my_model");
 	if (!my_model)
 		return false;
 
-	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();
+	my_model.BindValue("hello_world", &my_data.hello_world);
+	my_model.BindValue("rating", &my_data.rating);
+	my_model.BindValue("good_rating", &my_data.good_rating);
 
 	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);
+	invader_type.RegisterMember("name", &Invader::name);
+	invader_type.RegisterMember("sprite", &Invader::sprite);
+	invader_type.RegisterMember("color", &Invader::color);
 
-	my_model.BindDataTypeValue("invader", "Invader", &my_data.invader);
+	my_model.BindTypeValue("invader", "Invader", &my_data.invader);
 
 	return true;
 }

+ 61 - 0
Source/Core/DataController.cpp

@@ -0,0 +1,61 @@
+/*
+ * 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 "precompiled.h"
+#include "../../Include/RmlUi/Core/DataController.h"
+#include "../../Include/RmlUi/Core/DataModel.h"
+
+namespace Rml {
+namespace Core {
+
+
+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)
+{
+	if (model.bindings.count(value_name) == 0)
+	{
+		attribute_name.clear();
+		value_name.clear();
+	}
+}
+
+bool DataControllerAttribute::Update(Element* element, const DataModel& model) 
+{
+	bool result = false;
+	if (dirty)
+	{
+		if(Variant* value = element->GetAttribute(attribute_name))
+			result = model.SetValue(value_name, *value);
+		dirty = false;
+	}
+	return result;
+}
+
+
+
+}
+}

+ 82 - 0
Source/Core/DataModel.cpp

@@ -0,0 +1,82 @@
+/*
+ * 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 "precompiled.h"
+#include "../../Include/RmlUi/Core/DataModel.h"
+
+namespace Rml {
+namespace Core {
+
+
+bool DataModel::GetValue(const String& name, Variant& out_value) const
+{
+	bool success = true;
+
+	auto it = bindings.find(name);
+	if (it != bindings.end())
+	{
+		DataBinding& binding = *it->second;
+
+		success = binding.Get(out_value);
+		if (!success)
+			Log::Message(Log::LT_WARNING, "Could not get value from '%s' in data model.", name.c_str());
+	}
+	else
+	{
+		Log::Message(Log::LT_WARNING, "Could not find value named '%s' in data model.", name.c_str());
+		success = false;
+	}
+
+	return success;
+}
+
+
+bool DataModel::SetValue(const String& name, const Variant& value) const
+{
+	bool success = true;
+
+	auto it = bindings.find(name);
+	if (it != bindings.end())
+	{
+		DataBinding& binding = *it->second;
+
+		success = binding.Set(value);
+		if (!success)
+			Log::Message(Log::LT_WARNING, "Could not set value to '%s' in data model.", name.c_str());
+	}
+	else
+	{
+		Log::Message(Log::LT_WARNING, "Could not find value named '%s' in data model.", name.c_str());
+		success = false;
+	}
+
+	return success;
+}
+
+}
+}

+ 6 - 131
Source/Core/DataBinding.cpp → Source/Core/DataView.cpp

@@ -27,8 +27,9 @@
  */
 
 #include "precompiled.h"
-#include "../../Include/RmlUi/Core/DataBinding.h"
-#include "../../Include/RmlUi/Core/Element.h"
+#include "../../Include/RmlUi/Core/DataView.h"
+#include "../../Include/RmlUi/Core/DataModel.h"
+
 
 namespace Rml {
 namespace Core {
@@ -111,7 +112,7 @@ bool DataViewText::Update(const DataModel& model)
 		}
 		else
 		{
-			Log::Message(Log::LT_WARNING, "Could not update data view text, parent element no longer valid. Was it destroyed?");
+			Log::Message(Log::LT_WARNING, "Could not update data view text, element no longer valid. Was it destroyed?");
 		}
 	}
 
@@ -177,12 +178,9 @@ DataViewIf::DataViewIf(const DataModel& model, Element* element, const String& b
 bool DataViewIf::Update(const DataModel& model)
 {
 	bool result = false;
-	Variant variant;
-	int int_value = 0;
-
-	if (model.GetValue(binding_name, variant) && variant.GetInto(int_value))
+	bool value = false;
+	if (model.GetValue(binding_name, value))
 	{
-		bool value = (int_value != 0);
 		bool is_visible = (element->GetLocalStyleProperties().count(PropertyId::Display) == 0);
 		if(is_visible != value)
 		{
@@ -196,128 +194,5 @@ bool DataViewIf::Update(const DataModel& model)
 	return result;
 }
 
-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)
-{
-	if (!model.IsWritable(value_name))
-	{
-		attribute_name.clear();
-		value_name.clear();
-	}
-}
-
-bool DataControllerAttribute::Update(Element* element, const DataModel& model) 
-{
-	bool result = false;
-	if (dirty)
-	{
-		if(Variant* value = element->GetAttribute(attribute_name))
-			result = model.SetValue(value_name, *value);
-		dirty = false;
-	}
-	return result;
-}
-
-
-bool DataModel::GetValue(const String& in_name, Variant& out_value) const
-{
-	bool success = true;
-
-	String name = in_name;
-	String member;
-
-	size_t i_dot = name.find('.');
-	if (i_dot != String::npos)
-	{
-		name = in_name.substr(0, i_dot);
-		member = in_name.substr(i_dot + 1);
-	}
-
-	auto it = bindings.find(name);
-	if (it != bindings.end())
-	{
-		const Binding& binding = it->second;
-
-		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.");
-			success = false;
-		}
-	}
-	else
-	{
-		Log::Message(Log::LT_WARNING, "Could not find value named '%s' in data model.", name.c_str());
-		success = false;
-	}
-
-	return success;
-}
-
-
-bool DataModel::SetValue(const String& name, const Variant& value) const
-{
-	bool result = true;
-
-	auto it = bindings.find(name);
-	if (it != bindings.end())
-	{
-		const Binding& binding = it->second;
-
-		if (binding.writable)
-		{
-			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.");
-				result = false;
-			}
-		}
-		else
-		{
-			RMLUI_ERRORMSG("Controller attempted to write to a non-writable binding.");
-			result = false;
-		}
-	}
-	else
-	{
-		Log::Message(Log::LT_WARNING, "Could not find value named '%s' in data model.", name.c_str());
-		result = false;
-	}
-	return false;
-}
-
-bool DataModel::IsWritable(const String& name) const
-{
-	bool result = false;
-	auto it = bindings.find(name);
-	if (it != bindings.end())
-		result = it->second.writable;
-	return result;
-}
-
 }
 }