Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic animation support for decorators. #421

Merged
merged 11 commits into from
Mar 7, 2023
2 changes: 1 addition & 1 deletion Include/RmlUi/Core/PropertySpecification.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<PropertyDefinition> >;
Expand Down
7 changes: 6 additions & 1 deletion Include/RmlUi/Core/StyleSheet.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ class StyleSheetParser;
struct PropertySource;
struct Sprite;

using DecoratorPtrList = Vector<SharedPtr<const Decorator>>;

/**
StyleSheet maintains a single stylesheet definition. A stylesheet can be combined with another stylesheet to create
a new, merged stylesheet.
Expand All @@ -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;
Expand All @@ -78,7 +83,7 @@ class RMLUICORE_API StyleSheet final : public NonCopyMoveable
SharedPtr<const ElementDefinition> GetElementDefinition(const Element* element) const;

/// Returns a list of instanced decorators from the declarations. The instances are cached for faster future retrieval.
const Vector<SharedPtr<const Decorator>>& InstanceDecorators(const DecoratorDeclarationList& declaration_list, const PropertySource* decorator_source) const;
const DecoratorPtrList& InstanceDecorators(const DecoratorDeclarationList& declaration_list, const PropertySource* decorator_source) const;

private:
StyleSheet();
Expand Down
139 changes: 136 additions & 3 deletions Source/Core/ElementAnimation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -165,6 +170,121 @@ 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; };
auto PrepareDeclaration = [&](const DecoratorDeclaration& declaration) {
if (declaration.instancer)
mikke89 marked this conversation as resolved.
Show resolved Hide resolved
return declaration;

// Maybe it needs to be taken out of the lambda?
const StyleSheet* style_sheet = element.GetStyleSheet();
if (!style_sheet)
{
RMLUI_ERRORMSG("style_sheet is empty.");
return declaration;
}

const DecoratorSpecification* specification = style_sheet->GetDecoratorSpecification(declaration.type);
if (!specification)
{
RMLUI_ERRORMSG("Invalid DecoratorSpecification pointer.");
mikke89 marked this conversation as resolved.
Show resolved Hide resolved
return declaration;
}

return DecoratorDeclaration{ specification->decorator_type, Factory::GetDecoratorInstancer(specification->decorator_type), specification->properties };
};

auto& ptr0 = p0.value.GetReference<DecoratorsPtr>();
auto& ptr1 = p1.value.GetReference<DecoratorsPtr>();
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<DecoratorDeclarationList> decorator(new DecoratorDeclarationList);
decorator->list.reserve(ptr0->list.size());

// Interpolate decorators that have common types.
for (size_t i = 0; i < small.size(); i++)
{
const DecoratorDeclaration& d0 = PrepareDeclaration(ptr0->list[i]);
const DecoratorDeclaration& d1 = PrepareDeclaration(ptr1->list[i]);
mikke89 marked this conversation as resolved.
Show resolved Hide resolved
if(!d0.instancer || !d1.instancer)
{
return DiscreteInterpolation();
}

if (d0.instancer != d1.instancer || d0.type != d1.type ||
d0.properties.GetNumProperties() != d1.properties.GetNumProperties())
{
// Incompatible decorators, fall back to discrete interpolation.
return DiscreteInterpolation();
}

decorator->list.push_back(DecoratorDeclaration{ d0.type, d0.instancer, PropertyDictionary() });
PropertyDictionary& props = decorator->list.back().properties;

const auto& props0 = d0.properties.GetProperties();
const auto& props1 = d1.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++)
{
const DecoratorDeclaration& d_big = PrepareDeclaration(big[i]);

decorator->list.push_back(DecoratorDeclaration{ d_big.type, d_big.instancer, PropertyDictionary() });
DecoratorDeclaration& d_new = decorator->list.back();

const PropertySpecification& specification = d_new.instancer->GetPropertySpecification();

const PropertyMap& props_big = d_big.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;
}
Expand Down Expand Up @@ -392,6 +512,15 @@ static bool PrepareTransforms(Vector<AnimationKey>& 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<DecoratorsPtr>())
property.value = MakeShared<DecoratorDeclarationList>();
}


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) :
Expand All @@ -409,7 +538,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))
{
Expand All @@ -424,6 +553,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)
{
Expand Down
2 changes: 1 addition & 1 deletion Source/Core/ElementDecoration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<const Decorator>& decorator : decorator_list)
{
Expand Down
7 changes: 3 additions & 4 deletions Source/Core/PropertyParserDecorator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand Down
28 changes: 23 additions & 5 deletions Source/Core/PropertySpecification.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <algorithm>
#include <limits.h>
#include <stdint.h>

Expand Down Expand Up @@ -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<PropertyId> 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;
}

Expand Down
Loading