Browse Source

- Implemented 'data-event' controller. Can use assignment in its expression, and also 'ev.' variable name to fetch parameters from the event.
- Move default controllers to separate file.
- The 'data-value' controller now uses the 'change' event to listen for value changes.
- Get size on data array variables by .size member.

Michael Ragazzon 5 years ago
parent
commit
373084165c

+ 2 - 0
CMake/FileList.cmake

@@ -4,6 +4,7 @@ set(Core_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/Clock.h
     ${PROJECT_SOURCE_DIR}/Source/Core/ComputeProperty.h
     ${PROJECT_SOURCE_DIR}/Source/Core/ContextInstancerDefault.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/DataControllerDefault.h
     ${PROJECT_SOURCE_DIR}/Source/Core/DataParser.h
     ${PROJECT_SOURCE_DIR}/Source/Core/DataViewDefault.h
     ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorGradient.h
@@ -199,6 +200,7 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/ConvolutionFilter.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Core.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/DataController.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/DataControllerDefault.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/DataModel.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/DataParser.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/DataVariable.cpp

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

@@ -37,6 +37,7 @@ namespace Rml {
 namespace Core {
 
 class Stream;
+class URL;
 using XMLAttributes = Dictionary;
 
 enum class XMLDataType { Text, CData, InnerXML };

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

@@ -44,7 +44,7 @@ class ElementDocument;
 class EventListener;
 class RenderInterface;
 class DataModel;
-class DataModelHandle;
+class DataModelConstructor;
 class DataTypeRegister;
 enum class EventId : uint16_t;
 
@@ -225,7 +225,7 @@ public:
 	/// Elements can bind to the model using the attribute 'data-model="name"'.
 	/// @param[in] name The name of the data model.
 	/// @return A handle to the data model which can be used to bind data variables.
-	DataModelHandle CreateDataModel(const String& name);
+	DataModelConstructor CreateDataModel(const String& name);
 
 protected:
 	void Release() override;

+ 54 - 32
Include/RmlUi/Core/DataController.h

@@ -31,8 +31,8 @@
 
 #include "Header.h"
 #include "Types.h"
-#include "Variant.h"
-#include "DataVariable.h"
+#include "Traits.h"
+#include <unordered_map>
 
 namespace Rml {
 namespace Core {
@@ -40,58 +40,80 @@ namespace Core {
 class Element;
 class DataModel;
 
-class RMLUICORE_API DataController {
-public:
-	bool UpdateVariable(DataModel& model);
 
-    String GetVariableName() const {
-        return address.empty() ? String() : address.front().name;
-    }
+class RMLUICORE_API DataControllerInstancer : public NonCopyMoveable {
+public:
+    DataControllerInstancer() {}
+    virtual ~DataControllerInstancer() {}
+    virtual DataControllerPtr InstanceController(Element* element) = 0;
+};
 
-    Element* GetElement() const {
-        return attached_element.get();
+template<typename T>
+class DataControllerInstancerDefault final : public DataControllerInstancer {
+public:
+    DataControllerPtr InstanceController(Element* element) override {
+        return DataControllerPtr(new T(element));
     }
+};
 
-	explicit operator bool() const {
-		return !address.empty() && attached_element;
-	}
 
-    virtual ~DataController();
+/**
+    Data controller.
 
-protected:
-	DataController(Element* element);
+    Data controllers are used to respond to some change in the document,
+    usually by setting data variables. Such document changes are usually
+    a result of user input.
+    A data controller is declared in the document by the element attribute:
 
-	void SetAddress(DataAddress new_address) {
-		address = std::move(new_address);
-	}
+        data-[type]-[modifier]="[assignment_expression]"
 
-	// Return true if value changed
-	virtual bool UpdateValue(Element* element, Variant& value_inout) = 0;
+    This is similar to declaration of data views, except that controllers
+    instead take an assignment expression to set a variable. Note that, as
+    opposed to views, controllers only respond to certain changes in the
+    document, not to changed data variables.
 
-private:
-	ObserverPtr<Element> attached_element;
-	DataAddress address;
-	Variant value;
-};
+    The modifier may or may not be required depending on the data controller.
 
+ */
 
-class DataControllerValue final : public DataController {
+class RMLUICORE_API DataController : public Releasable {
 public:
-	DataControllerValue(DataModel& model, Element* element, const String& in_value_name);
+	virtual ~DataController();
+
+    // Initialize the data controller.
+    // @param[in] model The data model the controller will be attached to.
+    // @param[in] element The element which spawned the controller.
+    // @param[in] expression The value of the element's 'data-' attribute which spawned the controller (see above).
+    // @param[in] modifier The modifier for the given controller type (see above).
+    // @return True on success.
+    virtual bool Initialize(DataModel& model, Element* element, const String& expression, const String& modifier) = 0;
+
+    // Returns the attached element if it still exists.
+    Element* GetElement() const;
+
+    // Returns true if the element still exists.
+    bool IsValid() const;
+
+protected:
+	DataController(Element* element);
 
 private:
-	bool UpdateValue(Element* element, Variant& value_inout) override;
+	ObserverPtr<Element> attached_element;
 };
 
 
 class RMLUICORE_API DataControllers : NonCopyMoveable {
 public:
-	void Add(UniquePtr<DataController> controller);
+    DataControllers();
+    ~DataControllers();
+
+	void Add(DataControllerPtr controller);
 
-    void DirtyElement(DataModel& model, Element* element);
+    void OnElementRemove(Element* element);
 
 private:
-	UnorderedMap<Element*, UniquePtr<DataController>> controllers;
+    using ElementControllersMap = std::unordered_multimap<Element*, DataControllerPtr>;
+    ElementControllersMap controllers;
 };
 
 

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

@@ -42,16 +42,17 @@ namespace Core {
 
 class Element;
 
-
 class RMLUICORE_API DataModel : NonCopyMoveable {
 public:
 	DataModel(const TransformFuncRegister* transform_register = nullptr) : transform_register(transform_register) {}
 
 	void AddView(DataViewPtr view) { views.Add(std::move(view)); }
-	void AddController(UniquePtr<DataController> controller) { controllers.Add(std::move(controller)); }
+	void AddController(DataControllerPtr controller) { controllers.Add(std::move(controller)); }
 
 	bool BindVariable(const String& name, Variable variable);
 
+	bool BindEventCallback(const String& name, DataEventFunc event_func);
+
 	bool InsertAlias(Element* element, const String& alias_name, DataAddress replace_with_address);
 	bool EraseAliases(Element* element);
 
@@ -59,6 +60,8 @@ public:
 
 	Variable GetVariable(const DataAddress& address) const;
 
+	const DataEventFunc* GetEventCallback(const String& name);
+
 	template<typename T>
 	bool GetValue(const DataAddress& address, T& out_value) const {
 		Variant variant;
@@ -76,7 +79,6 @@ public:
 	bool CallTransform(const String& name, Variant& inout_result, const VariantList& arguments) const;
 
 	void OnElementRemove(Element* element);
-	void DirtyController(Element* element);
 
 	bool Update();
 
@@ -85,7 +87,9 @@ private:
 	DataControllers controllers;
 
 	UnorderedMap<String, Variable> variables;
-	SmallUnorderedSet<String> dirty_variables;
+	DirtyVariables dirty_variables;
+
+	UnorderedMap<String, DataEventFunc> event_callbacks;
 
 	using ScopedAliases = UnorderedMap<Element*, SmallUnorderedMap<String, DataAddress>>;
 	ScopedAliases aliases;
@@ -94,12 +98,11 @@ private:
 };
 
 
+
 class RMLUICORE_API DataModelHandle {
 public:
-	DataModelHandle() : model(nullptr), type_register(nullptr) {}
-	DataModelHandle(DataModel* model, DataTypeRegister* type_register) : model(model), type_register(type_register) {
-		RMLUI_ASSERT(model && type_register);
-	}
+	DataModelHandle(DataModel* model = nullptr) : model(model)
+	{}
 
 	void Update() {
 		model->Update();
@@ -112,17 +115,48 @@ public:
 		model->DirtyVariable(variable_name);
 	}
 
+	explicit operator bool() { return model; }
+
+private:
+	DataModel* model;
+};
+
+class RMLUICORE_API DataModelConstructor {
+public:
+	template<typename T>
+	using DataEventMemberFunc = void(T::*)(DataModelHandle, Event&, const VariantList&);
+
+	DataModelConstructor() : model(nullptr), type_register(nullptr) {}
+	DataModelConstructor(DataModel* model, DataTypeRegister* type_register) : model(model), type_register(type_register) {
+		RMLUI_ASSERT(model && type_register);
+	}
+
+	// Return a handle to the data model being constructed, which can later be used to synchronize variables and update the model.
+	DataModelHandle GetModelHandle() const {
+		return DataModelHandle(model);
+	}
+
 	// Bind a data variable.
 	// @note For non-scalar 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 model->BindVariable(name, Variable(type_register->GetOrAddScalar<T>(), ptr));
 	}
+
 	// Bind a get/set function pair.
 	bool BindFunc(const String& name, DataGetFunc get_func, DataSetFunc set_func = {}) {
 		VariableDefinition* func_definition = type_register->RegisterFunc(std::move(get_func), std::move(set_func));
-		bool result = model->BindVariable(name, Variable(func_definition, nullptr));
-		return result;
+		return model->BindVariable(name, Variable(func_definition, nullptr));
+	}
+
+	// Bind an event callback.
+	bool BindEventCallback(const String& name, DataEventFunc event_func) {
+		return model->BindEventCallback(name, std::move(event_func));
+	}
+	// Convenience wrapper around BindEventCallback for member functions.
+	template<typename T>
+	bool BindEventCallback(const String& name, DataEventMemberFunc<T> member_func, T* object_pointer) {
+		return BindEventCallback(name, std::bind(member_func, object_pointer, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
 	}
 
 	// Register a struct type.
@@ -154,7 +188,6 @@ public:
 private:
 	DataModel* model;
 	DataTypeRegister* type_register;
-
 };
 
 }

+ 10 - 2
Include/RmlUi/Core/DataVariable.h

@@ -39,6 +39,9 @@ namespace Rml {
 namespace Core {
 
 class Variable;
+class DataTypeRegister;
+class DataModelHandle;
+class Event;
 
 template<typename T>
 struct is_valid_scalar {
@@ -53,6 +56,7 @@ enum class DataFunctionHandle : int {};
 using DataGetFunc = std::function<void(Variant&)>;
 using DataSetFunc = std::function<void(const Variant&)>;
 using DataTransformFunc = std::function<bool(Variant&, const VariantList&)>;
+using DataEventFunc = std::function<void(DataModelHandle, Event&, const VariantList&)>;
 
 template<typename T> using MemberGetFunc = void(T::*)(Variant&);
 template<typename T> using MemberSetFunc = void(T::*)(const Variant&);
@@ -158,7 +162,6 @@ private:
 };
 
 
-
 template<typename Container>
 class ArrayDefinition final : public VariableDefinition {
 public:
@@ -174,8 +177,12 @@ protected:
 		Container* ptr = static_cast<Container*>(void_ptr);
 		const int index = address.index;
 
-		if (index < 0 || index >= int(ptr->size()))
+		const int container_size = int(ptr->size());
+		if (index < 0 || index >= container_size)
 		{
+			if (address.name == "size")
+				return Variable(DataTypeRegister::GetArraySizeDefinition(), reinterpret_cast<void*>(static_cast<intptr_t>(container_size)));
+
 			Log::Message(Log::LT_WARNING, "Data array index out of bounds.");
 			return Variable();
 		}
@@ -464,6 +471,7 @@ public:
 		return &transform_register;
 	}
 
+	static VariableDefinition* GetArraySizeDefinition();
 
 private:
 	UnorderedMap<DataFunctionHandle, UniquePtr<FuncDefinition>> functions;

+ 3 - 6
Include/RmlUi/Core/DataView.h

@@ -80,7 +80,7 @@ public:
 	virtual bool Initialize(DataModel& model, Element* element, const String& expression, const String& modifier_or_inner_rml) = 0;
 
 	// Update the data view.
-	// Returns true if the update resulted in a change.
+	// Returns true if the update resulted in a document change.
 	virtual bool Update(DataModel& model) = 0;
 
 	// Returns the list of data variable name(s) which can modify this view.
@@ -92,15 +92,12 @@ public:
 	// Returns the depth of the attached element in the document tree.
 	int GetElementDepth() const;
 	
-	// Returns true if the element still exists
+	// Returns true if the element still exists.
 	bool IsValid() const;
 	
 protected:
 	DataView(Element* element);
 
-	// Delete this
-	void Release() override;
-
 private:
 	ObserverPtr<Element> attached_element;
 	int element_depth;
@@ -117,7 +114,7 @@ public:
 
 	void OnElementRemove(Element* element);
 
-	bool Update(DataModel& model, const SmallUnorderedSet< String >& dirty_variables);
+	bool Update(DataModel& model, const DirtyVariables& dirty_variables);
 
 private:
 	using DataViewList = std::vector<DataViewPtr>;

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

@@ -37,6 +37,7 @@ namespace Core {
 
 class Context;
 class ContextInstancer;
+class DataControllerInstancer;
 class DataViewInstancer;
 class Decorator;
 class DecoratorInstancer;
@@ -176,8 +177,10 @@ public:
 
 	// TODO documentation
 	static void RegisterDataViewInstancer(DataViewInstancer* instancer, const String& name, bool is_structural_view = false);
+	static void RegisterDataControllerInstancer(DataControllerInstancer* instancer, const String& name);
 
 	static DataViewPtr InstanceDataView(const String& type_name, Element* element, bool is_structural_view);
+	static DataControllerPtr InstanceDataController(const String& type_name, Element* element);
 
 private:
 	Factory();

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

@@ -189,6 +189,9 @@ using FontEffectsPtr = SharedPtr<const FontEffects>;
 // Data binding types
 class DataView;
 using DataViewPtr = UniqueReleaserPtr<DataView>;
+class DataController;
+using DataControllerPtr = UniqueReleaserPtr<DataController>;
+using DirtyVariables = SmallUnorderedSet<String>;
 
 }
 }

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

@@ -38,6 +38,7 @@ namespace Core {
 
 class Element;
 class XMLParser;
+enum class XMLDataType;
 
 /**
 	A handler gets ElementStart, ElementEnd and ElementData called by the XMLParser.

+ 19 - 2
Samples/basic/databinding/data/databinding.rml

@@ -93,6 +93,15 @@ p.title
 .big {
 	font-size: 1.8em;
 }
+.mouse_detector {
+	width: 300px;
+	min-height: 2em;
+	line-height: 30px;
+	background-color: #909090;
+	border: 1px #666;
+	margin: 2em auto;
+	cursor: pointer;
+}
 
 
 /***  Decorators  ***/
@@ -171,9 +180,17 @@ 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-value="rating"/>
+	<input type="range" name="rating" min="0" max="100" step="1" value="50" data-value="rating" data-attribute="rating"/>
 	<div data-if="rating > 50">Thanks for the <span data-if="rating < 80">good</span><span data-if="rating >= 80">awesome</span> rating!</div>
-	<div data-rml="'Rml from the attribute? <strong>Yup!</strong>'"></div>
+	<div class="mouse_detector" style="height: 70px;"
+		data-event-mousemove="mouse_detector = 'x: ' + ev.mouse_x + '<br/>y: ' + ev.mouse_y"
+		data-event-click="add_mouse_pos(); hello_world = 'Hello click!'"
+		data-rml="mouse_detector">
+	</div>
+	<div class="mouse_detector" data-if="positions.size > 0" data-event-click="clear_positionsÅ">
+		Recorded mouse positions.<br/>
+		<span data-for="pos : positions"> x: {{ pos.x }}, y: {{ pos.y }}<br/></span>
+	</div>
 	<h1>{{delightful_invader.name}}</h1>
 	<img data-attr-sprite="delightful_invader.sprite" data-style-image-color="rating < 80 ? 'black' : 'green'"/>
 	<p>

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

@@ -118,8 +118,9 @@ struct Invader {
 
 struct MyData {
 	Rml::Core::String hello_world = "Hello World!";
-	int rating = 99;
+	Rml::Core::String mouse_detector = "Mouse-move <em>Detector</em>.";
 
+	int rating = 99;
 
 	Invader delightful_invader{ "Delightful invader", "icon-invader" };
 
@@ -130,31 +131,49 @@ struct MyData {
 	};
 
 	std::vector<int> indices = { 1, 2, 3, 4, 5 };
+
+	std::vector<Rml::Core::Vector2f> positions;
+
+	void AddMousePos(Rml::Core::DataModelHandle model_handle, Rml::Core::Event& ev, const Rml::Core::VariantList& arguments)
+	{
+		positions.emplace_back(ev.GetParameter("mouse_x", 0.f), ev.GetParameter("mouse_y", 0.f));
+		model_handle.DirtyVariable("positions");
+	}
+
 } my_data;
 
-Rml::Core::DataModelHandle my_model;
 
+void ClearPositions(Rml::Core::DataModelHandle model_handle, Rml::Core::Event& ev, const Rml::Core::VariantList& arguments)
+{
+	my_data.positions.clear();
+	model_handle.DirtyVariable("positions");
+}
 
 void HasGoodRating(Rml::Core::Variant& variant) {
 	variant = int(my_data.rating > 50);
 }
 
+
+Rml::Core::DataModelHandle my_model;
+
+
 bool SetupDataBinding(Rml::Core::Context* context)
 {
-	my_model = context->CreateDataModel("my_model");
-	if (!my_model)
+	Rml::Core::DataModelConstructor constructor = context->CreateDataModel("my_model");
+	if (!constructor)
 		return false;
 
-	my_model.Bind("hello_world", &my_data.hello_world);
-	my_model.Bind("rating", &my_data.rating);
-	my_model.BindFunc("good_rating", &HasGoodRating);
-	my_model.BindFunc("great_rating", [](Rml::Core::Variant& variant) {
+	constructor.Bind("hello_world", &my_data.hello_world);
+	constructor.Bind("mouse_detector", &my_data.mouse_detector);
+	constructor.Bind("rating", &my_data.rating);
+	constructor.BindFunc("good_rating", &HasGoodRating);
+	constructor.BindFunc("great_rating", [](Rml::Core::Variant& variant) {
 		variant = int(my_data.rating > 80);
 	});
 
-	my_model.RegisterArray<std::vector<int>>();
+	constructor.RegisterArray<std::vector<int>>();
 
-	if(auto invader_handle = my_model.RegisterStruct<Invader>())
+	if(auto invader_handle = constructor.RegisterStruct<Invader>())
 	{
 		invader_handle.AddMember("name", &Invader::name);
 		invader_handle.AddMember("sprite", &Invader::sprite);
@@ -162,12 +181,26 @@ bool SetupDataBinding(Rml::Core::Context* context)
 		invader_handle.AddMemberFunc("color", &Invader::GetColor);
 	}
 
-	my_model.Bind("delightful_invader", &my_data.delightful_invader);
+	constructor.Bind("delightful_invader", &my_data.delightful_invader);
+
+	constructor.RegisterArray<std::vector<Invader>>();
+
+	constructor.Bind("indices", &my_data.indices);
+	constructor.Bind("invaders", &my_data.invaders);
+
+	if (auto vec2_handle = constructor.RegisterStruct<Rml::Core::Vector2f>())
+	{
+		vec2_handle.AddMember("x", &Rml::Core::Vector2f::x);
+		vec2_handle.AddMember("y", &Rml::Core::Vector2f::y);
+	}
+
+	constructor.RegisterArray<std::vector<Rml::Core::Vector2f>>();
+	constructor.Bind("positions", &my_data.positions);
 
-	my_model.RegisterArray<std::vector<Invader>>();
+	constructor.BindEventCallback("clear_positions", &ClearPositions);
+	constructor.BindEventCallback("add_mouse_pos", &MyData::AddMousePos, &my_data);
 
-	my_model.Bind("indices", &my_data.indices);
-	my_model.Bind("invaders", &my_data.invaders);
+	my_model = constructor.GetModelHandle();
 
 	return true;
 }

+ 0 - 6
Source/Controls/ElementFormControl.cpp

@@ -95,12 +95,6 @@ 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);
-	}
 }
 
 }

+ 3 - 3
Source/Core/Context.cpp

@@ -788,7 +788,7 @@ void Context::SetInstancer(ContextInstancer* _instancer)
 	instancer = _instancer;
 }
 
-DataModelHandle Context::CreateDataModel(const String& name)
+DataModelConstructor Context::CreateDataModel(const String& name)
 {
 	if (!data_type_register)
 		data_type_register = std::make_unique<DataTypeRegister>();
@@ -796,10 +796,10 @@ DataModelHandle Context::CreateDataModel(const String& name)
 	auto result = data_models.emplace(name, std::make_unique<DataModel>(data_type_register->GetTransformFuncRegister()));
 	bool inserted = result.second;
 	if (inserted)
-		return DataModelHandle(result.first->second.get(), data_type_register.get());
+		return DataModelConstructor(result.first->second.get(), data_type_register.get());
 
 	RMLUI_ERRORMSG("Data model name already exists.")
-	return DataModelHandle();
+	return DataModelConstructor();
 }
 
 // Internal callback for when an element is removed from the hierarchy.

+ 21 - 57
Source/Core/DataController.cpp

@@ -29,86 +29,50 @@
 #include "precompiled.h"
 #include "../../Include/RmlUi/Core/DataController.h"
 #include "../../Include/RmlUi/Core/DataModel.h"
+#include "DataParser.h"
+#include "EventSpecification.h"
 
 namespace Rml {
 namespace Core {
 
+
 DataController::DataController(Element* element) : attached_element(element->GetObserverPtr())
 {}
 
 DataController::~DataController()
 {}
-
-bool DataController::UpdateVariable(DataModel& model)
-{
-    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;
+Element* DataController::GetElement() const {
+	return attached_element.get();
 }
 
-DataControllerValue::DataControllerValue(DataModel& model, Element* element, const String& in_value_name) : DataController(element)
-{
-    DataAddress variable_address = model.ResolveAddress(in_value_name, element);
-
-    if (model.GetVariable(variable_address) && !variable_address.empty())
-    {
-        SetAddress(std::move(variable_address));
-    }
+bool DataController::IsValid() const {
+	return static_cast<bool>(attached_element);
 }
 
-bool DataControllerValue::UpdateValue(Element* element, Variant& value_inout)
-{
-    bool value_changed = false;
-
-    if (Variant* new_value = element->GetAttribute("value"))
-    {
-        if (*new_value != value_inout)
-        {
-            value_inout = *new_value;
-            value_changed = true;
-        }
-    }
-    
-    return value_changed;
-}
 
 
+DataControllers::DataControllers()
+{}
 
+DataControllers::~DataControllers()
+{}
 
-void DataControllers::Add(UniquePtr<DataController> controller) {
-    RMLUI_ASSERT(controller);
+void DataControllers::Add(DataControllerPtr controller) {
+	RMLUI_ASSERT(controller);
 
-    Element* element = controller->GetElement();
-    RMLUI_ASSERTMSG(element, "Invalid controller, make sure it is valid before adding");
+	Element* element = controller->GetElement();
+	RMLUI_ASSERTMSG(element, "Invalid controller, make sure it is valid before adding");
+	if (!element)
+		return;
 
-    bool inserted = controllers.emplace(element, std::move(controller)).second;
-    if (!inserted)
-    {
-        RMLUI_ERRORMSG("Cannot add multiple controllers to the same element.");
-    }
+	controllers.emplace(element, std::move(controller));
 }
 
-void DataControllers::DirtyElement(DataModel& model, Element* element)
+void DataControllers::OnElementRemove(Element* element)
 {
-    auto it = controllers.find(element);
-    if (it != controllers.end())
-    {
-        DataController& controller = *it->second;
-        if (controller.UpdateVariable(model))
-        {
-            model.DirtyVariable(controller.GetVariableName());
-        }
-    }
+	controllers.erase(element);
 }
 
+
 }
 }

+ 160 - 0
Source/Core/DataControllerDefault.cpp

@@ -0,0 +1,160 @@
+/*
+ * 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 "DataControllerDefault.h"
+#include "../../Include/RmlUi/Core/DataController.h"
+#include "../../Include/RmlUi/Core/DataModel.h"
+#include "DataParser.h"
+#include "EventSpecification.h"
+
+namespace Rml {
+namespace Core {
+
+
+
+DataControllerValue::DataControllerValue(Element* element) : DataController(element)
+{}
+
+DataControllerValue::~DataControllerValue()
+{
+	if (Element* element = GetElement())
+	{
+		element->RemoveEventListener(EventId::Change, this);
+	}
+}
+
+bool DataControllerValue::Initialize(DataModel& model, Element* element, const String& variable_name, const String& modifier)
+{
+	RMLUI_ASSERT(element);
+
+	DataAddress variable_address = model.ResolveAddress(variable_name, element);
+	if (variable_address.empty())
+		return false;
+
+	if (model.GetVariable(variable_address))
+		address = std::move(variable_address);
+	
+	element->AddEventListener(EventId::Change, this);
+
+	return true;
+}
+
+void DataControllerValue::ProcessEvent(Event& event)
+{
+	if (Element* element = GetElement())
+	{
+		const auto& parameters = event.GetParameters();
+		auto it = parameters.find("value");
+		if (it == parameters.end())
+		{
+			Log::Message(Log::LT_WARNING, "A 'change' event was received, but it did not contain a value. During processing of 'data-value' in %s", element->GetAddress().c_str());
+			return;
+		}
+
+		SetValue(it->second);
+	}
+}
+
+void DataControllerValue::Release()
+{
+	delete this;
+}
+
+void DataControllerValue::SetValue(const Variant& value)
+{
+	Element* element = GetElement();
+	if (!element)
+		return;
+
+	DataModel* model = element->GetDataModel();
+	if (!model)
+		return;
+
+	if (Variable variable = model->GetVariable(address))
+	{
+		variable.Set(value);
+		model->DirtyVariable(address.front().name);
+	}
+}
+
+
+DataControllerEvent::DataControllerEvent(Element* element) : DataController(element)
+{}
+
+DataControllerEvent::~DataControllerEvent()
+{
+	if (Element* element = GetElement())
+	{
+		if (id != EventId::Invalid)
+			element->RemoveEventListener(EventId::Mousemove, this);
+	}
+}
+
+bool DataControllerEvent::Initialize(DataModel& model, Element* element, const String& expression_str, const String& modifier)
+{
+	RMLUI_ASSERT(element);
+
+	expression = std::make_unique<DataExpression>(expression_str);
+	DataExpressionInterface interface(&model, element);
+
+	if (!expression->Parse(interface, true))
+		return false;
+
+	id = EventSpecificationInterface::GetIdOrInsert(modifier);
+	if (id == EventId::Invalid)
+	{
+		Log::Message(Log::LT_WARNING, "Event type '%s' could not be recognized, while adding 'data-event' to %s", modifier.c_str(), element->GetAddress().c_str());
+		return false;
+	}
+
+	element->AddEventListener(id, this);
+
+	return true;
+}
+
+void DataControllerEvent::ProcessEvent(Event& event)
+{
+	if (!expression)
+		return;
+
+	if (Element* element = GetElement())
+	{
+		DataExpressionInterface interface(element->GetDataModel(), element, &event);
+		Variant unused_value_out;
+		expression->Run(interface, unused_value_out);
+	}
+}
+
+void DataControllerEvent::Release()
+{
+	delete this;
+}
+
+}
+}

+ 90 - 0
Source/Core/DataControllerDefault.h

@@ -0,0 +1,90 @@
+/*
+ * 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 RMLUICOREDATACONTROLLERDEFAULT_H
+#define RMLUICOREDATACONTROLLERDEFAULT_H
+
+#include "../../Include/RmlUi/Core/Header.h"
+#include "../../Include/RmlUi/Core/Types.h"
+#include "../../Include/RmlUi/Core/EventListener.h"
+#include "../../Include/RmlUi/Core/DataVariable.h"
+#include "../../Include/RmlUi/Core/DataController.h"
+
+namespace Rml {
+namespace Core {
+
+class Element;
+class DataModel;
+class DataExpression;
+using DataExpressionPtr = UniquePtr<DataExpression>;
+
+
+class DataControllerValue final : public DataController, private EventListener {
+public:
+    DataControllerValue(Element* element);
+    ~DataControllerValue();
+
+    bool Initialize(DataModel& model, Element* element, const String& expression, const String& modifier) override;
+
+protected:
+    // Responds to 'Change' events.
+    void ProcessEvent(Event& event) override;
+    
+    // Delete this.
+    void Release() override;
+
+private:
+    void SetValue(const Variant& new_value);
+
+    DataAddress address;
+};
+
+
+class DataControllerEvent final : public DataController, private EventListener {
+public:
+    DataControllerEvent(Element* element);
+    ~DataControllerEvent();
+
+    bool Initialize(DataModel& model, Element* element, const String& expression, const String& modifier) override;
+
+protected:
+    // Responds to the event type specified in the attribute modifier.
+    void ProcessEvent(Event& event) override;
+
+    // Delete this.
+    void Release() override;
+
+private:
+    EventId id = EventId::Invalid;
+    DataExpressionPtr expression;
+};
+
+}
+}
+
+#endif

+ 76 - 9
Source/Core/DataModel.cpp

@@ -74,8 +74,41 @@ static DataAddress ParseAddress(const String& address_str)
 	return address;
 };
 
+// Returns an error string on error, or nullptr on success.
+static const char* LegalVariableName(const String& name)
+{
+	static SmallUnorderedSet<String> reserved_names{ "it", "ev", "true", "false", "size" };
+	
+	if (name.empty())
+		return "Name cannot be empty.";
+	
+	const String name_lower = StringUtilities::ToLower(name);
+
+	const char first = name_lower.front();
+	if (!(first >= 'a' && first <= 'z'))
+		return "First character must be 'a-z' or 'A-Z'.";
+
+	for (const char c : name_lower)
+	{
+		if (!(c == '_' || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')))
+			return "Name must strictly contain characters a-z, A-Z, 0-9 and under_score.";
+	}
+
+	if (reserved_names.count(name_lower) == 1)
+		return "Name is reserved.";
+
+	return nullptr;
+}
+
 bool DataModel::BindVariable(const String& name, Variable variable)
 {
+	const char* name_error_str = LegalVariableName(name);
+	if (name_error_str)
+	{
+		Log::Message(Log::LT_WARNING, "Could not bind data variable '%s'. %s", name.c_str(), name_error_str);
+		return false;
+	}
+
 	if (!variable)
 	{
 		Log::Message(Log::LT_WARNING, "Could not bind variable '%s' to data model, data type not registered.", name.c_str());
@@ -92,6 +125,31 @@ bool DataModel::BindVariable(const String& name, Variable variable)
 	return true;
 }
 
+bool DataModel::BindEventCallback(const String& name, DataEventFunc event_func)
+{
+	const char* name_error_str = LegalVariableName(name);
+	if (name_error_str)
+	{
+		Log::Message(Log::LT_WARNING, "Could not bind data event callback '%s'. %s", name.c_str(), name_error_str);
+		return false;
+	}
+
+	if (!event_func)
+	{
+		Log::Message(Log::LT_WARNING, "Could not bind data event callback '%s' to data model, empty function provided.", name.c_str());
+		return false;
+	}
+
+	bool inserted = event_callbacks.emplace(name, std::move(event_func)).second;
+	if (!inserted)
+	{
+		Log::Message(Log::LT_WARNING, "Data event callback with name '%s' already exists.", name.c_str());
+		return false;
+	}
+
+	return true;
+}
+
 bool DataModel::InsertAlias(Element* element, const String& alias_name, DataAddress replace_with_address)
 {
 	if (replace_with_address.empty() || replace_with_address.front().name.empty())
@@ -187,14 +245,27 @@ Variable DataModel::GetVariable(const DataAddress& address) const
 	return variable;
 }
 
+const DataEventFunc* DataModel::GetEventCallback(const String& name)
+{
+	auto it = event_callbacks.find(name);
+	if (it == event_callbacks.end())
+	{
+		Log::Message(Log::LT_WARNING, "Could not find data event callback '%s' in data model.", name.c_str());
+		return nullptr;
+	}
+
+	return &it->second;
+}
+
 void DataModel::DirtyVariable(const String& variable_name)
 {
 	RMLUI_ASSERTMSG(variables.count(variable_name) == 1, "Variable name not found among added variables.");
-	dirty_variables.insert(variable_name);
+	dirty_variables.emplace(variable_name);
 }
 
-bool DataModel::IsVariableDirty(const String& variable_name) const {
-	return (dirty_variables.count(variable_name) == 1);
+bool DataModel::IsVariableDirty(const String& variable_name) const
+{
+	return dirty_variables.count(variable_name) == 1;
 }
 
 bool DataModel::CallTransform(const String& name, Variant& inout_result, const VariantList& arguments) const
@@ -208,11 +279,7 @@ void DataModel::OnElementRemove(Element* element)
 {
 	EraseAliases(element);
 	views.OnElementRemove(element);
-}
-
-void DataModel::DirtyController(Element* element) 
-{
-	controllers.DirtyElement(*this, element);
+	controllers.OnElementRemove(element);
 }
 
 bool DataModel::Update() 
@@ -248,7 +315,7 @@ static struct TestDataVariables {
 		DataModel model;
 		DataTypeRegister types;
 
-		DataModelHandle handle(&model, &types);
+		DataModelConstructor handle(&model, &types);
 
 		{
 			handle.RegisterArray<IntVector>();

+ 180 - 45
Source/Core/DataParser.cpp

@@ -64,26 +64,28 @@ class DataParser;
 		S-  Pop stack S (returns the popped value).
 */
 enum class Instruction { // Assignment (register/stack) = Read (register R/L/C, instruction data D, or stack)
-	Push      = 'P',     //      S+ = R
-	Pop       = 'o',     // <R/L/C> = S-  (D determines R/L/C)
-	Literal   = 'D',     //       R = D
-	Variable  = 'V',     //       R = DataModel.GetVariable(D)  (D is an index into the variable address list)
-	Add       = '+',     //       R = L + R
-	Subtract  = '-',     //       R = L - R
-	Multiply  = '*',     //       R = L * R
-	Divide    = '/',     //       R = L / R
-	Not       = '!',     //       R = !R
-	And       = '&',     //       R = L && R
-	Or        = '|',     //       R = L || R
-	Less      = '<',     //       R = L < R
-	LessEq    = 'L',     //       R = L <= R
-	Greater   = '>',     //       R = L > R
-	GreaterEq = 'G',     //       R = L >= R
-	Equal     = '=',     //       R = L == R
-	NotEqual  = 'N',     //       R = L != R
-	Ternary   = '?',     //       R = L ? C : R
-	Arguments = 'a',     //      A+ = S-  (Repeated D times, where D gives the num. arguments)
-	Function  = 'F',     //       R = DataModel.Execute( D, R, A ); A.Clear();  (D determines function name, R the input value, A the arguments)
+	Push         = 'P',     //      S+ = R
+	Pop          = 'o',     // <R/L/C> = S-  (D determines R/L/C)
+	Literal      = 'D',     //       R = D
+	Variable     = 'V',     //       R = DataModel.GetVariable(D)  (D is an index into the variable address list)
+	Add          = '+',     //       R = L + R
+	Subtract     = '-',     //       R = L - R
+	Multiply     = '*',     //       R = L * R
+	Divide       = '/',     //       R = L / R
+	Not          = '!',     //       R = !R
+	And          = '&',     //       R = L && R
+	Or           = '|',     //       R = L || R
+	Less         = '<',     //       R = L < R
+	LessEq       = 'L',     //       R = L <= R
+	Greater      = '>',     //       R = L > R
+	GreaterEq    = 'G',     //       R = L >= R
+	Equal        = '=',     //       R = L == R
+	NotEqual     = 'N',     //       R = L != R
+	Ternary      = '?',     //       R = L ? C : R
+	Arguments    = 'a',     //      A+ = S-  (Repeated D times, where D gives the num. arguments)
+	TransformFnc = 'T',     //       R = DataModel.Execute( D, R, A ); A.Clear();  (D determines function name, R the input value, A the arguments)
+	EventFnc     = 'E',     //       DataModel.EventCallback(D, A); A.Clear();
+	Assign       = 'A',     //       DataModel.SetVariable(D, R)
 };
 enum class Register {
 	R,
@@ -97,6 +99,7 @@ struct InstructionData {
 };
 
 namespace Parse {
+	static void Assignment(DataParser& parser);
 	static void Expression(DataParser& parser);
 };
 
@@ -156,7 +159,7 @@ public:
 		Expected(String(1, '\'') + expected + '\'');
 	}
 
-	bool Parse() 
+	bool Parse(bool is_assignment_expression)
 	{
 		program.clear();
 		variable_addresses.clear();
@@ -167,7 +170,11 @@ public:
 			reached_end = true;
 
 		SkipWhitespace();
-		Parse::Expression(*this);
+
+		if (is_assignment_expression)
+			Parse::Assignment(*this);
+		else
+			Parse::Expression(*this);
 		
 		if (!reached_end) {
 			parse_error = true;
@@ -193,8 +200,8 @@ public:
 	void Emit(Instruction instruction, Variant data = Variant())
 	{
 		RMLUI_ASSERTMSG(instruction != Instruction::Push && instruction != Instruction::Pop &&
-			instruction != Instruction::Arguments && instruction != Instruction::Variable,
-			"Use the Push(), Pop(), Arguments(), and Variable() procedures for stack manipulation and variable instructions.");
+			instruction != Instruction::Arguments && instruction != Instruction::Variable && instruction != Instruction::Assign,
+			"Use the Push(), Pop(), Arguments(), Variable(), and Assign() procedures for stack manipulation and variable instructions.");
 		program.push_back(InstructionData{ instruction, std::move(data) });
 	}
 	void Push() {
@@ -218,6 +225,15 @@ public:
 		program.push_back(InstructionData{ Instruction::Arguments, Variant(int(num_arguments)) });
 	}
 	void Variable(const String& name) {
+		VariableGetSet(name, false);
+	}
+	void Assign(const String& name) {
+		VariableGetSet(name, true);
+	}
+
+private:
+	void VariableGetSet(const String& name, bool is_assignment)
+	{
 		DataAddress address = expression_interface.ParseAddress(name);
 		if (address.empty()) {
 			Error(CreateString(name.size() + 50, "Could not find data variable with name '%s'.", name.c_str()));
@@ -225,10 +241,9 @@ public:
 		}
 		int index = int(variable_addresses.size());
 		variable_addresses.push_back(std::move(address));
-		program.push_back(InstructionData{ Instruction::Variable, Variant(int(index)) });
+		program.push_back(InstructionData{ is_assignment ? Instruction::Assign : Instruction::Variable, Variant(int(index)) });
 	}
 
-private:
 	const String expression;
 	DataExpressionInterface expression_interface;
 
@@ -246,6 +261,8 @@ private:
 namespace Parse {
 
 	// Forward declare all parse functions.
+	static void Assignment(DataParser& parser);
+
 	static void Expression(DataParser& parser);
 	static void Factor(DataParser& parser);
 	static void Term(DataParser& parser);
@@ -268,7 +285,7 @@ namespace Parse {
 	static void NotEqual(DataParser& parser);
 
 	static void Ternary(DataParser& parser);
-	static void Function(DataParser& parser);
+	static void Function(DataParser& parser, Instruction function_type, const String& name);
 
 	// Helper functions
 	static bool IsVariableCharacter(char c, bool is_first_character)
@@ -319,6 +336,49 @@ namespace Parse {
 	}
 
 	// Parser functions
+	static void Assignment(DataParser& parser)
+	{
+		bool looping = true;
+		while (looping)
+		{
+			if (parser.Look() != '\0')
+			{
+				const String variable_name = VariableName(parser);
+				if (variable_name.empty()) {
+					parser.Error("Expected a variable for assignment but got an empty name.");
+					return;
+				}
+
+				const char c = parser.Look();
+				if (c == '=')
+				{
+					parser.Match('=');
+					Expression(parser);
+					parser.Assign(variable_name);
+				}
+				else if (c == '(' || c == ';' || c == '\0')
+				{
+					Function(parser, Instruction::EventFnc, variable_name);
+				}
+				else
+				{
+					parser.Expected("one of  = ; (  or end of string");
+					return;
+				}
+			}
+
+			const char c = parser.Look();
+			if (c == ';')
+				parser.Match(';');
+			else if (c == '\0')
+				looping = false;
+			else
+			{
+				parser.Expected("';' or end of string");
+				looping = false;
+			}
+		}
+	}
 	static void Expression(DataParser& parser)
 	{
 		Term(parser);
@@ -339,7 +399,13 @@ namespace Parse {
 				else
 				{
 					parser.SkipWhitespace();
-					Function(parser);
+					const String fnc_name = VariableName(parser);
+					if (fnc_name.empty()) {
+						parser.Error("Expected a transform function name but got an empty name.");
+						return;
+					}
+
+					Function(parser, Instruction::TransformFnc, fnc_name);
 				}
 			}
 			break;
@@ -601,15 +667,11 @@ namespace Parse {
 		parser.Pop(Register::L);
 		parser.Emit(Instruction::Ternary);
 	}
-	static void Function(DataParser& parser)
+	static void Function(DataParser& parser, Instruction function_type, const String& func_name)
 	{
-		// We already matched '|' during expression
-		String name = VariableName(parser);
-		if (name.empty()) {
-			parser.Error("Expected a transform name but got an empty name.");
-			return;
-		}
+		RMLUI_ASSERT(function_type == Instruction::TransformFnc || function_type == Instruction::EventFnc);
 
+		// We already matched the variable name (and '|' for transform functions)
 		if (parser.Look() == '(')
 		{
 			int num_arguments = 0;
@@ -647,7 +709,7 @@ namespace Parse {
 			parser.SkipWhitespace();
 		}
 
-		parser.Emit(Instruction::Function, Variant(name));
+		parser.Emit(function_type, Variant(func_name));
 	}
 
 
@@ -820,7 +882,7 @@ private:
 			}
 		}
 		break;
-		case Instruction::Function:
+		case Instruction::TransformFnc:
 		{
 			const String function_name = data.Get<String>();
 			
@@ -839,6 +901,37 @@ private:
 			arguments.clear();
 		}
 		break;
+		case Instruction::EventFnc:
+		{
+			const String function_name = data.Get<String>();
+
+			if (!expression_interface.EventCallback(function_name, arguments))
+			{
+				String arguments_str;
+				for (size_t i = 0; i < arguments.size(); i++)
+				{
+					arguments_str += arguments[i].Get<String>();
+					if (i < arguments.size() - 1)
+						arguments_str += ", ";
+				}
+				Error(CreateString(50 + function_name.size() + arguments_str.size(), "Failed to execute event callback: %s(%s)", function_name.c_str(), arguments_str.c_str()));
+			}
+
+			arguments.clear();
+		}
+		break;
+		case Instruction::Assign:
+		{
+			size_t variable_index = size_t(data.Get<int>(-1));
+			if (variable_index < addresses.size())
+			{
+				if (!expression_interface.SetValue(addresses[variable_index], R))
+					return Error("Could not assign to variable.");
+			}
+			else
+				return Error("Variable address not found.");
+		}
+		break;
 		default:
 			RMLUI_ERRORMSG("Instruction not implemented."); break;
 		}
@@ -853,7 +946,7 @@ private:
 struct TestParser {
 	TestParser() : model(type_register.GetTransformFuncRegister())
 	{
-		DataModelHandle handle(&model, &type_register);
+		DataModelConstructor handle(&model, &type_register);
 		handle.Bind("color_name", &color_name);
 		handle.BindFunc("color_value", [this](Rml::Core::Variant& variant) {
 			variant = ToString(color_value);
@@ -879,7 +972,7 @@ struct TestParser {
 		DataExpressionInterface interface(&model, nullptr);
 
 		DataParser parser(expression, interface);
-		if (parser.Parse())
+		if (parser.Parse(false))
 		{
 			Program program = parser.ReleaseProgram();
 			AddressList addresses = parser.ReleaseAddresses();
@@ -907,7 +1000,7 @@ DataExpression::~DataExpression()
 {
 }
 
-bool DataExpression::Parse(const DataExpressionInterface& expression_interface)
+bool DataExpression::Parse(const DataExpressionInterface& expression_interface, bool is_assignment_expression)
 {
 	// @todo: Remove, debugging only
 	static TestParser test_parser;
@@ -917,7 +1010,7 @@ bool DataExpression::Parse(const DataExpressionInterface& expression_interface)
 	//  5. Add tests
 
 	DataParser parser(expression, expression_interface);
-	if (!parser.Parse())
+	if (!parser.Parse(is_assignment_expression))
 		return false;
 
 	program = parser.ReleaseProgram();
@@ -949,16 +1042,44 @@ StringList DataExpression::GetVariableNameList() const
 	return list;
 }
 
-DataExpressionInterface::DataExpressionInterface(DataModel* data_model, Element* element) : data_model(data_model), element(element)
+DataExpressionInterface::DataExpressionInterface(DataModel* data_model, Element* element, Event* event) : data_model(data_model), element(element), event(event)
 {}
 
-DataAddress DataExpressionInterface::ParseAddress(const String& address_str) const {
+DataAddress DataExpressionInterface::ParseAddress(const String& address_str) const
+{
+	if (address_str.size() >= 4 && address_str[0] == 'e' && address_str[1] == 'v' && address_str[2] == '.')
+		return DataAddress{ AddressEntry("ev"), AddressEntry(address_str.substr(3)) };
+
 	return data_model ? data_model->ResolveAddress(address_str, element) : DataAddress();
 }
-Variant DataExpressionInterface::GetValue(const DataAddress& address) const {
+Variant DataExpressionInterface::GetValue(const DataAddress& address) const
+{
 	Variant result;
-	if (data_model)
+	if(event && address.size() == 2 && address.front().name == "ev")
+	{
+		auto& parameters = event->GetParameters();
+		auto it = parameters.find(address.back().name);
+		if (it != parameters.end())
+			result = it->second;
+	}
+	else if (data_model)
+	{
 		data_model->GetValue(address, result);
+	}
+	return result;
+}
+
+bool DataExpressionInterface::SetValue(const DataAddress& address, const Variant& value) const
+{
+	bool result = false;
+	if (data_model && !address.empty())
+	{
+		if (Variable variable = data_model->GetVariable(address))
+			result = variable.Set(value);
+
+		if (result)
+			data_model->DirtyVariable(address.front().name);
+	}
 	return result;
 }
 
@@ -967,5 +1088,19 @@ bool DataExpressionInterface::CallTransform(const String& name, Variant& inout_v
 	return data_model ? data_model->CallTransform(name, inout_variant, arguments) : false;
 }
 
+bool DataExpressionInterface::EventCallback(const String& name, const VariantList& arguments)
+{
+	if (!data_model || !event)
+		return false;
+
+	const DataEventFunc* func = data_model->GetEventCallback(name);
+	if (!func || !*func)
+		return false;
+
+	DataModelHandle handle(data_model);
+	func->operator()(handle, *event, arguments);
+	return true;
+}
+
 }
 }

+ 5 - 2
Source/Core/DataParser.h

@@ -45,15 +45,18 @@ using AddressList = std::vector<DataAddress>;
 class DataExpressionInterface {
 public:
     DataExpressionInterface() = default;
-    DataExpressionInterface(DataModel* data_model, Element* element);
+    DataExpressionInterface(DataModel* data_model, Element* element, Event* event = nullptr);
 
     DataAddress ParseAddress(const String& address_str) const;
     Variant GetValue(const DataAddress& address) const;
+    bool SetValue(const DataAddress& address, const Variant& value) const;
     bool CallTransform(const String& name, Variant& inout_result, const VariantList& arguments);
+    bool EventCallback(const String& name, const VariantList& arguments);
 
 private:
     DataModel* data_model = nullptr;
     Element* element = nullptr;
+    Event* event = nullptr;
 };
 
 class DataExpression {
@@ -61,7 +64,7 @@ public:
     DataExpression(String expression);
     ~DataExpression();
 
-    bool Parse(const DataExpressionInterface& expression_interface);
+    bool Parse(const DataExpressionInterface& expression_interface, bool is_assignment_expression);
 
     bool Run(const DataExpressionInterface& expression_interface, Variant& out_value);
 

+ 18 - 0
Source/Core/DataVariable.cpp

@@ -75,5 +75,23 @@ DataTypeRegister::DataTypeRegister()
 DataTypeRegister::~DataTypeRegister()
 {}
 
+
+class ArraySizeDefinition final : public VariableDefinition {
+public:
+    ArraySizeDefinition() : VariableDefinition(VariableType::Scalar) {}
+
+    bool Get(void* ptr, Variant& variant) override
+    {
+        variant = static_cast<int>(reinterpret_cast<intptr_t>(ptr));
+        return true;
+    }
+};
+
+VariableDefinition* DataTypeRegister::GetArraySizeDefinition()
+{
+	static ArraySizeDefinition size_definition;
+	return &size_definition;
+}
+
 }
 }

+ 4 - 8
Source/Core/DataView.cpp

@@ -33,7 +33,8 @@
 namespace Rml {
 namespace Core {
 
-DataView::~DataView() {}
+DataView::~DataView()
+{}
 
 Element* DataView::GetElement() const
 {
@@ -48,7 +49,7 @@ int DataView::GetElementDepth() const {
 }
 
 bool DataView::IsValid() const {
-	return (bool)attached_element;
+	return static_cast<bool>(attached_element);
 }
 
 DataView::DataView(Element* element) : attached_element(element->GetObserverPtr()), element_depth(0) {
@@ -59,11 +60,6 @@ DataView::DataView(Element* element) : attached_element(element->GetObserverPtr(
 	}
 }
 
-void DataView::Release()
-{
-	delete this;
-}
-
 
 DataViews::DataViews()
 {}
@@ -90,7 +86,7 @@ void DataViews::OnElementRemove(Element* element)
 	}
 }
 
-bool DataViews::Update(DataModel& model, const SmallUnorderedSet< String >& dirty_variables)
+bool DataViews::Update(DataModel& model, const DirtyVariables& dirty_variables)
 {
 	bool result = false;
 

+ 24 - 30
Source/Core/DataViewDefault.cpp

@@ -36,19 +36,19 @@ namespace Rml {
 namespace Core {
 
 
-DataViewCommon::DataViewCommon(Element* element) : DataView(element)
-{}
-
-DataViewCommon::~DataViewCommon()
+DataViewCommon::DataViewCommon(Element* element, String override_modifier) : DataView(element), modifier(override_modifier)
 {}
 
 bool DataViewCommon::Initialize(DataModel& model, Element* element, const String& expression_str, const String& in_modifier)
 {
-	modifier = in_modifier;
+	// The modifier can be overriden in the constructor
+	if (modifier.empty())
+		modifier = in_modifier;
+
 	expression = std::make_unique<DataExpression>(expression_str);
 	DataExpressionInterface interface(&model, element);
 
-	bool result = expression->Parse(interface);
+	bool result = expression->Parse(interface, false);
 	return result;
 }
 
@@ -66,12 +66,16 @@ DataExpression& DataViewCommon::GetExpression() {
 	return *expression;
 }
 
+void DataViewCommon::Release()
+{
+	delete this;
+}
 
 
 DataViewAttribute::DataViewAttribute(Element* element) : DataViewCommon(element)
 {}
 
-DataViewAttribute::~DataViewAttribute()
+DataViewAttribute::DataViewAttribute(Element * element, String override_attribute) : DataViewCommon(element, std::move(override_attribute))
 {}
 
 bool DataViewAttribute::Update(DataModel& model)
@@ -97,13 +101,12 @@ bool DataViewAttribute::Update(DataModel& model)
 }
 
 
-DataViewStyle::DataViewStyle(Element* element) : DataViewCommon(element)
+DataViewValue::DataViewValue(Element* element) : DataViewAttribute(element, "value")
 {}
 
-DataViewStyle::~DataViewStyle()
-{
-}
 
+DataViewStyle::DataViewStyle(Element* element) : DataViewCommon(element)
+{}
 
 bool DataViewStyle::Update(DataModel& model)
 {
@@ -130,9 +133,6 @@ bool DataViewStyle::Update(DataModel& model)
 DataViewClass::DataViewClass(Element* element) : DataViewCommon(element)
 {}
 
-DataViewClass::~DataViewClass()
-{}
-
 bool DataViewClass::Update(DataModel& model)
 {
 	const String& class_name = GetModifier();
@@ -158,9 +158,6 @@ bool DataViewClass::Update(DataModel& model)
 DataViewRml::DataViewRml(Element* element) : DataViewCommon(element)
 {}
 
-DataViewRml::~DataViewRml()
-{}
-
 bool DataViewRml::Update(DataModel & model)
 {
 	bool result = false;
@@ -185,10 +182,6 @@ bool DataViewRml::Update(DataModel & model)
 DataViewIf::DataViewIf(Element* element) : DataViewCommon(element)
 {}
 
-DataViewIf::~DataViewIf()
-{}
-
-
 bool DataViewIf::Update(DataModel& model)
 {
 	bool result = false;
@@ -217,11 +210,6 @@ bool DataViewIf::Update(DataModel& model)
 
 
 DataViewText::DataViewText(Element* element) : DataView(element)
-{
-
-}
-
-DataViewText::~DataViewText()
 {}
 
 bool DataViewText::Initialize(DataModel& model, Element* element, const String& RMLUI_UNUSED_PARAMETER(expression), const String& RMLUI_UNUSED_PARAMETER(modifier))
@@ -255,7 +243,7 @@ bool DataViewText::Initialize(DataModel& model, Element* element, const String&
 		entry.index = text.size();
 		entry.data_expression = std::make_unique<DataExpression>(String(in_text.begin() + begin_name, in_text.begin() + end_name));
 
-		if (entry.data_expression->Parse(expression_interface))
+		if (entry.data_expression->Parse(expression_interface, false))
 			data_entries.push_back(std::move(entry));
 
 		previous_close_brackets = end_name + 2;
@@ -330,6 +318,11 @@ StringList DataViewText::GetVariableNameList() const
 	return full_list;
 }
 
+void DataViewText::Release()
+{
+	delete this;
+}
+
 String DataViewText::BuildText() const
 {
 	size_t reserve_size = text.size();
@@ -359,9 +352,6 @@ String DataViewText::BuildText() const
 DataViewFor::DataViewFor(Element* element) : DataView(element)
 {}
 
-DataViewFor::~DataViewFor()
-{}
-
 bool DataViewFor::Initialize(DataModel& model, Element* element, const String& in_expression, const String& in_rml_content)
 {
 	rml_contents = in_rml_content;
@@ -442,6 +432,10 @@ StringList DataViewFor::GetVariableNameList() const {
 	return StringList{ container_address.front().name };
 }
 
+void DataViewFor::Release()
+{
+	delete this;
+}
 
 }
 }

+ 18 - 10
Source/Core/DataViewDefault.h

@@ -43,8 +43,7 @@ using DataExpressionPtr = UniquePtr<DataExpression>;
 
 class DataViewCommon : public DataView {
 public:
-	DataViewCommon(Element* element);
-	~DataViewCommon();
+	DataViewCommon(Element* element, String override_modifier = String());
 
 	bool Initialize(DataModel& model, Element* element, const String& expression, const String& modifier) override;
 
@@ -54,25 +53,33 @@ protected:
 	const String& GetModifier() const;
 	DataExpression& GetExpression();
 
+	// Delete this
+	void Release() override;
+
 private:
 	String modifier;
 	DataExpressionPtr expression;
 };
 
 
-class DataViewAttribute final : public DataViewCommon {
+class DataViewAttribute : public DataViewCommon {
 public:
 	DataViewAttribute(Element* element);
-	~DataViewAttribute();
+	DataViewAttribute(Element* element, String override_attribute);
 
 	bool Update(DataModel& model) override;
 };
 
 
+class DataViewValue final : public DataViewAttribute {
+public:
+	DataViewValue(Element* element);
+};
+
+
 class DataViewStyle final : public DataViewCommon {
 public:
 	DataViewStyle(Element* element);
-	~DataViewStyle();
 
 	bool Update(DataModel& model) override;
 };
@@ -81,7 +88,6 @@ public:
 class DataViewClass final : public DataViewCommon {
 public:
 	DataViewClass(Element* element);
-	~DataViewClass();
 
 	bool Update(DataModel& model) override;
 };
@@ -90,7 +96,6 @@ public:
 class DataViewRml final : public DataViewCommon {
 public:
 	DataViewRml(Element* element);
-	~DataViewRml();
 
 	bool Update(DataModel& model) override;
 
@@ -102,7 +107,6 @@ private:
 class DataViewIf final : public DataViewCommon {
 public:
 	DataViewIf(Element* element);
-	~DataViewIf();
 
 	bool Update(DataModel& model) override;
 };
@@ -111,13 +115,15 @@ public:
 class DataViewText final : public DataView {
 public:
 	DataViewText(Element* in_element);
-	~DataViewText();
 
 	bool Initialize(DataModel& model, Element* element, const String& expression, const String& modifier) override;
 
 	bool Update(DataModel& model) override;
 	StringList GetVariableNameList() const override;
 
+protected:
+	void Release() override;
+
 private:
 	String BuildText() const;
 
@@ -135,7 +141,6 @@ private:
 class DataViewFor final : public DataView {
 public:
 	DataViewFor(Element* element);
-	~DataViewFor();
 
 	bool Initialize(DataModel& model, Element* element, const String& expression, const String& inner_rml) override;
 
@@ -143,6 +148,9 @@ public:
 
 	StringList GetVariableNameList() const override;
 
+protected:
+	void Release() override;
+
 private:
 	DataAddress container_address;
 	String iterator_name;

+ 9 - 12
Source/Core/ElementUtilities.cpp

@@ -403,13 +403,13 @@ void ElementUtilities::ApplyDataViewsControllers(Element* element)
 
 				const String data_expression = attribute.second.Get<String>();
 
+				String modifier;
+				const size_t label_offset = sizeof("data") + type_name.size() + 1;
+				if (label_offset < name.size())
+					modifier = name.substr(label_offset);
+
 				if (DataViewPtr view = Factory::InstanceDataView(type_name, element, false))
 				{
-					String modifier;
-					const size_t label_offset = sizeof("data") + type_name.size() + 1;
-					if (label_offset < name.size())
-						modifier = name.substr(label_offset);
-
 					bool success = view->Initialize(*data_model, element, data_expression, modifier);
 					if (success)
 						data_model->AddView(std::move(view));
@@ -417,16 +417,14 @@ void ElementUtilities::ApplyDataViewsControllers(Element* element)
 						Log::Message(Log::LT_WARNING, "Could not add data-%s view to element: %s", type_name.c_str(), element->GetAddress().c_str());
 				}
 
-				if (type_name == "value")
+				if (DataControllerPtr controller = Factory::InstanceDataController(type_name, element))
 				{
-					// TODO: Make the same abstraction for controllers as for views (or maybe make them into views instead if possible?)
-					auto controller = std::make_unique<DataControllerValue>(*data_model, element, data_expression);
-					if (controller)
+					bool success = controller->Initialize(*data_model, element, data_expression, modifier);
+					if (success)
 						data_model->AddController(std::move(controller));
 					else
-						Log::Message(Log::LT_WARNING, "Could not add data-value controller to element: %s", element->GetAddress().c_str());
+						Log::Message(Log::LT_WARNING, "Could not add data-%s controller to element: %s", type_name.c_str(), element->GetAddress().c_str());
 				}
-
 			}
 		}
 
@@ -450,7 +448,6 @@ bool ElementUtilities::ApplyStructuralDataViews(Element* element, const String&
 
 			if (name.size() > 5 && name[0] == 'd' && name[1] == 'a' && name[2] == 't' && name[3] == 'a' && name[4] == '-')
 			{
-
 				const size_t data_type_end = name.find('-', 5);
 				const size_t count = (data_type_end == String::npos ? String::npos : data_type_end - 5);
 				const String view_type = name.substr(5, count);

+ 28 - 0
Source/Core/Factory.cpp

@@ -31,6 +31,7 @@
 #include "../../Include/RmlUi/Core.h"
 
 #include "ContextInstancerDefault.h"
+#include "DataControllerDefault.h"
 #include "DataViewDefault.h"
 #include "DecoratorTiledBoxInstancer.h"
 #include "DecoratorTiledHorizontalInstancer.h"
@@ -76,6 +77,10 @@ static FontEffectInstancerMap font_effect_instancers;
 using DataViewInstancerMap = UnorderedMap< String, DataViewInstancer* >;
 static DataViewInstancerMap data_view_instancers;
 
+// Data controller instancers.
+using DataControllerInstancerMap = UnorderedMap< String, DataControllerInstancer* >;
+static DataControllerInstancerMap data_controller_instancers;
+
 // Structural data view instancers.
 using StructuralDataViewInstancerMap = SmallUnorderedMap< String, DataViewInstancer* >;
 static StructuralDataViewInstancerMap structural_data_view_instancers;
@@ -124,6 +129,10 @@ struct DefaultInstancers {
 	Ptr<DataViewInstancer> data_view_rml       = std::make_unique<DataViewInstancerDefault< DataViewRml >>();
 	Ptr<DataViewInstancer> data_view_style     = std::make_unique<DataViewInstancerDefault< DataViewStyle >>();
 	Ptr<DataViewInstancer> data_view_text      = std::make_unique<DataViewInstancerDefault< DataViewText >>();
+	Ptr<DataViewInstancer> data_view_value     = std::make_unique<DataViewInstancerDefault< DataViewValue >>();
+
+	Ptr<DataControllerInstancer> data_controller_value = std::make_unique<DataControllerInstancerDefault< DataControllerValue >>();
+	Ptr<DataControllerInstancer> data_controller_event = std::make_unique<DataControllerInstancerDefault< DataControllerEvent >>();
 
 	Ptr<DataViewInstancer> structural_data_view_for = std::make_unique<DataViewInstancerDefault< DataViewFor >>();
 };
@@ -195,6 +204,10 @@ bool Factory::Initialise()
 	RegisterDataViewInstancer(default_instancers->data_view_rml.get(),       "rml", false);
 	RegisterDataViewInstancer(default_instancers->data_view_style.get(),     "style", false);
 	RegisterDataViewInstancer(default_instancers->data_view_text.get(),      "text", false);
+	RegisterDataViewInstancer(default_instancers->data_view_value.get(),     "value", false);
+
+	RegisterDataControllerInstancer(default_instancers->data_controller_value.get(), "value");
+	RegisterDataControllerInstancer(default_instancers->data_controller_event.get(), "event");
 
 	RegisterDataViewInstancer(default_instancers->structural_data_view_for.get(), "for", true);
 
@@ -546,6 +559,13 @@ void Factory::RegisterDataViewInstancer(DataViewInstancer* instancer, const Stri
 		Log::Message(Log::LT_WARNING, "Could not register data view instancer '%s'. The given name is already registered.", name.c_str());
 }
 
+void Factory::RegisterDataControllerInstancer(DataControllerInstancer* instancer, const String& name)
+{
+	bool inserted = data_controller_instancers.emplace(name, instancer).second;
+	if (!inserted)
+		Log::Message(Log::LT_WARNING, "Could not register data controller instancer '%s'. The given name is already registered.", name.c_str());
+}
+
 DataViewPtr Factory::InstanceDataView(const String& type_name, Element* element, bool is_structural_view)
 {
 	RMLUI_ASSERT(element);
@@ -565,5 +585,13 @@ DataViewPtr Factory::InstanceDataView(const String& type_name, Element* element,
 	return nullptr;
 }
 
+DataControllerPtr Factory::InstanceDataController(const String& type_name, Element* element)
+{
+	auto it = data_controller_instancers.find(type_name);
+	if (it != data_controller_instancers.end())
+		return it->second->InstanceController(element);
+	return DataControllerPtr();
+}
+
 }
 }