Browse Source

Update data controller

Michael Ragazzon 5 years ago
parent
commit
acebd3b220

+ 31 - 43
Include/RmlUi/Core/DataController.h

@@ -32,7 +32,6 @@
 #include "Header.h"
 #include "Types.h"
 #include "Variant.h"
-#include "StringUtilities.h"
 #include "DataVariable.h"
 
 namespace Rml {
@@ -41,65 +40,54 @@ namespace Core {
 class Element;
 class DataModel;
 
-class RMLUICORE_API DataController {};
-
-
-class DataControllerAttribute : public DataController {
+class RMLUICORE_API DataController {
 public:
-	DataControllerAttribute(DataModel& model, Element* parent, const String& in_attribute_name, const String& in_value_name);
+	bool UpdateVariable(DataModel& model);
+
+	const String& GetVariableName() const;
 
 	explicit operator bool() const {
-		return !attribute_name.empty() && !variable_address.empty();
+		return !address.empty() && attached_element;
 	}
-	bool Update(Element* element, DataModel& model);
-
-	bool OnAttributeChange( const ElementAttributes& changed_attributes)
-	{
-		bool result = false;
-		if (changed_attributes.count(attribute_name) > 0)
-		{
-			dirty = true;
-			result = true;
-		}
-		return result;
+
+    Element* GetElement() const {
+        return attached_element.get();
+    }
+
+protected:
+	DataController(Element* element);
+
+	void SetAddress(Address new_address) {
+		address = std::move(new_address);
 	}
 
+	// Return true if value changed
+	virtual bool UpdateValue(Element* element, Variant& value_inout) = 0;
+
 private:
-	bool dirty = false;
-	String attribute_name;
-	Address variable_address;
+	ObserverPtr<Element> attached_element;
+	Address address;
+	Variant value;
 };
 
 
-class RMLUICORE_API DataControllers : NonCopyMoveable {
+class DataControllerValue final : public DataController {
 public:
+	DataControllerValue(DataModel& model, Element* element, const String& in_value_name);
 
-	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);
-	}
+private:
+	bool UpdateValue(Element* element, Variant& value_inout) override;
+};
 
-	bool Update(DataModel& model)
-	{
-		bool result = false;
-		for (auto& controller : attribute_controllers)
-			result |= controller.second.Update(controller.first, model);
-		return result;
-	}
 
+class RMLUICORE_API DataControllers : NonCopyMoveable {
+public:
+	void Add(UniquePtr<DataController> controller);
 
-	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);
-		}
-	}
+    void DirtyElement(DataModel& model, Element* element);
 
 private:
-	UnorderedMap<Element*, DataControllerAttribute> attribute_controllers;
+	UnorderedMap<Element*, UniquePtr<DataController>> controllers;
 };
 
 

+ 11 - 12
Include/RmlUi/Core/DataModel.h

@@ -36,7 +36,6 @@
 #include "DataView.h"
 #include "DataController.h"
 #include "DataVariable.h"
-#include <unordered_map>
 
 namespace Rml {
 namespace Core {
@@ -59,22 +58,23 @@ public:
 	}
 
 	void DirtyVariable(const String& variable_name);
-	bool UpdateVariable(const String& variable_name);
+	bool IsVariableDirty(const String& variable_name) const;
 
 	Address ResolveAddress(const String& address_str, Element* parent) const;
 
 	void AddView(UniquePtr<DataView> view) { views.Add(std::move(view)); }
+	void AddController(UniquePtr<DataController> controller) { controllers.Add(std::move(controller)); }
 
 	// Todo: remove const / mutable. 
 	bool InsertAlias(Element* element, const String& alias_name, Address replace_with_address) const;
 	bool EraseAliases(Element* element) const;
 
-	bool UpdateViews();
+	bool Update();
 
+	void DirtyController(Element* element) { controllers.DirtyElement(*this, element); }
 	void OnElementRemove(Element* element);
 
-	// Todo: Make private
-	DataControllers controllers;
+
 
 private:
 	UnorderedMap<String, Variable> variables;
@@ -85,6 +85,8 @@ private:
 	DataViews views;
 
 	SmallUnorderedSet< String > dirty_variables;
+
+	DataControllers controllers;
 };
 
 
@@ -95,15 +97,12 @@ public:
 		RMLUI_ASSERT(model && type_register);
 	}
 
