diff --git a/Include/RmlUi/Core/PropertySpecification.h b/Include/RmlUi/Core/PropertySpecification.h index 39195a57b..175967453 100644 --- a/Include/RmlUi/Core/PropertySpecification.h +++ b/Include/RmlUi/Core/PropertySpecification.h @@ -120,7 +120,7 @@ class RMLUICORE_API PropertySpecification void SetPropertyDefaults(PropertyDictionary& dictionary) const; /// Returns the properties of dictionary converted to a string. - String PropertiesToString(const PropertyDictionary& dictionary) const; + String PropertiesToString(const PropertyDictionary& dictionary, bool include_name, char delimiter) const; private: using Properties = Vector< UniquePtr >; diff --git a/Include/RmlUi/Core/StyleSheet.h b/Include/RmlUi/Core/StyleSheet.h index 5409ad542..daf04e091 100644 --- a/Include/RmlUi/Core/StyleSheet.h +++ b/Include/RmlUi/Core/StyleSheet.h @@ -46,6 +46,8 @@ class StyleSheetParser; struct PropertySource; struct Sprite; +using DecoratorPtrList = Vector>; + /** StyleSheet maintains a single stylesheet definition. A stylesheet can be combined with another stylesheet to create a new, merged stylesheet. @@ -66,6 +68,9 @@ class RMLUICORE_API StyleSheet final : public NonCopyMoveable /// Builds the node index for a combined style sheet. void BuildNodeIndex(); + /// Returns the DecoratorSpecification of the given name, or null if it does not exist. + const DecoratorSpecification* GetDecoratorSpecification(const String& name) const; + /// Returns the Keyframes of the given name, or null if it does not exist. /// @lifetime The returned pointer becomes invalidated whenever the style sheet is re-generated. Do not store this pointer or references to subobjects around. const Keyframes* GetKeyframes(const String& name) const; @@ -78,7 +83,7 @@ class RMLUICORE_API StyleSheet final : public NonCopyMoveable SharedPtr GetElementDefinition(const Element* element) const; /// Returns a list of instanced decorators from the declarations. The instances are cached for faster future retrieval. - const Vector>& InstanceDecorators(const DecoratorDeclarationList& declaration_list, const PropertySource* decorator_source) const; + const DecoratorPtrList& InstanceDecorators(const DecoratorDeclarationList& declaration_list, const PropertySource* decorator_source) const; private: StyleSheet(); diff --git a/Include/RmlUi/Core/StyleSheetTypes.h b/Include/RmlUi/Core/StyleSheetTypes.h index fde4dba84..e3701056d 100644 --- a/Include/RmlUi/Core/StyleSheetTypes.h +++ b/Include/RmlUi/Core/StyleSheetTypes.h @@ -30,6 +30,7 @@ #define RMLUI_CORE_STYLESHEETTYPES_H #include "PropertyDictionary.h" +#include "Factory.h" #include "Types.h" #include "Utilities.h" @@ -63,6 +64,16 @@ struct DecoratorDeclaration { DecoratorInstancer* instancer; PropertyDictionary properties; }; + +struct DecoratorDeclarationView { + DecoratorDeclarationView(const DecoratorDeclaration& declaration) : type(declaration.type), instancer(declaration.instancer), properties(declaration.properties) {} + DecoratorDeclarationView(const DecoratorSpecification* specification) : type(specification->decorator_type), instancer(Factory::GetDecoratorInstancer(specification->decorator_type)), properties(specification->properties) {} + + const String& type; + DecoratorInstancer* instancer; + const PropertyDictionary& properties; +}; + struct DecoratorDeclarationList { Vector list; String value; diff --git a/Source/Core/ElementAnimation.cpp b/Source/Core/ElementAnimation.cpp index 241050f2e..f3a463f63 100644 --- a/Source/Core/ElementAnimation.cpp +++ b/Source/Core/ElementAnimation.cpp @@ -27,13 +27,18 @@ */ #include "ElementAnimation.h" -#include "ElementStyle.h" -#include "TransformUtilities.h" +#include "../../Include/RmlUi/Core/DecoratorInstancer.h" +#include "../../Include/RmlUi/Core/Factory.h" #include "../../Include/RmlUi/Core/Element.h" #include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/PropertySpecification.h" #include "../../Include/RmlUi/Core/StyleSheetSpecification.h" +#include "../../Include/RmlUi/Core/StyleSheetTypes.h" +#include "../../Include/RmlUi/Core/StyleSheet.h" #include "../../Include/RmlUi/Core/Transform.h" #include "../../Include/RmlUi/Core/TransformPrimitive.h" +#include "ElementStyle.h" +#include "TransformUtilities.h" namespace Rml { @@ -165,6 +170,124 @@ static Property InterpolateProperties(const Property & p0, const Property& p1, f return Property{ TransformPtr(std::move(t)), Property::TRANSFORM }; } + if (p0.unit == Property::DECORATOR && p1.unit == Property::DECORATOR) + { + auto DiscreteInterpolation = [&]() { return alpha < 0.5f ? p0 : p1; }; + + // We construct DecoratorDeclarationView from declaration if it has instancer, otherwise we look for DecoratorSpecification and return DecoratorDeclarationView from it + auto GetDecoratorDeclarationView = [&](const DecoratorDeclaration& declaration) -> DecoratorDeclarationView { + if (declaration.instancer) + return DecoratorDeclarationView{ declaration }; + + const StyleSheet* style_sheet = element.GetStyleSheet(); + if (!style_sheet) + { + Log::Message(Log::LT_WARNING, "Failed to get element stylesheet for '%s' decorator rule.", declaration.type.c_str()); + return DecoratorDeclarationView{ declaration }; + } + + const DecoratorSpecification* specification = style_sheet->GetDecoratorSpecification(declaration.type); + if (!specification) + { + Log::Message(Log::LT_WARNING, "Could not find DecoratorSpecification for '%s' decorator rule.", declaration.type.c_str()); + return DecoratorDeclarationView{ declaration }; + } + + return DecoratorDeclarationView{ specification }; + }; + + auto& ptr0 = p0.value.GetReference(); + auto& ptr1 = p1.value.GetReference(); + if (!ptr0 || !ptr1) + { + RMLUI_ERRORMSG("Invalid decorator pointer, were the decorator keys properly prepared?"); + return DiscreteInterpolation(); + } + + const bool p0_smaller = (ptr0->list.size() < ptr1->list.size()); + auto& small = (p0_smaller ? ptr0->list : ptr1->list); + auto& big = (p0_smaller ? ptr1->list : ptr0->list); + + // Build the new, interpolated decorator. + UniquePtr decorator(new DecoratorDeclarationList); + decorator->list.reserve(ptr0->list.size()); + + // Interpolate decorators that have common types. + for (size_t i = 0; i < small.size(); i++) + { + DecoratorDeclarationView d0_view{ GetDecoratorDeclarationView(ptr0->list[i]) }; + DecoratorDeclarationView d1_view{ GetDecoratorDeclarationView(ptr1->list[i]) }; + + if (!d0_view.instancer || !d1_view.instancer) + return DiscreteInterpolation(); + + if (d0_view.instancer != d1_view.instancer || d0_view.type != d1_view.type || + d0_view.properties.GetNumProperties() != d1_view.properties.GetNumProperties()) + { + // Incompatible decorators, fall back to discrete interpolation. + return DiscreteInterpolation(); + } + + decorator->list.push_back(DecoratorDeclaration{ d0_view.type, d0_view.instancer, PropertyDictionary() }); + PropertyDictionary& props = decorator->list.back().properties; + + const auto& props0 = d0_view.properties.GetProperties(); + const auto& props1 = d1_view.properties.GetProperties(); + + for (const auto& pair0 : props0) + { + const PropertyId id = pair0.first; + const Property& prop0 = pair0.second; + + auto it = props1.find(id); + if (it == props1.end()) + { + RMLUI_ERRORMSG("Incompatible decorator properties."); + return DiscreteInterpolation(); + } + const Property& prop1 = it->second; + + Property p = InterpolateProperties(prop0, prop1, alpha, element, prop0.definition); + p.definition = prop0.definition; + props.SetProperty(id, p); + } + } + + // Append any trailing decorators from the largest list and interpolate against the default values of its type. + for (size_t i = small.size(); i < big.size(); i++) + { + DecoratorDeclarationView dbig_view{ GetDecoratorDeclarationView(big[i]) }; + + if (!dbig_view.instancer) + return DiscreteInterpolation(); + + decorator->list.push_back(DecoratorDeclaration{ dbig_view.type, dbig_view.instancer, PropertyDictionary() }); + DecoratorDeclaration& d_new = decorator->list.back(); + + const PropertySpecification& specification = d_new.instancer->GetPropertySpecification(); + + const PropertyMap& props_big = dbig_view.properties.GetProperties(); + for (const auto& pair_big : props_big) + { + const PropertyId id = pair_big.first; + const PropertyDefinition* underlying_definition = specification.GetProperty(id); + if (!underlying_definition) + return DiscreteInterpolation(); + + const Property& p_big = pair_big.second; + const Property& p_small = *underlying_definition->GetDefaultValue(); + const Property& p_interp0 = (p0_smaller ? p_small : p_big); + const Property& p_interp1 = (p0_smaller ? p_big : p_small); + + Property p = InterpolateProperties(p_interp0, p_interp1, alpha, element, p_big.definition); + p.definition = p_big.definition; + d_new.properties.SetProperty(id, p); + } + } + + return Property{ DecoratorsPtr(std::move(decorator)), Property::DECORATOR }; + } + // Fall back to discrete interpolation for incompatible units. return alpha < 0.5f ? p0 : p1; } @@ -392,6 +515,15 @@ static bool PrepareTransforms(Vector& keys, Element& element, int return (count_iterations < max_iterations); } +static void PrepareDecorator(AnimationKey& key) +{ + Property& property = key.property; + RMLUI_ASSERT(property.value.GetType() == Variant::DECORATORSPTR); + + if (!property.value.GetReference()) + property.value = MakeShared(); +} + ElementAnimation::ElementAnimation(PropertyId property_id, ElementAnimationOrigin origin, const Property& current_value, Element& element, double start_world_time, float duration, int num_iterations, bool alternate_direction) : @@ -409,7 +541,7 @@ ElementAnimation::ElementAnimation(PropertyId property_id, ElementAnimationOrigi bool ElementAnimation::InternalAddKey(float time, const Property& in_property, Element& element, Tween tween) { - int valid_properties = (Property::NUMBER_LENGTH_PERCENT | Property::ANGLE | Property::COLOUR | Property::TRANSFORM | Property::KEYWORD); + int valid_properties = (Property::NUMBER_LENGTH_PERCENT | Property::ANGLE | Property::COLOUR | Property::TRANSFORM | Property::KEYWORD | Property::DECORATOR); if (!(in_property.unit & valid_properties)) { @@ -424,6 +556,10 @@ bool ElementAnimation::InternalAddKey(float time, const Property& in_property, E { result = PrepareTransforms(keys, element, (int)keys.size() - 1); } + else if (keys.back().property.unit == Property::DECORATOR) + { + PrepareDecorator(keys.back()); + } if (!result) { diff --git a/Source/Core/ElementDecoration.cpp b/Source/Core/ElementDecoration.cpp index 1098904f3..03057c2a0 100644 --- a/Source/Core/ElementDecoration.cpp +++ b/Source/Core/ElementDecoration.cpp @@ -88,7 +88,7 @@ bool ElementDecoration::ReloadDecorators() } } - const auto& decorator_list = style_sheet->InstanceDecorators(*decorators_ptr, source); + const DecoratorPtrList& decorator_list = style_sheet->InstanceDecorators(*decorators_ptr, source); for (const SharedPtr& decorator : decorator_list) { diff --git a/Source/Core/PropertyParserDecorator.cpp b/Source/Core/PropertyParserDecorator.cpp index 31a6f85c0..a1423b463 100644 --- a/Source/Core/PropertyParserDecorator.cpp +++ b/Source/Core/PropertyParserDecorator.cpp @@ -52,19 +52,18 @@ bool PropertyParserDecorator::ParseValue(Property& property, const String& decor if (decorator_string_value.empty() || decorator_string_value == "none") { - property.value = Variant(); - property.unit = Property::UNKNOWN; + property.value = Variant(DecoratorsPtr()); + property.unit = Property::DECORATOR; return true; } RMLUI_ZoneScoped; - DecoratorDeclarationList decorators; - // Make sure we don't split inside the parenthesis since they may appear in decorator shorthands. StringList decorator_string_list; StringUtilities::ExpandString(decorator_string_list, decorator_string_value, ',', '(', ')'); + DecoratorDeclarationList decorators; decorators.value = decorator_string_value; decorators.list.reserve(decorator_string_list.size()); diff --git a/Source/Core/PropertySpecification.cpp b/Source/Core/PropertySpecification.cpp index b040abfc4..5545b0fe7 100644 --- a/Source/Core/PropertySpecification.cpp +++ b/Source/Core/PropertySpecification.cpp @@ -29,11 +29,12 @@ #include "../../Include/RmlUi/Core/PropertySpecification.h" #include "../../Include/RmlUi/Core/Debug.h" #include "../../Include/RmlUi/Core/Log.h" +#include "../../Include/RmlUi/Core/Profiling.h" #include "../../Include/RmlUi/Core/PropertyDefinition.h" #include "../../Include/RmlUi/Core/PropertyDictionary.h" -#include "../../Include/RmlUi/Core/Profiling.h" -#include "PropertyShorthandDefinition.h" #include "IdNameMap.h" +#include "PropertyShorthandDefinition.h" +#include #include #include @@ -432,13 +433,30 @@ void PropertySpecification::SetPropertyDefaults(PropertyDictionary& dictionary) } } -String PropertySpecification::PropertiesToString(const PropertyDictionary& dictionary) const +String PropertySpecification::PropertiesToString(const PropertyDictionary& dictionary, bool include_name, char delimiter) const { + const PropertyMap& properties = dictionary.GetProperties(); + + // For determinism we print the strings in order of increasing property ids. + Vector ids; + ids.reserve(properties.size()); + for (auto& pair : properties) + ids.push_back(pair.first); + + std::sort(ids.begin(), ids.end()); + String result; - for (auto& pair : dictionary.GetProperties()) + for (PropertyId id : ids) { - result += property_map->GetName(pair.first) + ": " + pair.second.ToString() + '\n'; + const Property& p = properties.find(id)->second; + if (include_name) + result += property_map->GetName(id) + ": "; + result += p.ToString() + delimiter; } + + if (!result.empty()) + result.pop_back(); + return result; } diff --git a/Source/Core/StyleSheet.cpp b/Source/Core/StyleSheet.cpp index 9bfa117a1..ba248ae28 100644 --- a/Source/Core/StyleSheet.cpp +++ b/Source/Core/StyleSheet.cpp @@ -103,8 +103,16 @@ void StyleSheet::BuildNodeIndex() root->BuildIndex(styled_node_index); } +const DecoratorSpecification* StyleSheet::GetDecoratorSpecification(const String& name) const +{ + auto it = decorator_map.find(name); + if (it != decorator_map.end()) + return &(it->second); + return nullptr; +} + // Returns the Keyframes of the given name, or null if it does not exist. -const Keyframes * StyleSheet::GetKeyframes(const String & name) const +const Keyframes* StyleSheet::GetKeyframes(const String & name) const { auto it = keyframes.find(name); if (it != keyframes.end()) @@ -112,47 +120,71 @@ const Keyframes * StyleSheet::GetKeyframes(const String & name) const return nullptr; } -const Vector>& StyleSheet::InstanceDecorators(const DecoratorDeclarationList& declaration_list, const PropertySource* source) const +const DecoratorPtrList& StyleSheet::InstanceDecorators(const DecoratorDeclarationList& declaration_list, const PropertySource* source) const { + RMLUI_ASSERT_NONRECURSIVE; // Since we may return a reference to the below static variable. + static DecoratorPtrList non_cached_decorator_list; + + // Empty declaration values are used for interpolated values which we don't want to cache. + const bool enable_cache = !declaration_list.value.empty(); + // Generate the cache key. Relative paths of textures may be affected by the source path, and ultimately // which texture should be displayed. Thus, we need to include this path in the cache key. String key; - key.reserve(declaration_list.value.size() + 1 + (source ? source->path.size() : 0)); - key = declaration_list.value; - key += ';'; - if (source) - key += source->path; - auto it_cache = decorator_cache.find(key); - if (it_cache != decorator_cache.end()) - return it_cache->second; + if (enable_cache) + { + key.reserve(declaration_list.value.size() + 1 + (source ? source->path.size() : 0)); + key = declaration_list.value; + key += ';'; + if (source) + key += source->path; + + auto it_cache = decorator_cache.find(key); + if (it_cache != decorator_cache.end()) + return it_cache->second; + } + else + { + non_cached_decorator_list.clear(); + } - Vector>& decorators = decorator_cache[key]; + DecoratorPtrList& decorators = enable_cache ? decorator_cache[key] : non_cached_decorator_list; + decorators.reserve(declaration_list.list.size()); for (const DecoratorDeclaration& declaration : declaration_list.list) { + SharedPtr decorator; + if (declaration.instancer) { RMLUI_ZoneScopedN("InstanceDecorator"); - - if (SharedPtr decorator = declaration.instancer->InstanceDecorator(declaration.type, declaration.properties, DecoratorInstancerInterface(*this, source))) - decorators.push_back(std::move(decorator)); - else - Log::Message(Log::LT_WARNING, "Decorator '%s' in '%s' could not be instanced, declared at %s:%d", declaration.type.c_str(), declaration_list.value.c_str(), source ? source->path.c_str() : "", source ? source->line_number : -1); + decorator = + declaration.instancer->InstanceDecorator(declaration.type, declaration.properties, DecoratorInstancerInterface(*this, source)); + + if (!decorator) + Log::Message(Log::LT_WARNING, "Decorator '%s' in '%s' could not be instanced, declared at %s:%d", declaration.type.c_str(), + declaration_list.value.c_str(), source ? source->path.c_str() : "", source ? source->line_number : -1); } else { // If we have no instancer, this means the type is the name of an @decorator rule. - SharedPtr decorator; auto it_map = decorator_map.find(declaration.type); if (it_map != decorator_map.end()) decorator = it_map->second.decorator; - if (decorator) - decorators.push_back(std::move(decorator)); - else - Log::Message(Log::LT_WARNING, "Decorator name '%s' could not be found in any @decorator rule, declared at %s:%d", declaration.type.c_str(), source ? source->path.c_str() : "", source ? source->line_number : -1); + if (!decorator) + Log::Message(Log::LT_WARNING, "Decorator name '%s' could not be found in any @decorator rule, declared at %s:%d", + declaration.type.c_str(), source ? source->path.c_str() : "", source ? source->line_number : -1); } + + if (!decorator) + { + decorators.clear(); + break; + } + + decorators.push_back(std::move(decorator)); } return decorators; diff --git a/Source/Core/TypeConverter.cpp b/Source/Core/TypeConverter.cpp index cfed64ccc..6f9580ea1 100644 --- a/Source/Core/TypeConverter.cpp +++ b/Source/Core/TypeConverter.cpp @@ -30,7 +30,9 @@ #include "../../Include/RmlUi/Core/StyleSheetSpecification.h" #include "../../Include/RmlUi/Core/StyleSheetTypes.h" #include "../../Include/RmlUi/Core/Animation.h" +#include "../../Include/RmlUi/Core/DecoratorInstancer.h" #include "../../Include/RmlUi/Core/Transform.h" +#include "../../Include/RmlUi/Core/PropertySpecification.h" #include "../../Include/RmlUi/Core/TransformPrimitive.h" #include "../../Include/RmlUi/Core/PropertyDictionary.h" #include "TransformUtilities.h" @@ -127,8 +129,23 @@ bool TypeConverter::Convert(const DecoratorsPtr& src, Str { if (!src || src->list.empty()) dest = "none"; - else + else if (!src->value.empty()) dest += src->value; + else + { + dest.clear(); + for (const DecoratorDeclaration& declaration : src->list) + { + dest += declaration.type; + if (auto instancer = declaration.instancer) + { + dest += '(' + instancer->GetPropertySpecification().PropertiesToString(declaration.properties, false, ' ') + ')'; + } + dest += ", "; + } + if (dest.size() > 2) + dest.resize(dest.size() - 2); + } return true; } diff --git a/Tests/Source/Common/TestsInterface.cpp b/Tests/Source/Common/TestsInterface.cpp index 198aaa63e..7668c16eb 100644 --- a/Tests/Source/Common/TestsInterface.cpp +++ b/Tests/Source/Common/TestsInterface.cpp @@ -33,7 +33,7 @@ double TestsSystemInterface::GetElapsedTime() { - return 0.0; + return elapsed_time; } bool TestsSystemInterface::LogMessage(Rml::Log::Type type, const Rml::String& message) @@ -78,6 +78,11 @@ void TestsSystemInterface::SetNumExpectedWarnings(int in_num_expected_warnings) num_expected_warnings = in_num_expected_warnings; } +void TestsSystemInterface::SetTime(double t) +{ + elapsed_time = t; +} + void TestsRenderInterface::RenderGeometry(Rml::Vertex* /*vertices*/, int /*num_vertices*/, int* /*indices*/, int /*num_indices*/, const Rml::TextureHandle /*texture*/, const Rml::Vector2f& /*translation*/) { counters.render_calls += 1; diff --git a/Tests/Source/Common/TestsInterface.h b/Tests/Source/Common/TestsInterface.h index c810529e3..6ad8c4609 100644 --- a/Tests/Source/Common/TestsInterface.h +++ b/Tests/Source/Common/TestsInterface.h @@ -43,7 +43,11 @@ class TestsSystemInterface : public Rml::SystemInterface { // warnings and errors until the next call. void SetNumExpectedWarnings(int num_expected_warnings); + void SetTime(double t); + private: + double elapsed_time = 0.0; + int num_logged_warnings = 0; int num_expected_warnings = 0; diff --git a/Tests/Source/Common/TestsShell.cpp b/Tests/Source/Common/TestsShell.cpp index 6df733618..8fa7f81bd 100644 --- a/Tests/Source/Common/TestsShell.cpp +++ b/Tests/Source/Common/TestsShell.cpp @@ -226,3 +226,8 @@ TestsRenderInterface* TestsShell::GetTestsRenderInterface() return &shell_render_interface; #endif } + +TestsSystemInterface* TestsShell::GetTestsSystemInterface() +{ + return &tests_system_interface; +} \ No newline at end of file diff --git a/Tests/Source/Common/TestsShell.h b/Tests/Source/Common/TestsShell.h index c6a665669..c610ff003 100644 --- a/Tests/Source/Common/TestsShell.h +++ b/Tests/Source/Common/TestsShell.h @@ -32,6 +32,7 @@ #include namespace Rml { class RenderInterface; } class TestsRenderInterface; +class TestsSystemInterface; namespace TestsShell { @@ -52,11 +53,14 @@ namespace TestsShell { // or until 'ShutdownShell()'. void SetNumExpectedWarnings(int num_warnings); + void SetTime(double t); + // Stats only available for the dummy renderer. Rml::String GetRenderStats(); // Returns nullptr if the dummy renderer is not being used. TestsRenderInterface* GetTestsRenderInterface(); + TestsSystemInterface* GetTestsSystemInterface(); } #endif diff --git a/Tests/Source/UnitTests/Animation.cpp b/Tests/Source/UnitTests/Animation.cpp new file mode 100644 index 000000000..07da8862d --- /dev/null +++ b/Tests/Source/UnitTests/Animation.cpp @@ -0,0 +1,221 @@ +/* + * 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 "../Common/TestsInterface.h" +#include "../Common/TestsShell.h" +#include +#include +#include +#include + +using namespace Rml; + +static const String document_decorator_rml = R"( + + + Test + + + + + +
+ + +)"; + +TEST_CASE("animation.decorator") +{ + struct Test { + String from_rule; + String to_rule; + String from; + String to; + String expected_25p; // expected interpolated value at 25% progression + }; + + Vector tests{ + // Only standard declaration + { + "", "", + + "gradient(horizontal transparent transparent)", + "gradient(horizontal white white)", + + "gradient(horizontal rgba(255,255,255,63) rgba(255,255,255,63))", + }, + { + "", "", + + "none", + "gradient(horizontal transparent transparent)", + + "gradient(horizontal rgba(255,255,255,191) rgba(255,255,255,191))", + }, + { + "", "", + + "none", + "gradient(horizontal transparent transparent), gradient(vertical transparent transparent)", + + "gradient(horizontal rgba(255,255,255,191) rgba(255,255,255,191)), gradient(horizontal rgba(255,255,255,191) rgba(255,255,255,191))", + }, + { + "", "", + + "gradient(horizontal transparent transparent), gradient(vertical transparent transparent)", + "none", + + "gradient(horizontal rgba(255,255,255,63) rgba(255,255,255,63)), gradient(vertical rgba(255,255,255,63) rgba(255,255,255,63))", + }, + + /// Only rule declaration + { + "direction: horizontal; start-color: transparent; stop-color: transparent;", + "direction: horizontal; start-color: white; stop-color: white;", + + "from_rule", + "to_rule", + + "gradient(horizontal rgba(255,255,255,63) rgba(255,255,255,63))", + }, + { + "", + "direction: horizontal; start-color: transparent; stop-color: transparent;", + + "from_rule", + "to_rule", + + "gradient(horizontal rgba(255,255,255,191) rgba(255,255,255,191))", + }, + { + "direction: vertical; start-color: transparent; stop-color: transparent;", + "", + + "from_rule", + "to_rule", + + "gradient(vertical rgba(255,255,255,63) rgba(255,255,255,63))", + }, + + /// Mix rule and standard declaration + { + "direction: horizontal; start-color: transparent; stop-color: transparent;", + "", + + "from_rule", + "gradient(horizontal white white)", + + "gradient(horizontal rgba(255,255,255,63) rgba(255,255,255,63))", + }, + { + "", + "direction: horizontal; start-color: transparent; stop-color: transparent;", + + "none", + "to_rule", + + "gradient(horizontal rgba(255,255,255,191) rgba(255,255,255,191))", + }, + { + "direction: vertical; start-color: transparent; stop-color: transparent;", + "", + + "from_rule", + "none", + + "gradient(vertical rgba(255,255,255,63) rgba(255,255,255,63))", + }, + { + "", "", + + "from_rule, to_rule", + "gradient(horizontal transparent transparent), gradient(vertical transparent transparent)", + + "gradient(horizontal rgba(255,255,255,191) rgba(255,255,255,191)), gradient(horizontal rgba(255,255,255,191) rgba(255,255,255,191))", + }, + { + "", "", + + "gradient(horizontal transparent transparent), gradient(vertical transparent transparent)", + "from_rule, to_rule", + + "gradient(horizontal rgba(255,255,255,63) rgba(255,255,255,63)), gradient(vertical rgba(255,255,255,63) rgba(255,255,255,63))", + }, + }; + + TestsSystemInterface* system_interface = TestsShell::GetTestsSystemInterface(); + Context* context = TestsShell::GetContext(); + context->SetDensityIndependentPixelRatio(2.0f); + + for (const Test& test : tests) + { + const double t_final = 0.1; + + system_interface->SetTime(0.0); + String document_rml = Rml::CreateString(document_decorator_rml.size() + 512, document_decorator_rml.c_str(), test.from_rule.c_str(), + test.to_rule.c_str(), test.from.c_str(), test.to.c_str()); + + ElementDocument* document = context->LoadDocumentFromMemory(document_rml, "assets/"); + Element* element = document->GetChild(0); + + document->Show(); + TestsShell::RenderLoop(); + + system_interface->SetTime(0.25 * t_final); + TestsShell::RenderLoop(); + CHECK_MESSAGE(element->GetProperty("decorator") == test.expected_25p, "from: ", test.from, ", to: ", test.to); + + document->Close(); + } + + system_interface->SetTime(0.0); + + TestsShell::ShutdownShell(); +} \ No newline at end of file