Browse Source

Initial animation support

Michael 7 years ago
parent
commit
664067d5df

+ 1 - 1
Build/CMakeLists.txt

@@ -468,7 +468,7 @@ endmacro()
 if(BUILD_SAMPLES)
 if(BUILD_SAMPLES)
     include(SampleFileList)
     include(SampleFileList)
 
 
-    set(samples treeview customlog drag loaddocument transform bitmapfont)
+    set(samples treeview customlog drag loaddocument transform bitmapfont animation)
     set(tutorials template datagrid datagrid_tree tutorial_drag)
     set(tutorials template datagrid datagrid_tree tutorial_drag)
     
     
 if(NOT BUILD_FRAMEWORK)
 if(NOT BUILD_FRAMEWORK)

+ 2 - 0
Build/cmake/FileList.cmake

@@ -17,6 +17,7 @@ set(Core_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledVertical.h
     ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledVertical.h
     ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledVerticalInstancer.h
     ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledVerticalInstancer.h
     ${PROJECT_SOURCE_DIR}/Source/Core/DocumentHeader.h
     ${PROJECT_SOURCE_DIR}/Source/Core/DocumentHeader.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/ElementAnimation.h
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementBackground.h
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementBackground.h
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementBorder.h
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementBorder.h
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementDecoration.h
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementDecoration.h
@@ -220,6 +221,7 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/Dictionary.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Dictionary.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/DocumentHeader.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/DocumentHeader.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Element.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Element.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/ElementAnimation.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementBackground.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementBackground.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementBorder.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementBorder.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementDecoration.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementDecoration.cpp

+ 7 - 0
Build/cmake/SampleFileList.cmake

@@ -85,6 +85,13 @@ set(transform_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Samples/basic/transform/src/main.cpp
     ${PROJECT_SOURCE_DIR}/Samples/basic/transform/src/main.cpp
 )
 )
 
 
