瀏覽代碼

Transitions working for pseudo-classes. Working on element class.changes. Experimenting with reversal of transitions.

Michael 7 年之前
父節點
當前提交
23bdc17b8a

+ 7 - 2
Include/Rocket/Core/Element.h

@@ -292,11 +292,16 @@ public:
 	const Vector2f Project(const Vector2f& point) noexcept;
 
 	/// Start an animation of the given property on this element.
-	/// Target values must have the same unit as the property set on this element. Keywords are not 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, alternate_direction and delay will be ignored.
+	/// If start_value is null, the current property value on this element is used.
 	/// @return True if a new animation or key was added.
-	bool Animate(const String& property_name, const Property& target_value, float duration, Tween tween = Tween{}, int num_iterations = 1, bool alternate_direction = true, float delay = 0.0f, bool replace = false);
+	bool Animate(const String& property_name, const Property& target_value, float duration, Tween tween = Tween{}, int num_iterations = 1, bool alternate_direction = true, float delay = 0.0f, const Property* start_value = nullptr);
+
+	/// Start a transition of the given property on this element.
+	/// If an animation exists for the property, the call will be ignored. If a transition exists for this property, it will be replaced.
+	/// @return True if the transition was added.
+	bool StartTransition(const Transition& transition, const Property& start_value, const Property& target_value);
 	
 	/// 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.

+ 1 - 0
Include/Rocket/Core/Transition.h

@@ -43,6 +43,7 @@ struct Transition {
 	Tween tween;
 	float duration = 0.0f;
 	float delay = 0.0f;
+	float reverse_adjustment_factor = 0.0f;
 };
 
 struct TransitionList {

+ 5 - 0
Include/Rocket/Core/Tween.h

@@ -65,6 +65,11 @@ public:
 		return t;
 	}
 
+	void reverse()
+	{
+		std::swap(type_in, type_out);
+	}
+
 	// Tweening functions below.
 	// Partly based on http://libclaw.sourceforge.net/tweeners.html
 

+ 18 - 5
Samples/basic/animation/data/animation.rml

@@ -7,7 +7,7 @@
 		{
 			max-width: 2000px;
 			max-height: 2000px;
-			width: 1400px;
+			width: 1600px;
 			height: 750px;
 			perspective: 3000px;
 			/*opacity: 0;*/
@@ -85,19 +85,28 @@
 		#text_align {
 			text-align: left;
 		}
+		/* -- TRANSITION TESTS */
 		#transition_test {
-			transition: padding-left 1.6s elastic-out, background-color 1.2s quadratic-out, transform 1.3s exponential-out;
+			/*transition: padding-left 1.6s elastic-in 1.0, background-color 1.2s quadratic-in 1.0, transform 1.3s exponential-in 1.0;*/
+			transition: padding-left background-color transform 1.6s elastic-out 0.0;
 			transform: scale(1.0);
 		}
 		#transition_test:hover {
+			/*transition: padding-left background-color transform 0.8s quadratic-out 1.0;*/
 			padding-left: 60px;
 			transform: scale(1.5);
 		} 
+		#transition_class {
+			transition: margin-left 1s cubic-in-out;
+		}
+		#transition_class.blue {
+			margin-left: -50px;
+		}
 	</style>
 </head>
 
 <body template="window">
-<div style="width: 30%; height: 80%; position: absolute;">
+<div style="width: 20%; height: 80%; position: absolute;">
 	<div style="font-size: 0.85em; text-align: left;" id="fps"></div>
 	<button id="start_game">Start Game</button><br />
 	<button id="high_scores" onkeydown="hello">High Scores</button><br />
@@ -106,18 +115,22 @@
 	<button id="exit" onclick="exit">Exit</button>
 </div>
 
-<div style="width: 25%; height: 80%;position: absolute; left: 40%;" id="transform_tests">
+<div style="width: 20%; height: 80%;position: absolute; left: 28%;" id="transform_tests">
 	<div style="font-size: 1.5em; text-align: left;">Test transform animations</div>
 	<div class="container"><div class="plain" id="generic">Generic form conversion.</div></div>
 	<div class="container"><div class="plain" id="combine">Match different transform primitive sizes</div></div>
 	<div class="container"><div class="plain" id="decomposition">Force full matrix decomposition</div></div>
 </div>
 
