Skip to content

Commit

Permalink
Animation support for decorators (#421)
Browse files Browse the repository at this point in the history
Co-authored-by: Michael R. P. Ragazzon <mikke89@users.noreply.github.com>
  • Loading branch information
0suddenly0 and mikke89 committed Mar 7, 2023
1 parent d129fb8 commit e850073
Show file tree
Hide file tree
Showing 14 changed files with 495 additions and 38 deletions.
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
11 changes: 11 additions & 0 deletions Include/RmlUi/Core/StyleSheetTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#define RMLUI_CORE_STYLESHEETTYPES_H

#include "PropertyDictionary.h"
#include "Factory.h"
#include "Types.h"
#include "Utilities.h"

Expand Down Expand Up @@ -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<DecoratorDeclaration> list;
String value;
Expand Down
142 changes: 139 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,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<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++)
{
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;
}
Expand Down Expand Up @@ -392,6 +515,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 +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))
{
Expand All @@ -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)
{
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

0 comments on commit e850073

Please sign in to comment.