Browse Source

Add enum support to variants and data bindings (#445)


Co-authored-by: Michael Ragazzon <[email protected]>
Maximilian Stark 2 years ago
parent
commit
33dd587c75

+ 1 - 0
.gitignore

@@ -30,3 +30,4 @@
 Win32/
 x64/
 CMakeSettings.json
+compile_commands.json

+ 2 - 1
Include/RmlUi/Core/DataTypeRegister.h

@@ -39,7 +39,8 @@ namespace Rml {
 
 template <typename T>
 struct is_builtin_data_scalar {
-	static constexpr bool value = std::is_arithmetic<T>::value || std::is_same<typename std::remove_const<T>::type, String>::value;
+	static constexpr bool value =
+		std::is_arithmetic<T>::value || std::is_enum<T>::value || std::is_same<typename std::remove_const<T>::type, String>::value;
 };
 
 class RMLUICORE_API TransformFuncRegister {

+ 12 - 5
Include/RmlUi/Core/Variant.h

@@ -93,18 +93,22 @@ public:
 
 	inline Type GetType() const;
 
-	/// Templatised data accessor. TypeConverters will be used to attempt to convert from the
-	/// internal representation to the requested representation.
+	/// Templatised data accessor. TypeConverters will be used to attempt to convert from the internal representation to
+	/// the requested representation.
 	/// @param[in] default_value The value returned if the conversion failed.
 	/// @return Data in the requested type.
 	template <typename T>
 	T Get(T default_value = T()) const;
 
-	/// Templatised data accessor. TypeConverters will be used to attempt to convert from the
-	/// internal representation to the requested representation.
+	/// Templatised data accessor. TypeConverters will be used to attempt to convert from the internal representation to
+	/// the requested representation.
 	/// @param[out] value Data in the requested type.
 	/// @return True if the value was converted and returned, false if no data was stored in the variant.
-	template <typename T>
+	template <typename T, typename std::enable_if_t<!std::is_enum<T>::value, int> = 0>
+	bool GetInto(T& value) const;
+
+	/// Enum overload for the data accessor, will convert any stored integral value to the requested enum type.
+	template <typename T, typename std::enable_if_t<std::is_enum<T>::value, int> = 0>
 	bool GetInto(T& value) const;
 
 	/// Returns a reference to the variant's underlying type.
@@ -152,6 +156,9 @@ private:
 	void Set(const FontEffectsPtr& value);
 	void Set(FontEffectsPtr&& value);
 
+	template <typename T, typename = std::enable_if_t<std::is_enum<T>::value>>
+	void Set(const T value);
+
 	static constexpr size_t LOCAL_DATA_SIZE = (sizeof(TransitionList) > sizeof(String) ? sizeof(TransitionList) : sizeof(String));
 
 	Type type = NONE;

+ 22 - 1
Include/RmlUi/Core/Variant.inl

@@ -47,7 +47,7 @@ Variant& Variant::operator=(T&& t)
 	return *this;
 }
 
-template <typename T>
+template <typename T, typename std::enable_if<!std::is_enum<T>::value, int>::type>
 bool Variant::GetInto(T& value) const
 {
 	switch (type)
@@ -80,6 +80,19 @@ bool Variant::GetInto(T& value) const
 	return false;
 }
 
+template <typename T, typename std::enable_if<std::is_enum<T>::value, int>::type>
+bool Variant::GetInto(T& value) const
+{
+	static_assert(sizeof(T) <= sizeof(int64_t), "Enum underlying type exceeds maximum supported integer type size");
+	int64_t stored_value = 0;
+	if (GetInto(stored_value))
+	{
+		value = static_cast<T>(stored_value);
+		return true;
+	}
+	return false;
+}
+
 template <typename T>
 T Variant::Get(T default_value) const
 {
@@ -93,4 +106,12 @@ const T& Variant::GetReference() const
 	return *reinterpret_cast<const T*>(&data);
 }
 
+template <typename T, typename>
+void Variant::Set(const T value)
+{
+	static_assert(sizeof(T) <= sizeof(int64_t), "Enum underlying type exceeds maximum supported integer type size");
+	type = INT64;
+	*(reinterpret_cast<int64_t*>(data)) = static_cast<int64_t>(value);
+}
+
 } // namespace Rml

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

@@ -79,6 +79,8 @@ static const String document_rml = R"(
 <p>{{ s3.val }}</p>
 <p>{{ s4.val }}</p>
 <p>{{ s5.val }}</p>
+<p>{{ simple }}</p>
+<p>{{ scoped }}</p>
 
 <h1>Basic</h1>
 <p>{{ basic.a }}</p>
@@ -87,6 +89,8 @@ static const String document_rml = R"(
 <p>{{ basic.d }}</p>
 <p>{{ basic.e }}</p>
 <p>{{ basic.f }}</p>
+<p id="simple" data-event-click="basic.simple = 2">{{ basic.simple }}</p>
+<p>{{ basic.scoped }}</p>
 
 <h1>Wrapped</h1>
 <p>{{ wrapped.a.val }}</p>
@@ -184,12 +188,19 @@ struct StringWrap {
 	String val;
 };
 
+enum SimpleEnum { Simple_Zero = 0, Simple_One, Simple_Two };
+
+enum class ScopedEnum : uint64_t { Zero = 0, One, Two };
+
 struct Globals {
 	int i0 = 0;
 	int* i1 = new int(1);
 	UniquePtr<int> i2 = MakeUnique<int>(2);
 	SharedPtr<int> i3 = MakeShared<int>(3);
 
+	SimpleEnum simple = Simple_One;
+	ScopedEnum scoped = ScopedEnum::One;
+
 	String s0 = "s0";
 	String* s1 = new String("s1");
 	StringWrap s2 = StringWrap("s2");
@@ -209,6 +220,9 @@ struct Basic {
 	int a = 1;
 	int* b = new int(2);
 
+	SimpleEnum simple = Simple_One;
+	ScopedEnum scoped = ScopedEnum::One;
+
 	int GetC()
 	{
 		static int v = 5;
@@ -345,6 +359,8 @@ bool InitializeDataBindings(Context* context)
 		constructor.Bind("s4", &globals.s4);
 		constructor.Bind("s5", &globals.s5);
 
+		constructor.Bind("simple", &globals.simple);
+		constructor.Bind("scoped", &globals.scoped);
 		// Invalid: Each of the following should give a compile-time failure.
 		// constructor.Bind("x0", &globals.x0);
 		// constructor.Bind("x1", &globals.x1);
@@ -361,6 +377,8 @@ bool InitializeDataBindings(Context* context)
 		handle.RegisterMember("d", &Basic::GetD);
 		handle.RegisterMember("e", &Basic::GetE);
 		handle.RegisterMember("f", &Basic::GetF);
+		handle.RegisterMember("simple", &Basic::simple);
+		handle.RegisterMember("scoped", &Basic::scoped);
 
 		// handle.RegisterMember("x0", &Basic::x0);
 		// handle.RegisterMember("x1", &Basic::x1);
@@ -486,3 +504,51 @@ TEST_CASE("databinding.aliasing")
 
 	TestsShell::ShutdownShell();
 }
+
+static const String set_enum_rml = R"(
+<rml>
+<head>
+	<title>Test</title>
+	<link type="text/template" href="/assets/window.rml"/>
+	<style>
+		body.window {
+			width: 500px;
+			height: 400px;
+		}
+	</style>
+</head>
+<body template="window">
+<div data-model="basics">
+<p id="simple" data-event-click="simple = 2">{{ simple }}</p>
+</div>
+</body>
+</rml>
+)";
+
+TEST_CASE("databinding.set_enum")
+{
+	Context* context = TestsShell::GetContext();
+	REQUIRE(context);
+
+	globals.simple = Simple_One;
+
+	REQUIRE(InitializeDataBindings(context));
+
+	ElementDocument* document = context->LoadDocumentFromMemory(set_enum_rml);
+	REQUIRE(document);
+	document->Show();
+
+	TestsShell::RenderLoop();
+
+	Element* element = document->GetElementById("simple");
+	CHECK(element->GetInnerRML() == "1");
+
+	element->DispatchEvent(EventId::Click, Dictionary());
+	TestsShell::RenderLoop();
+
+	CHECK(globals.simple == Simple_Two);
+	CHECK(element->GetInnerRML() == "2");
+
+	document->Close();
+	TestsShell::ShutdownShell();
+}

+ 113 - 0
Tests/Source/UnitTests/Variant.cpp

@@ -0,0 +1,113 @@
+/*
+ * 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-2023 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 <RmlUi/Core/Types.h>
+#include <RmlUi/Core/Variant.h>
+#include <doctest.h>
+
+using namespace Rml;
+
+TEST_CASE("Variant.ScopedEnum")
+{
+	enum class X : uint64_t {
+		A = 1,
+		B = 5,
+		C = UINT64_MAX,
+	};
+
+	X e1 = X::A;
+	const X e2 = X::B;
+	const X& e3 = e1;
+
+	Variant v1 = Variant(X::A);
+	Variant v2(X::B);
+	Variant v3(X::C);
+
+	Variant v4(e1);
+	Variant v5(e2);
+	Variant v6(e3);
+
+	CHECK(v1.Get<X>() == X::A);
+	CHECK(v2.Get<X>() == X::B);
+	CHECK(v3.Get<X>() == X::C);
+
+	CHECK(v4.Get<X>() == X::A);
+	CHECK(v5.Get<X>() == X::B);
+	CHECK(v6.Get<X>() == X::A);
+
+	CHECK(v1 != v2);
+	CHECK(v1 == v4);
+
+	Variant v7 = v5;
+	CHECK(v7.Get<X>() == X::B);
+
+	CHECK(v1.Get<int>() == 1);
+	CHECK(v2.Get<int>() == 5);
+	CHECK(v3.Get<uint64_t>() == UINT64_MAX);
+	CHECK(v3.Get<int64_t>() == static_cast<int64_t>(UINT64_MAX));
+}
+
+TEST_CASE("Variant.UnscopedEnum")
+{
+	enum X : uint64_t {
+		A = 1,
+		B = 5,
+		C = UINT64_MAX,
+	};
+
+	X e1 = X::A;
+	const X e2 = X::B;
+	const X& e3 = e1;
+
+	Variant v1 = Variant(X::A);
+	Variant v2(X::B);
+	Variant v3(X::C);
+
+	Variant v4(e1);
+	Variant v5(e2);
+	Variant v6(e3);
+
+	CHECK(v1.Get<X>() == X::A);
+	CHECK(v2.Get<X>() == X::B);
+	CHECK(v3.Get<X>() == X::C);
+
+	CHECK(v4.Get<X>() == X::A);
+	CHECK(v5.Get<X>() == X::B);
+	CHECK(v6.Get<X>() == X::A);
+
+	CHECK(v1 != v2);
+	CHECK(v1 == v4);
+
+	Variant v7 = v5;
+	CHECK(v7.Get<X>() == X::B);
+
+	CHECK(v1.Get<int>() == 1);
+	CHECK(v2.Get<int>() == 5);
+	CHECK(v3.Get<uint64_t>() == UINT64_MAX);
+	CHECK(v3.Get<int64_t>() == static_cast<int64_t>(UINT64_MAX));
+}