-<div style="width: 25%; height: 80%;position: absolute; left: 70%;" id="mixed_units_tests">
+<div style="width: 20%; height: 80%;position: absolute; left: 52%;" id="mixed_units_tests">
 	<div style="font-size: 1.5em; text-align: left;">Mixed units tests</div>
 	<div class="container"><div class="plain" id="abs_rel">Pixel vs percentage.</div></div>
 	<div class="container"><div class="plain" id="abs_rel_transform">Pixel vs percentage transform.</div></div>
+</div>
+<div style="width: 20%; height: 80%;position: absolute; left: 75%;" id="transition_tests">
+	<div style="font-size: 1.5em; text-align: left;">Transition tests</div>
 	<div class="container"><div class="plain" id="transition_test">Transition test (hover)</div></div>
+	<div class="container"><div class="plain" id="transition_class" onclick="add_class">Transition class (click)</div></div>
 </div>
 </body>
 </rml>

+ 10 - 1
Samples/basic/animation/src/main.cpp

@@ -114,7 +114,7 @@ public:
 			// Mixed units tests
 			{
 				auto el = document->GetElementById("abs_rel");
-				el->Animate("margin-left", Property(100.f, Property::PERCENT), 1.5f, Tween{}, -1, true);
+				el->Animate("margin-left", Property(50.f, Property::PERCENT), 1.5f, Tween{}, -1, true);
 			}
 			{
 				auto el = document->GetElementById("abs_rel_transform");
@@ -243,6 +243,15 @@ public:
 				Rocket::Debugger::SetVisible(!Rocket::Debugger::IsVisible());
 			}
 		}
+		if (event == "click")
+		{
+			auto el = event.GetTargetElement();
+			if (el->GetId() == "transition_class")
+			{
+				// TODO: Doesn't seem to properly animate
+				el->SetClass("blue", !el->IsClassSet("blue"));
+			}
+		}
 	}
 
 	void OnDetach(Rocket::Core::Element* element) override { delete this; }

+ 48 - 16
Source/Core/Element.cpp

@@ -911,11 +911,8 @@ const Vector2f Element::Project(const Vector2f& point) noexcept
 	}
 }
 