-	void UpdateControllers() {
-		model->controllers.Update(*model);
-	}
-	void UpdateViews() {
-		model->UpdateViews();
+	void Update() {
+		model->Update();
 	}
 
-	bool UpdateVariable(const String& variable_name) {
-		return model->UpdateVariable(variable_name);
+	bool IsVariableDirty(const String& variable_name) {
+		return model->IsVariableDirty(variable_name);
 	}
 	void DirtyVariable(const String& variable_name) {
 		model->DirtyVariable(variable_name);

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

@@ -35,6 +35,7 @@
 #include "StringUtilities.h"
 #include "Traits.h"
 #include "DataVariable.h"
+#include <unordered_map>
 
 namespace Rml {
 namespace Core {

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

@@ -165,7 +165,7 @@ form h2
 <panel id="welcome" data-model="my_model">
 	<p class="title" style="margin-top: 1.8em;">{{hello_world}}<span style="color: blue;"> Rated: {{rating}}</span></p>
 	<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"/>
+	<input type="range" name="rating" min="0" max="100" step="1" value="50" data-value="rating"/>
 	<div data-if="good_rating">Thanks for the <span data-if="good_rating">good</span><span data-if="great_rating"> and awesome</span> rating!</div>
 	<h1>{{delightful_invader.name}}</h1>
 	<img data-attr-sprite="delightful_invader.sprite" data-style-image-color="delightful_invader.color"/>

+ 3 - 5
Samples/basic/databinding/src/main.cpp

@@ -180,15 +180,13 @@ std::unique_ptr<DemoWindow> demo_window;
 
 void GameLoop()
 {
-	my_model.UpdateControllers();
-
-	if(my_model.UpdateVariable("rating"))
+	if (my_model.IsVariableDirty("rating"))
 	{
 		my_model.DirtyVariable("good_rating");
 		my_model.DirtyVariable("great_rating");
 
 		size_t new_size = my_data.rating / 10 + 1;
-		if(new_size != my_data.indices.size())
+		if (new_size != my_data.indices.size())
 		{
 			my_data.indices.resize(new_size);
 			std::iota(my_data.indices.begin(), my_data.indices.end(), int(new_size));
@@ -196,7 +194,7 @@ void GameLoop()
 		}
 	}
 
-	my_model.UpdateViews();
+	my_model.Update();
 
 	demo_window->Update();
 	context->Update();

+ 7 - 0
Source/Controls/ElementFormControl.cpp

@@ -28,6 +28,7 @@
 
 #include "../../Include/RmlUi/Controls/ElementFormControl.h"
 #include "../../Include/RmlUi/Core/ComputedValues.h"
+#include "../../Include/RmlUi/Core/DataModel.h"
 
 namespace Rml {
 namespace Controls {
@@ -94,6 +95,12 @@ void ElementFormControl::OnAttributeChange(const Core::ElementAttributes& change
 		else
 			RemoveProperty("focus");
 	}
+
+	if (Core::DataModel* data_model = GetDataModel())
+	{
+		if (changed_attributes.find("value") != changed_attributes.end())
+			data_model->DirtyController(this);
+	}
 }
 
 }

+ 69 - 20
Source/Core/DataController.cpp

@@ -33,36 +33,85 @@
 namespace Rml {
 namespace Core {
 
+DataController::DataController(Element* element) : attached_element(element->GetObserverPtr())
+{}
 
-DataControllerAttribute::DataControllerAttribute(DataModel& model, Element* parent, const String& in_attribute_name, const String& in_value_name) : attribute_name(in_attribute_name)
+bool DataController::UpdateVariable(DataModel& model)
 {
-    variable_address = model.ResolveAddress(in_value_name, parent);
-    if (!model.GetVariable(variable_address))
-	{
-		attribute_name.clear();
-        variable_address.clear();
-	}
+    Element* element = attached_element.get();
+    if (!element)
+        return false;
+
+    if (!UpdateValue(element, value))
+        return false;
+
+    bool variable_changed = false;
+    if (Variable variable = model.GetVariable(address))
+        variable_changed = variable.Set(value);
+
+    return variable_changed;
 }
 
-bool DataControllerAttribute::Update(Element* element, DataModel& model) 
+const String& DataController::GetVariableName() const {
+    static const String empty_string;
+    return address.empty() ? empty_string : address.front().name;
+}
+
+
+DataControllerValue::DataControllerValue(DataModel& model, Element* element, const String& in_value_name) : DataController(element)
 {
-	bool result = false;
-	if (dirty)
-	{
-		if(Variant* value = element->GetAttribute(attribute_name))
+    Address variable_address = model.ResolveAddress(in_value_name, element);
+
+    if (model.GetVariable(variable_address) && !variable_address.empty())
+    {
+        SetAddress(std::move(variable_address));
+    }
+}
+
+bool DataControllerValue::UpdateValue(Element* element, Variant& value_inout)
+{
+    bool value_changed = false;
+
+    if (Variant* new_value = element->GetAttribute("value"))
+    {
+        if (*new_value != value_inout)
         {
-            if (Variable variable = model.GetVariable(variable_address))
-            {
-                result = variable.Set(*value);
-                model.DirtyVariable(variable_address.front().name);
-            }
+            value_inout = *new_value;
+            value_changed = true;
         }
-		dirty = false;
-	}
-	return result;
+    }
+    
+    return value_changed;
 }
 
 
 
+
+void DataControllers::Add(UniquePtr<DataController> controller) {
+    RMLUI_ASSERT(controller);
+
+    Element* element = controller->GetElement();
+    RMLUI_ASSERTMSG(element, "Invalid controller, make sure it is valid before adding");
+
+    bool inserted = controllers.emplace(element, std::move(controller)).second;
+    if (!inserted)
+    {
+        RMLUI_ERRORMSG("Cannot add multiple controllers to the same element.");
+    }
+}
+
+void DataControllers::DirtyElement(DataModel& model, Element* element)
+{
+    auto it = controllers.find(element);
+    if (it != controllers.end())
+    {
+        DataController& controller = *it->second;
+        if (controller.UpdateVariable(model))
+        {
+            model.DirtyVariable(controller.GetVariableName());
+        }
+    }
+}
+
 }
 }

+ 3 - 12
Source/Core/DataModel.cpp

@@ -138,17 +138,8 @@ void DataModel::DirtyVariable(const String& variable_name)
 	dirty_variables.insert(variable_name);
 }
 
-bool DataModel::UpdateVariable(const String& variable_name)
-{
-	auto it = dirty_variables.find(variable_name);
-	if (it == dirty_variables.end())
-		return false;
-
-	SmallUnorderedSet< String > dirty_variable{ *it };
-	dirty_variables.erase(it);
-	bool result = views.Update(*this, dirty_variable);
-
-	return result;
+bool DataModel::IsVariableDirty(const String& variable_name) const {
+	return (dirty_variables.count(variable_name) == 1);
 }
 
 
@@ -223,7 +214,7 @@ bool DataModel::EraseAliases(Element* element) const
 	return aliases.erase(element) == 1;
 }
 
-bool DataModel::UpdateViews() 
+bool DataModel::Update() 
 {
 	bool result = views.Update(*this, dirty_variables);
 	dirty_variables.clear();

+ 0 - 2
Source/Core/DataView.cpp

@@ -414,8 +414,6 @@ bool DataViews::Update(const DataModel & model, const SmallUnorderedSet< String
 
 		if (view->IsValid())
 			result |= view->Update(model);
-		else
-			Log::Message(Log::LT_DEBUG, "Invalid view");
 	}
 
 	// Destroy views marked for destruction

+ 0 - 6
Source/Core/Element.cpp

@@ -1617,12 +1617,6 @@ void Element::OnAttributeChange(const ElementAttributes& changed_attributes)
 			meta->style.SetProperty((*i).first, (*i).second);
 		}
 	}
-
-	if (data_model)
-	{
-		// @todo @performance: Add a volatility flag so we don't have to do this all the time.
-		data_model->controllers.OnAttributeChange(*data_model, this, changed_attributes);
-	}
 }
 
 // Called when properties on the element are changed.

+ 13 - 4
Source/Core/Factory.cpp

@@ -290,12 +290,21 @@ ElementPtr Factory::InstanceElement(Element* parent, const String& instancer_nam
 									data_model->AddView(std::move(view));
 								else
 									Log::Message(Log::LT_WARNING, "Could not add data-attr view to element '%s'.", parent->GetAddress().c_str());
+							}
+							else if (data_type == "value")
+							{
+								const String attr_bind_name = "value";
+								auto view = std::make_unique<DataViewAttribute>(*data_model, element.get(), parent, value_bind_name, attr_bind_name);
+								if (*view)
+									data_model->AddView(std::move(view));
+								else
+									Log::Message(Log::LT_WARNING, "Could not add data-value view to element '%s'.", parent->GetAddress().c_str());
 
-								DataControllerAttribute data_controller(*data_model, parent, attr_bind_name, value_bind_name);
-								if (data_controller)
-									data_model->controllers.AddController(element.get(), std::move(data_controller));
+								auto controller = std::make_unique<DataControllerValue>(*data_model, element.get(), value_bind_name);
+								if (controller)
+									data_model->AddController(std::move(controller));
 								else
-									Log::Message(Log::LT_WARNING, "Could not add data-attr controller to element '%s'.", parent->GetAddress().c_str());
+									Log::Message(Log::LT_WARNING, "Could not add data-value controller to element '%s'.", parent->GetAddress().c_str());
 							}
 							else if (data_type == "style")
 							{