+set(animation_HDR_FILES
+)
+
+set(animation_SRC_FILES
+    ${PROJECT_SOURCE_DIR}/Samples/basic/animation/src/main.cpp
+)
+
 set(sdl2_HDR_FILES
 set(sdl2_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Samples/basic/sdl2/src/RenderInterfaceSDL2.h
     ${PROJECT_SOURCE_DIR}/Samples/basic/sdl2/src/RenderInterfaceSDL2.h
     ${PROJECT_SOURCE_DIR}/Samples/basic/sdl2/src/SystemInterfaceSDL2.h
     ${PROJECT_SOURCE_DIR}/Samples/basic/sdl2/src/SystemInterfaceSDL2.h

+ 10 - 6
Include/Rocket/Core/Element.h

@@ -40,17 +40,12 @@
 
 
 #include <memory>
 #include <memory>
 
 
-namespace Rocket {
-namespace Core {
-	class Dictionary;
-}
-}
-
 namespace Rocket {
 namespace Rocket {
 namespace Core {
 namespace Core {
 
 
 class Context;
 class Context;
 class Decorator;
 class Decorator;
+class Dictionary;
 class ElementInstancer;
 class ElementInstancer;
 class EventDispatcher;
 class EventDispatcher;
 class EventListener;
 class EventListener;
@@ -295,6 +290,11 @@ public:
 	/// @return The projected coordinates.
 	/// @return The projected coordinates.
 	const Vector2f Project(const Vector2f& point) throw();
 	const Vector2f Project(const Vector2f& point) throw();
 
 
+	/// Start an animation of the given property on this element.
+	/// Currently, only float and Colourb property values are supported. If an animation of the same property name exists, the target value
+	/// and duration will be added as a new animation key, adding to its total duration. Then, num_iterations and alternate_direction will be ignored.
+	void Animate(const String& property_name, const Property& target_value, float duration, int num_iterations = 1, bool alternate_direction = false);
+
 	/// Iterates over the properties defined on this element.
 	/// Iterates over the properties defined on this element.
 	/// @param[inout] index Index of the property to fetch. This is incremented to the next valid index after the fetch. Indices are not necessarily incremental.
 	/// @param[inout] index Index of the property to fetch. This is incremented to the next valid index after the fetch. Indices are not necessarily incremental.
 	/// @param[out] pseudo_classes The pseudo-classes the property is defined by.
 	/// @param[out] pseudo_classes The pseudo-classes the property is defined by.
@@ -678,6 +678,8 @@ private:
 	void DirtyTransformState(bool perspective_changed, bool transform_changed, bool parent_pv_changed);
 	void DirtyTransformState(bool perspective_changed, bool transform_changed, bool parent_pv_changed);
 	void UpdateTransformState();
 	void UpdateTransformState();
 
 
+	void UpdateAnimations();
+
 	// Original tag this element came from.
 	// Original tag this element came from.
 	String tag;
 	String tag;
 
 
@@ -761,6 +763,8 @@ private:
 	bool transform_state_transform_dirty;
 	bool transform_state_transform_dirty;
 	bool transform_state_parent_transform_dirty;
 	bool transform_state_parent_transform_dirty;
 
 
+	ElementAnimationList animations;
+
 	friend class Context;
 	friend class Context;
 	friend class ElementStyle;
 	friend class ElementStyle;
 	friend class LayoutEngine;
 	friend class LayoutEngine;

+ 2 - 0
Include/Rocket/Core/Types.h

@@ -90,6 +90,7 @@ typedef ColumnMajorMatrix4f Matrix4f;
 
 
 class Element;
 class Element;
 class Dictionary;
 class Dictionary;
+class ElementAnimation;
 
 
 // Types for external interfaces.
 // Types for external interfaces.
 typedef uintptr_t FileHandle;
 typedef uintptr_t FileHandle;
@@ -103,6 +104,7 @@ typedef std::set< String > PseudoClassList;
 typedef std::set< String > PropertyNameList;
 typedef std::set< String > PropertyNameList;
 typedef std::set< String > AttributeNameList;
 typedef std::set< String > AttributeNameList;
 typedef Dictionary ElementAttributes;
 typedef Dictionary ElementAttributes;
+typedef std::vector< ElementAnimation > ElementAnimationList;
 }
 }
 }
 }
 
 

+ 36 - 0
Samples/basic/animation/data/animation.rml

@@ -0,0 +1,36 @@
+<rml>
+<head>
+	<link type="text/template" href="../../../assets/window.rml"/>
+	<title>Animation Sample</title>
+	<style>
+		body
+		{
+			width: 350px;
+			height: 300px;
+		}
+
+		div#title_bar div#icon
+		{
+			display: none;
+		}
+
+		spacer
+		{
+			display: inline-block;
+			width: 25px;
+		}
+
+		#high_score {
+			margin-left: -100px;
+		}
+	</style>
+</head>
+
+<body template="window">
+	<button>Start Game</button><br />
+	<button id="high_score">High Scores</button><br />
+	<button>Options</button><br />
+	<button id="help">Help</button><br />
+	<button id="exit">Exit</button>
+</body>
+</rml>

+ 174 - 0
Samples/basic/animation/src/main.cpp

@@ -0,0 +1,174 @@
+/*
+ * This source file is part of libRocket, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://www.librocket.com
+ *
+ * Copyright (c) 2018 Michael Ragazzon
+ *
+ * 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 <Rocket/Core.h>
+#include <Rocket/Controls.h>
+#include <Rocket/Debugger.h>
+#include <Input.h>
+#include <Shell.h>
+
+#include <cmath>
+#include <sstream>
+
+class DemoWindow
+{
+public:
+	DemoWindow(const Rocket::Core::String &title, const Rocket::Core::Vector2f &position, Rocket::Core::Context *context)
+	{
+		document = context->LoadDocument("basic/animation/data/animation.rml");
+		if (document != NULL)
+		{
+			document->GetElementById("title")->SetInnerRML(title);
+			document->SetProperty("left", Rocket::Core::Property(position.x, Rocket::Core::Property::PX));
+			document->SetProperty("top", Rocket::Core::Property(position.y, Rocket::Core::Property::PX));
+
+			auto el = document->GetElementById("high_score");
+			el->Animate("margin-left", Rocket::Core::Property(0.f, Rocket::Core::Property::PX), 0.3f, 10, true);
+			el->Animate("margin-left", Rocket::Core::Property(100.f, Rocket::Core::Property::PX), 0.6f);
+
+			el = document->GetElementById("exit");
+			el->Animate("margin-left", Rocket::Core::Property(100.f, Rocket::Core::Property::PX), 8.0f, -1, true);
+
+			el = document->GetElementById("help");
+			el->Animate("image-color", Rocket::Core::Property(Rocket::Core::Colourb(128, 255, 255, 255), Rocket::Core::Property::COLOUR), 0.3f, -1, false);
+			el->Animate("image-color", Rocket::Core::Property(Rocket::Core::Colourb(128, 128, 255, 255), Rocket::Core::Property::COLOUR), 0.3f);
+			el->Animate("image-color", Rocket::Core::Property(Rocket::Core::Colourb(0, 128, 128, 255), Rocket::Core::Property::COLOUR), 0.3f);
+			el->Animate("image-color", Rocket::Core::Property(Rocket::Core::Colourb(64, 128, 255, 0), Rocket::Core::Property::COLOUR), 0.9f);
+			el->Animate("image-color", Rocket::Core::Property(Rocket::Core::Colourb(255, 255, 255, 255), Rocket::Core::Property::COLOUR), 0.3f);
+
+			document->Show();
+		}
+	}
+
+	~DemoWindow()
+	{
+		if (document)
+		{
+			document->RemoveReference();
+			document->Close();
+		}
+	}
+
+	Rocket::Core::ElementDocument * GetDocument() {
+		return document;
+	}
+
+private:
+	Rocket::Core::ElementDocument *document;
+};
+
+
+Rocket::Core::Context* context = NULL;
+ShellRenderInterfaceExtensions *shell_renderer;
+DemoWindow* window = NULL;
+
+void GameLoop()
+{
+	context->Update();
+
+	shell_renderer->PrepareRenderBuffer();
+	context->Render();
+	shell_renderer->PresentRenderBuffer();
+
+	//auto el = window->GetDocument()->GetElementById("exit");
+	//auto f = el->GetProperty<int>("margin-left");
+	//static int f_prev = 0.0f;
+	//int df = f - f_prev;
+	//f_prev = f;
+	//if(df != 0)
+	//	Rocket::Core::Log::Message(Rocket::Core::Log::LT_INFO, "Animation f = %d,  df = %d", f, df);
+}
+
+#if defined ROCKET_PLATFORM_WIN32
+#include <windows.h>
+int APIENTRY WinMain(HINSTANCE ROCKET_UNUSED_PARAMETER(instance_handle), HINSTANCE ROCKET_UNUSED_PARAMETER(previous_instance_handle), char* ROCKET_UNUSED_PARAMETER(command_line), int ROCKET_UNUSED_PARAMETER(command_show))
+#else
+int main(int ROCKET_UNUSED_PARAMETER(argc), char** ROCKET_UNUSED_PARAMETER(argv))
+#endif
+{
+#ifdef ROCKET_PLATFORM_WIN32
+	ROCKET_UNUSED(instance_handle);
+	ROCKET_UNUSED(previous_instance_handle);
+	ROCKET_UNUSED(command_line);
+	ROCKET_UNUSED(command_show);
+#else
+	ROCKET_UNUSED(argc);
+	ROCKET_UNUSED(argv);
+#endif
+
+	ShellRenderInterfaceOpenGL opengl_renderer;
+	shell_renderer = &opengl_renderer;
+
+	// Generic OS initialisation, creates a window and attaches OpenGL.
+	if (!Shell::Initialise("../../Samples/") ||
+		!Shell::OpenWindow("Animation Sample", shell_renderer, 1024, 768, true))
+	{
+		Shell::Shutdown();
+		return -1;
+	}
+
+	// Rocket initialisation.
+	Rocket::Core::SetRenderInterface(&opengl_renderer);
+	opengl_renderer.SetViewport(1024,768);
+
+	ShellSystemInterface system_interface;
+	Rocket::Core::SetSystemInterface(&system_interface);
+
+	Rocket::Core::Initialise();
+
+	// Create the main Rocket context and set it on the shell's input layer.
+	context = Rocket::Core::CreateContext("main", Rocket::Core::Vector2i(1024, 768));
+	if (context == NULL)
+	{
+		Rocket::Core::Shutdown();
+		Shell::Shutdown();
+		return -1;
+	}
+
+	Rocket::Controls::Initialise();
+	Rocket::Debugger::Initialise(context);
+	Input::SetContext(context);
+	shell_renderer->SetContext(context);
+
+	Shell::LoadFonts("assets/");
+
+	window = new DemoWindow("Animation sample", Rocket::Core::Vector2f(81, 200), context);
+
+
+	Shell::EventLoop(GameLoop);
+
+	delete window;
+
+	// Shutdown Rocket.
+	context->RemoveReference();
+	Rocket::Core::Shutdown();
+
+	Shell::CloseWindow();
+	Shell::Shutdown();
+
+	return 0;
+}

+ 2 - 0
Source/Core/Context.cpp

@@ -171,6 +171,7 @@ float Context::GetDensityIndependentPixelRatio() const
 // Updates all elements in the element tree.
 // Updates all elements in the element tree.
 bool Context::Update()
 bool Context::Update()
 {
 {
+#ifdef _DEBUG
 	// Reset all document layout locks (work-around due to leak, possibly in select element?)
 	// Reset all document layout locks (work-around due to leak, possibly in select element?)
 	for (int i = 0; i < root->GetNumChildren(); ++i)
 	for (int i = 0; i < root->GetNumChildren(); ++i)
 	{
 	{
@@ -181,6 +182,7 @@ bool Context::Update()
 			document->lock_layout = 0;
 			document->lock_layout = 0;
 		}
 		}
 	}
 	}
+#endif
 
 
 	root->Update();
 	root->Update();
 
 

+ 60 - 0
Source/Core/Element.cpp

@@ -32,6 +32,8 @@
 #include "../../Include/Rocket/Core/TransformPrimitive.h"
 #include "../../Include/Rocket/Core/TransformPrimitive.h"
 #include <algorithm>
 #include <algorithm>
 #include <limits>
 #include <limits>
+#include "Clock.h"
+#include "ElementAnimation.h"
 #include "ElementBackground.h"
 #include "ElementBackground.h"
 #include "ElementBorder.h"
 #include "ElementBorder.h"
 #include "ElementDefinition.h"
 #include "ElementDefinition.h"
@@ -159,6 +161,9 @@ void Element::Update()
 	for (size_t i = 0; i < active_children.size(); i++)
 	for (size_t i = 0; i < active_children.size(); i++)
 		active_children[i]->Update();
 		active_children[i]->Update();
 
 
+	// Update animations, if necessary.
+	UpdateAnimations();
+
 	// Force a definition reload, if necessary.
 	// Force a definition reload, if necessary.
 	style->GetDefinition();
 	style->GetDefinition();
 
 
@@ -169,6 +174,29 @@ void Element::Update()
 	OnUpdate();
 	OnUpdate();
 }
 }
 
 
+
+
+void Element::UpdateAnimations()
+{
+	if (!animations.empty())
+	{
+		float time = Clock::GetElapsedTime();
+
+		for(auto& animation : animations)
+		{
+			Property property = animation.UpdateAndGetProperty(time);
+			if(property.unit != Property::UNKNOWN)
+				SetProperty(animation.GetPropertyName(), property);
+		}
+
+		animations.erase(
+			std::remove_if(animations.begin(), animations.end(), [](const ElementAnimation& animation) { return animation.IsComplete(); }),
+			animations.end()
+		);
+	}
+}
+
+
 void Element::Render()
 void Element::Render()
 {
 {
 	// Rebuild our stacking context if necessary.
 	// Rebuild our stacking context if necessary.
@@ -861,6 +889,38 @@ const Vector2f Element::Project(const Vector2f& point) throw()
 	}
 	}
 }
 }
 
 
+void Element::Animate(const String & property_name, const Property & target_value, float duration, int num_iterations, bool alternate_direction)
+{
+	ElementAnimation* animation = nullptr;
+
+	for (auto& existing_animation : animations)
+	{
+		if (existing_animation.GetPropertyName() == property_name)
+		{
+			animation = &existing_animation;
+			break;
+		}
+	}
+
+	float target_time = duration;
+
+	if (!animation)
+	{
+		float time = Clock::GetElapsedTime();
+		auto property = GetProperty(property_name);
+		ElementAnimation new_animation{ property_name, *property, time, duration, num_iterations, alternate_direction };
+		animations.push_back(new_animation);
+		animation = &animations.back();
+	}
+	else
+	{
+		target_time += animation->GetDuration();
+		animation->SetDuration(target_time);
+	}
+
+	animation->AddKey(target_time, target_value);
+}
+
 // Iterates over the properties defined on this element.
 // Iterates over the properties defined on this element.
 bool Element::IterateProperties(int& index, PseudoClassList& pseudo_classes, String& name, const Property*& property) const
 bool Element::IterateProperties(int& index, PseudoClassList& pseudo_classes, String& name, const Property*& property) const
 {
 {

+ 184 - 0
Source/Core/ElementAnimation.cpp

@@ -0,0 +1,184 @@
+/*
+ * This source file is part of libRocket, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://www.librocket.com
+ *
+ * Copyright (c) 2018 Michael Ragazzon
+ *
+ * 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 "ElementAnimation.h"
+#include "../../Include/Rocket/Core/Element.h"
+
+namespace Rocket {
+namespace Core {
+
+
+static Colourf ColourToLinearSpace(Colourb c)
+{
+	Colourf result;
+	// Approximate inverse sRGB function
+	result.red = Math::SquareRoot((float)c.red / 255.f);
+	result.green = Math::SquareRoot((float)c.green / 255.f);
+	result.blue = Math::SquareRoot((float)c.blue / 255.f);
+	result.alpha = (float)c.alpha / 255.f;
+	return result;
+}
+
+static Colourb ColourFromLinearSpace(Colourf c)
+{
+	Colourb result;
+	result.red = (Rocket::Core::byte)Math::Clamp(c.red*c.red*255.f, 0.0f, 255.f);
+	result.green = (Rocket::Core::byte)Math::Clamp(c.green*c.green*255.f, 0.0f, 255.f);
+	result.blue = (Rocket::Core::byte)Math::Clamp(c.blue*c.blue*255.f, 0.0f, 255.f);
+	result.alpha = (Rocket::Core::byte)Math::Clamp(c.alpha*255.f, 0.0f, 255.f);
+	return result;
+}
+
+static Variant InterpolateValues(const Variant & from, const Variant & to, float alpha)
+{
+	auto type = from.GetType();
+	auto type_to = to.GetType();
+	if (type != type_to)
+	{
+		Log::Message(Log::LT_WARNING, "Interpolating properties must be of same unit. Got types: '%c' and '%c'.", type, type_to);
+		return from;
+	}
+
+	switch (type)
+	{
+	case Variant::FLOAT:
+	{
+		float f0 = from.Get<float>();
+		float f1 = to.Get<float>();
+		float f = (1.0f - alpha) * f0 + alpha * f1;
+		return Variant(f);
+	}
+	case Variant::COLOURB:
+	{
+		Colourf c0 = ColourToLinearSpace(from.Get<Colourb>());
+		Colourf c1 = ColourToLinearSpace(to.Get<Colourb>());
+		Colourf c = c0 * (1.0f - alpha) + c1 * alpha;
+		return Variant(ColourFromLinearSpace(c));
+	}
+	}
+
+	Log::Message(Log::LT_WARNING, "Currently, only float and color values can be interpolated. Got types of: '%c'.", type);
+
+	return from;
+}
+
+
+
+bool ElementAnimation::AddKey(float time, const Property & property)
+{
+	if (property.unit != property_unit)
+		return false;
+
+	keys.push_back({ time, property.value });
+
+	return true;
+}
+
+Property ElementAnimation::UpdateAndGetProperty(float time)
+{
+	Property result;
+
+
+	//Log::Message(Log::LT_INFO, "Animation it = %d,  t_it = %f, rev = %d,  dt = %f", current_iteration, time_since_iteration_start, (int)reverse_direction, time - last_update_time);
+
+	if (animation_complete || time - last_update_time <= 0.0f)
+		return result;
+
+	const float dt = time - last_update_time;
+
+	last_update_time = time;
+	time_since_iteration_start += dt;
+
+	if (time_since_iteration_start >= duration)
+	{
+		// Next iteration
+		current_iteration += 1;
+
+		if (current_iteration < num_iterations || num_iterations == -1)
+		{
+			time_since_iteration_start = 0.0f;
+
+			if (alternate_direction)
+				reverse_direction = !reverse_direction;
+		}
+		else
+		{
+			animation_complete = true;
+			time_since_iteration_start = duration;
+		}
+	}
+
+	float t = time_since_iteration_start;
+
+	if (reverse_direction)
+		t = duration - t;
+
+	int key0 = -1;
+	int key1 = -1;
+
+	{
+		for (int i = 0; i < (int)keys.size(); i++)
+		{
+			if (keys[i].time >= t)
+			{
+				key1 = i;
+				break;
+			}
+		}
+
+		if (key1 < 0) key1 = (int)keys.size() - 1;
+		key0 = (key1 == 0 ? 0 : key1 - 1 );
+	}
+
+	ROCKET_ASSERT(key0 >= 0 && key0 < (int)keys.size() && key1 >= 0 && key1 < (int)keys.size());
+
+	float alpha = 0.0f;
+
+	{
+		const float t0 = keys[key0].time;
+		const float t1 = keys[key1].time;
+
+		const float eps = 1e-3f;
+
+		if (t1 - t0 > eps)
+			alpha = (t - t0) / (t1 - t0);
+		
+
+		alpha = Math::Clamp(alpha, 0.0f, 1.0f);
+	}
+
+	result.unit = property_unit;
+	result.specificity = property_specificity;
+	result.value = InterpolateValues(keys[key0].value, keys[key1].value, alpha);
+	
+	return result;
+}
+
+
+}
+}

+ 90 - 0
Source/Core/ElementAnimation.h

@@ -0,0 +1,90 @@
+/*
+ * This source file is part of libRocket, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://www.librocket.com
+ *
+ * Copyright (c) 2018 Michael Ragazzon
+ *
+ * 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 ROCKETCOREELEMENTANIMATION_H
+#define ROCKETCOREELEMENTANIMATION_H
+
+#include "../../Include/Rocket/Core/Header.h"
+#include "../../Include/Rocket/Core/Property.h"
+
+namespace Rocket {
+namespace Core {
+
+
+struct AnimationKey {
+	float time;
+	Variant value;
+};
+
+
+class ElementAnimation
+{
+private:
+	String property_name;
+	Property::Unit property_unit;
+	int property_specificity;
+
+	float duration;           // for a single iteration
+	int num_iterations;       // -1 for infinity
+	bool alternate_direction; // between iterations
+
+	std::vector<AnimationKey> keys;
+
+	float last_update_time;
+	float time_since_iteration_start;
+	int current_iteration;
+	bool reverse_direction;  // if true, run time backwards
+
+	bool animation_complete;
+
+public:
+
+	ElementAnimation(const String& property_name, const Property& current_value, float time, float duration, int num_iterations, bool alternate_direction) 
+		: property_name(property_name), property_unit(current_value.unit), property_specificity(current_value.specificity),
+		duration(duration), num_iterations(num_iterations), alternate_direction(alternate_direction), 
+		keys({ AnimationKey{0.0f, current_value.value} }),
+		last_update_time(time), time_since_iteration_start(0.0f), current_iteration(0), reverse_direction(false), animation_complete(false) 
+	{}
+
+	bool AddKey(float time, const Property& property);
+
+	Property UpdateAndGetProperty(float time);
+
+	const String& GetPropertyName() const { return property_name; }
+	float GetDuration() const { return duration; }
+	void SetDuration(float duration) { this->duration = duration; }
+	bool IsComplete() const { return animation_complete; }
+};
+
+
+
+
+
+}
+}
+
+#endif