-bool Element::Animate(const String & property_name, const Property & target_value, float duration, Tween tween, int num_iterations, bool alternate_direction, float delay, bool replace)
+bool Element::Animate(const String & property_name, const Property & target_value, float duration, Tween tween, int num_iterations, bool alternate_direction, float delay, const Property* start_value)
 {
-	if (delay < 0.0f)
-		return false;
-
 	ElementAnimation* animation = nullptr;
 
 	for (auto& existing_animation : animations)
@@ -932,33 +929,68 @@ bool Element::Animate(const String & property_name, const Property & target_valu
 	if (!animation)
 	{
 		float start_time = Clock::GetElapsedTime() + delay;
-		const Property* property = GetProperty(property_name);
-		if (!property || !property->definition) 
+		if(!start_value)
+			start_value = GetProperty(property_name);
+		if (!start_value || !start_value->definition) 
 			return false;
 
 		animations.push_back(
-			ElementAnimation{ property_name, *property, start_time, duration, num_iterations, alternate_direction }
+			ElementAnimation{ property_name, *start_value, start_time, duration, num_iterations, alternate_direction, false }
 		);
 		animation = &animations.back();
 	}
-	else if (replace)
-	{
-		animation->ReverseAnimation();
-		animation = nullptr;
-	}
 	else
 	{
 		target_time += animation->GetDuration();
 		animation->SetDuration(target_time);
 	}
 
-	bool result = false;
+	bool result = animation->AddKey(target_time, target_value, *this, tween);
 
-	if(animation)
+	return result;
+}
+
+bool Element::StartTransition(const Transition & transition, const Property& start_value, const Property & target_value)
+{
+	ElementAnimation* animation = nullptr;
+
+	for (auto& existing_animation : animations)
 	{
-		result = animation->AddKey(target_time, target_value, *this, tween);
+		if (existing_animation.GetPropertyName() == transition.name)
+		{
+			animation = &existing_animation;
+			break;
+		}
 	}
-	
+
+	if (animation && !animation->IsTransition())
+		return false;
+
+	float duration = transition.duration;
+
+	if (!animation)
+	{
+		float start_time = Clock::GetElapsedTime() + transition.delay;
+
+		animations.push_back(
+			ElementAnimation{ transition.name, start_value, start_time, duration, 1, false, true }
+		);
+		animation = &animations.back();
+	}
+	else
+	{
+		float start_time = Clock::GetElapsedTime() + transition.delay;
+		
+		// Compress the duration based on the progress of the current animation
+		float f = animation->GetInterpolationFactor();
+		f = 1.0f - (1.0f - f)*transition.reverse_adjustment_factor;
+		duration = duration * f;
+
+		*animation = ElementAnimation{ transition.name, start_value, start_time, duration, 1, false, true };
+	}
+
+	bool result = animation->AddKey(duration, target_value, *this, transition.tween);
+
 	return result;
 }
 

+ 46 - 33
Source/Core/ElementAnimation.cpp

@@ -358,10 +358,10 @@ static bool PrepareTransforms(std::vector<AnimationKey>& keys, Element& element,
 }
 
 
-ElementAnimation::ElementAnimation(const String& property_name, const Property& current_value, float start_world_time, float duration, int num_iterations, bool alternate_direction)
+ElementAnimation::ElementAnimation(const String& property_name, const Property& current_value, float start_world_time, float duration, int num_iterations, bool alternate_direction, bool is_transition)
 	: property_name(property_name), duration(duration), num_iterations(num_iterations), alternate_direction(alternate_direction),
 	keys({ AnimationKey{0.0f, current_value, Tween{}} }),
-	last_update_world_time(start_world_time), time_since_iteration_start(0.0f), current_iteration(0), reverse_direction(false), animation_complete(false), reverse_animation(false)
+	last_update_world_time(start_world_time), time_since_iteration_start(0.0f), current_iteration(0), reverse_direction(false), animation_complete(false), is_transition(is_transition)
 {
 	ROCKET_ASSERT(current_value.definition);
 }
@@ -407,42 +407,13 @@ bool ElementAnimation::AddKey(float time, const Property & property, Element& el
 	return result;
 }
 
-
-Property ElementAnimation::UpdateAndGetProperty(float world_time, Element& element)
+float ElementAnimation::GetInterpolationFactorAndKeys(int* out_key0, int* out_key1) const
 {
-	if (animation_complete || world_time - last_update_world_time <= 0.0f)
-		return Property{};
-
-	const float dt = Math::Min(world_time - last_update_world_time, 0.1f);
-
-	last_update_world_time = world_time;
-	time_since_iteration_start += dt;
-
-	if (time_since_iteration_start >= duration)
-	{
-		// Next iteration
-		current_iteration += (reverse_animation ? -1 : 1);
-
-		if (num_iterations == -1 || (current_iteration >= 0 && current_iteration < num_iterations) )
-		{
-			time_since_iteration_start -= duration;
-
-			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;
-	if (reverse_animation)
-		t = duration - t;
 
 	int key0 = -1;
 	int key1 = -1;
@@ -458,7 +429,7 @@ Property ElementAnimation::UpdateAndGetProperty(float world_time, Element& eleme
 		}
 
 		if (key1 < 0) key1 = (int)keys.size() - 1;
-		key0 = (key1 == 0 ? 0 : key1 - 1 );
+		key0 = (key1 == 0 ? 0 : key1 - 1);
 	}
 
 	ROCKET_ASSERT(key0 >= 0 && key0 < (int)keys.size() && key1 >= 0 && key1 < (int)keys.size());
@@ -479,6 +450,48 @@ Property ElementAnimation::UpdateAndGetProperty(float world_time, Element& eleme
 
 	alpha = keys[key1].tween(alpha);
 
+	if (out_key0) *out_key0 = key0;
+	if (out_key1) *out_key1 = key1;
+
+	return alpha;
+}
+
+
+
+Property ElementAnimation::UpdateAndGetProperty(float world_time, Element& element)
+{
+	if (animation_complete || world_time - last_update_world_time <= 0.0f)
+		return Property{};
+
+	const float dt = Math::Min(world_time - last_update_world_time, 0.1f);
+
+	last_update_world_time = world_time;
+	time_since_iteration_start += dt;
+
+	if (time_since_iteration_start >= duration)
+	{
+		// Next iteration
+		current_iteration += 1;
+
+		if (num_iterations == -1 || (current_iteration >= 0 && current_iteration < num_iterations) )
+		{
+			time_since_iteration_start -= duration;
+
+			if (alternate_direction)
+				reverse_direction = !reverse_direction;
+		}
+		else
+		{
+			animation_complete = true;
+			time_since_iteration_start = duration;
+		}
+	}
+
+	int key0 = -1;
+	int key1 = -1;
+
+	float alpha = GetInterpolationFactorAndKeys(&key0, &key1);
+
 	Property result = InterpolateProperties(keys[key0].property, keys[key1].property, alpha, element, keys[0].property.definition);
 	
 	return result;

+ 6 - 4
Source/Core/ElementAnimation.h

@@ -37,7 +37,7 @@ namespace Core {
 
 
 struct AnimationKey {
-	float time;
+	float time;   // Local animation time (Zero means the time when the animation iteration starts)
 	Property property;
 	Tween tween;  // Tweening between the previous and this key. Ignored for the first animation key.
 };
@@ -60,11 +60,12 @@ private:
 	bool reverse_direction;
 
 	bool animation_complete;
-	bool reverse_animation;
+	bool is_transition;
 
+	float GetInterpolationFactorAndKeys(int* out_key0, int* out_key1) const;
 public:
 
-	ElementAnimation(const String& property_name, const Property& current_value, float start_world_time, float duration, int num_iterations, bool alternate_direction);
+	ElementAnimation(const String& property_name, const Property& current_value, float start_world_time, float duration, int num_iterations, bool alternate_direction, bool is_transition);
 
 	bool AddKey(float time, const Property& property, Element& element, Tween tween);
 
@@ -74,7 +75,8 @@ public:
 	float GetDuration() const { return duration; }
 	void SetDuration(float duration) { this->duration = duration; }
 	bool IsComplete() const { return animation_complete; }
-	void ReverseAnimation() { reverse_animation = !reverse_animation; }
+	bool IsTransition() const { return is_transition; }
+	float GetInterpolationFactor() const { return GetInterpolationFactorAndKeys(nullptr, nullptr); }
 };
 
 

+ 136 - 16
Source/Core/ElementStyle.cpp

@@ -89,6 +89,115 @@ const ElementDefinition* ElementStyle::GetDefinition()
 
 	return definition;
 }
+
+
+
+// Returns one of this element's properties.
+static const Property* GetLocalProperty(const String& name, PropertyDictionary* local_properties, ElementDefinition* definition, const PseudoClassList& pseudo_classes)
+{
+	// Check for overriding local properties.
+	if (local_properties != NULL)
+	{
+		const Property* property = local_properties->GetProperty(name);
+		if (property != NULL)
+			return property;
+	}
+
+	// Check for a property defined in an RCSS rule.
+	if (definition != NULL)
+		return definition->GetProperty(name, pseudo_classes);
+
+	return NULL;
+}
+
+// Returns one of this element's properties.
+static const Property* GetProperty(const String& name, Element* element, PropertyDictionary* local_properties, ElementDefinition* definition, const PseudoClassList& pseudo_classes)
+{
+	if (prop_counter.find(name) == prop_counter.end())
+		prop_counter[name] = 0;
+	prop_counter[name] = prop_counter[name] + 1;
+	
+	const Property* local_property = GetLocalProperty(name, local_properties, definition, pseudo_classes);
+	if (local_property != NULL)
+		return local_property;
+
+	// Fetch the property specification.
+	const PropertyDefinition* property = StyleSheetSpecification::GetProperty(name);
+	if (property == NULL)
+		return NULL;
+
+	// If we can inherit this property, return our parent's property.
+	if (property->IsInherited())
+	{
+		Element* parent = element->GetParentNode();
+		while (parent != NULL)
+		{
+			const Property* parent_property = parent->GetStyle()->GetLocalProperty(name);
+			if (parent_property)
+				return parent_property;
+
+			parent = parent->GetParentNode();
+		}
+	}
+
+	// No property available! Return the default value.
+	return property->GetDefaultValue();
+}
+
+// For the properties which are defined in a transition on the given element, apply transition to all properties changed due to a class change.
+// Call this before new_definition has been assigned to the element and its style.
+// Properties that are part of a transition are removed from the properties list.
+static void TransitionUpdatedClass(ElementStyle* style, Element* element, PropertyNameList& properties, ElementDefinition* old_definition, ElementDefinition* new_definition, PropertyDictionary* local_properties, const PseudoClassList& pseudo_classes)
+{
+	if (!element || !old_definition || !new_definition || properties.empty())
+		return;
+
+	if (const Property* transition_property = GetLocalProperty(TRANSITION, local_properties, new_definition, pseudo_classes))
+	{
+		auto transition_list = transition_property->Get<TransitionList>();
+
+		if (!transition_list.none && new_definition)
+		{
+			for (auto it = properties.begin(); it != properties.end();)
+			{
+				auto& property = *it;
+				Transition* transition = nullptr;
+				bool transition_added = false;
+
+				if (transition_list.all)
+				{
+					transition = &transition_list.transitions[0];
+				}
+				else
+				{
+					// TODO: Swap the loops because a lookup in properties is constant time
+					for (auto& transition_candidate : transition_list.transitions) {
+						if (transition_candidate.name == property) {
+							transition = &transition_candidate;
+							break;
+						}
+					}
+				}
+
+				if (transition)
+				{
+					const Property* start_value = GetProperty(property, element, local_properties, old_definition, pseudo_classes);
+					const Property* target_value = GetProperty(property, element, local_properties, new_definition, pseudo_classes);
+
+					// TODO: See if the start and target_value are equal, if so, skip.
+
+					if (start_value && target_value)
+						transition_added = element->StartTransition(*transition, *start_value, *target_value);
+				}
+
+				if (transition_added)
+					it = properties.erase(it);
+				else
+					++it;
+			}
+		}
+	}
+}
 	
 void ElementStyle::UpdateDefinition()
 {
@@ -110,16 +219,18 @@ void ElementStyle::UpdateDefinition()
 			PropertyNameList properties;
 			
 			if (definition != NULL)
-			{
 				definition->GetDefinedProperties(properties, pseudo_classes);
+
+			if (new_definition != NULL)
+				new_definition->GetDefinedProperties(properties, pseudo_classes);
+
+			TransitionUpdatedClass(this, element, properties, definition, new_definition, local_properties, pseudo_classes);
+
+			if (definition != NULL)
 				definition->RemoveReference();
-			}
-			
+
 			definition = new_definition;
 			
-			if (definition != NULL)
-				definition->GetDefinedProperties(properties, pseudo_classes);
-			
 			DirtyProperties(properties);
 			element->GetElementDecoration()->ReloadDecorators();
 		}
@@ -147,15 +258,16 @@ void ElementStyle::SetPseudoClass(const String& pseudo_class, bool activate)
 {
 	size_t num_pseudo_classes = pseudo_classes.size();
 
-	auto pseudo_classes_after = pseudo_classes;
+	auto pseudo_classes_before = pseudo_classes;
 
 	if (activate)
-		pseudo_classes_after.insert(pseudo_class);
+		pseudo_classes.insert(pseudo_class);
 	else
-		pseudo_classes_after.erase(pseudo_class);
+		pseudo_classes.erase(pseudo_class);
 
 
-	// Apply transition if set
+	// Apply transition if defined on this element
+	// Note: We had to set the pseudo classes first so that any transition being overriden in the pseudo class is captured
 	if (const Property* property = GetLocalProperty(TRANSITION))
 	{
 		ROCKET_ASSERT(property->unit == Property::TRANSITION);
@@ -165,7 +277,7 @@ void ElementStyle::SetPseudoClass(const String& pseudo_class, bool activate)
 		if (!transition_list.none && definition != NULL)
 		{
 			PropertyNameList properties;
-			definition->GetDefinedProperties(properties, pseudo_classes_after, pseudo_class);
+			definition->GetDefinedProperties(properties, pseudo_classes, pseudo_class);
 
 			for (auto& property : properties)
 			{
@@ -186,16 +298,22 @@ void ElementStyle::SetPseudoClass(const String& pseudo_class, bool activate)
 
 				if (transition)
 				{
-					auto target_property = definition->GetProperty(property, pseudo_classes_after);
-					if (target_property)
-						element->Animate(property, *target_property, transition->duration, transition->tween, 1, false, transition->delay, true);
+					// Get start value from local properties first, else, we fetch the property given the previous pseudo classes
+					const Property* start_value = nullptr;
+					if (local_properties)
+						start_value = local_properties->GetProperty(property);
+					if(!start_value) 
+						start_value = definition->GetProperty(property, pseudo_classes_before);
+
+					auto target_value = definition->GetProperty(property, pseudo_classes);
+
+					if (start_value && target_value)
+						element->StartTransition(*transition, *start_value, *target_value);
 				}
 			}
 		}
 	}
 
-	pseudo_classes.swap(pseudo_classes_after);
-
 
 	if (pseudo_classes.size() != num_pseudo_classes)
 	{
@@ -338,6 +456,8 @@ void ElementStyle::RemoveProperty(const String& name)
 	}
 }
 
+
+
 // Returns one of this element's properties.
 const Property* ElementStyle::GetProperty(const String& name)
 {

+ 56 - 14
Source/Core/PropertyParserTransition.cpp

@@ -29,6 +29,7 @@
 #include "precompiled.h"
 #include "PropertyParserTransition.h"
 #include "../../Include/Rocket/Core/StringUtilities.h"
+#include "PropertyShorthandDefinition.h"
 
 
 namespace Rocket {
@@ -113,12 +114,15 @@ bool PropertyParserTransition::ParseValue(Property & property, const String & va
 	for (const String& single_property : list_of_properties)
 	{
 		Transition transition;
+		StringList target_property_names;
 
 		StringList arguments;
 		StringUtilities::ExpandString(arguments, single_property, ' ');
 
+
 		bool duration_found = false;
 		bool delay_found = false;
+		bool reverse_adjustment_factor_found = false;
 
 		for (auto& argument : arguments)
 		{
@@ -150,36 +154,74 @@ bool PropertyParserTransition::ParseValue(Property & property, const String & va
 			else
 			{
 				// Either <duration>, <delay> or a <property name>
+				float number = 0.0f;
+				int count = 0;
 
-				float* time_target = (duration_found ? &transition.delay : &transition.duration);
-
-				if (sscanf(argument.CString(), "%fs", time_target) == 1)
+				if (sscanf(argument.CString(), "%fs%n", &number, &count) == 1)
 				{
-					// Duration or delay was assigned
-					if (!duration_found)
-						duration_found = true;
-					else if (!delay_found)
-						delay_found = true;
+					// Found a number, if there was an 's' unit, count will be positive
+					if (count > 0)
+					{
+						// Duration or delay was assigned
+						if (!duration_found)
+						{
+							duration_found = true;
+							transition.duration = number;
+						}
+						else if (!delay_found)
+						{
+							delay_found = true;
+							transition.delay = number;
+						}
+						else
+							return false;
+					}
 					else
-						return false;
+					{
+						// No 's' unit means reverse adjustment factor was found
+						if (!reverse_adjustment_factor_found)
+						{
+							reverse_adjustment_factor_found = true;
+							transition.reverse_adjustment_factor = number;
+						}
+						else
+							return false;
+					}
 				}
 				else
 				{
-					// Must be a property name (unless its invalid, we don't actually validate it)
-					if (!transition.name.Empty())
+					// Must be a property name or shorthand, expand now
+					if (auto shorthand = StyleSheetSpecification::GetShorthand(argument))
+					{
+						// For shorthands, add each underlying property separately
+						for (const auto& property : shorthand->properties)
+							target_property_names.push_back(property.first);
+					}
+					else if (auto definition = StyleSheetSpecification::GetProperty(argument))
+					{
+						// Single property
+						target_property_names.push_back(argument);
+					}
+					else
+					{
+						// Unknown property name
 						return false;
-					transition.name = argument;
+					}
 				}
 			}
 		}
 
 		// Validate the parsed transition
-		if (transition.name.Length() == 0 || transition.duration <= 0.0f)
+		if (target_property_names.empty() || transition.duration <= 0.0f || transition.reverse_adjustment_factor < 0.0f || transition.reverse_adjustment_factor > 1.0f)
 		{
 			return false;
 		}
 
-		transition_list.transitions.push_back(transition);
+		for (const auto& property_name : target_property_names)
+		{
+			transition.name = property_name;
+			transition_list.transitions.push_back(transition);
+		}
 	}
 
 	property = Property{ transition_list, Property::TRANSITION };

+ 3 - 1
Source/Core/Transform.cpp

@@ -47,7 +47,9 @@ Transform::Transform(std::vector<Transforms::Primitive> primitives)
 
 Property Transform::MakeProperty(std::vector<Transforms::Primitive> primitives)
 {
-	return Property{ TransformRef{new Transform{primitives}}, Property::TRANSFORM };
+	Property p{ TransformRef{new Transform{primitives}}, Property::TRANSFORM };
+	p.definition = StyleSheetSpecification::GetProperty(TRANSFORM);
+	return p;
 }
 
 Transform::Transform(const Transform& other)