Browse Source

- Decorators and font effects are now parsed by separate property parsers.
- Decorator textures are now fetched from the decorator interface, instead of using the property source.
- Decorators are now instanced when used in each element.

Due to the introduction of media queries, active sprites in a style sheets can now be changed at any time. Thus, we can no longer use the old trick of instancing all the decorators as soon as the style sheet is parsed. However, to ensure that performance is still good, I noticed that most of the work during decorator instancing was actually the parsing step. So now, we parse the decorator at the earliest opportunity, and then store the parsed properties of the decorator (DecoratorDeclaration). Then we instance the decorator later on once needed. The instancing step is actually quite fast once parsed, so it's okay now to delay (and possibly repeat) this step when needed.

Michael Ragazzon 4 years ago
parent
commit
79fb3b5405

+ 4 - 0
CMake/FileList.cmake

@@ -74,6 +74,8 @@ set(Core_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertiesIterator.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertiesIterator.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserAnimation.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserAnimation.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserColour.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserColour.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserDecorator.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserFontEffect.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserKeyword.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserKeyword.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserNumber.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserNumber.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserRatio.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserRatio.h
@@ -352,6 +354,8 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyDictionary.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyDictionary.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserAnimation.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserAnimation.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserColour.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserColour.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserDecorator.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserFontEffect.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserKeyword.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserKeyword.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserNumber.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserNumber.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserRatio.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserRatio.cpp

+ 2 - 2
Include/RmlUi/Core/ComputedValues.h

@@ -214,8 +214,8 @@ struct ComputedValues
 	TransitionList transition;
 	TransitionList transition;
 	AnimationList animation;
 	AnimationList animation;
 
 
-	DecoratorsPtr decorator;
-	FontEffectsPtr font_effect; // Sorted by layer first (back then front), then by declaration order.
+	bool has_decorator = false;
+	bool has_font_effect = false;
 };
 };
 }
 }
 
 

+ 7 - 1
Include/RmlUi/Core/DecoratorInstancer.h

@@ -36,6 +36,7 @@
 namespace Rml {
 namespace Rml {
 
 
 struct Sprite;
 struct Sprite;
+struct Texture;
 class StyleSheet;
 class StyleSheet;
 class Decorator;
 class Decorator;
 class DecoratorInstancerInterface;
 class DecoratorInstancerInterface;
@@ -86,13 +87,18 @@ private:
 
 
 class RMLUICORE_API DecoratorInstancerInterface {
 class RMLUICORE_API DecoratorInstancerInterface {
 public:
 public:
-	DecoratorInstancerInterface(const StyleSheet& style_sheet) : style_sheet(style_sheet) {}
+	DecoratorInstancerInterface(const StyleSheet& style_sheet, const PropertySource* property_source) : style_sheet(style_sheet), property_source(property_source) {}
 
 
 	/// Get a sprite from any @spritesheet in the style sheet the decorator is being instanced on.
 	/// Get a sprite from any @spritesheet in the style sheet the decorator is being instanced on.
 	const Sprite* GetSprite(const String& name) const;
 	const Sprite* GetSprite(const String& name) const;
 
 
+	/// Get the texture from a filename set in the decorator property.
+	/// This will use the document path where the 'decorator' property was declared to locate relative files, if available.
+	Texture GetTexture(const String& filename) const;
+
 private:
 private:
 	const StyleSheet& style_sheet;
 	const StyleSheet& style_sheet;
+	const PropertySource* property_source;
 };
 };
 
 
 } // namespace Rml
 } // namespace Rml

+ 2 - 0
Include/RmlUi/Core/Element.h

@@ -646,6 +646,8 @@ protected:
 
 
 	void Release() override;
 	void Release() override;
 
 
+	void DirtyDecoratorsRecursive();
+
 private:
 private:
 	void SetParent(Element* parent);
 	void SetParent(Element* parent);
 	
 	

+ 6 - 17
Include/RmlUi/Core/StyleSheet.h

@@ -65,26 +65,16 @@ public:
 
 
 	/// Combines this style sheet with another one, producing a new sheet.
 	/// Combines this style sheet with another one, producing a new sheet.
 	UniquePtr<StyleSheet> CombineStyleSheet(const StyleSheet& sheet) const;
 	UniquePtr<StyleSheet> CombineStyleSheet(const StyleSheet& sheet) const;
-
-	/// Creates an exact copy of this style sheet.
-	UniquePtr<StyleSheet> Clone() const;
+	/// Merges another style sheet into this.
+	void MergeStyleSheet(const StyleSheet& sheet);
 
 
 	/// Builds the node index for a combined style sheet.
 	/// Builds the node index for a combined style sheet.
 	void BuildNodeIndex();
 	void BuildNodeIndex();
-	/// Optimizes some properties for faster retrieval.
-	/// Specifically, converts all decorator and font-effect properties from strings to instanced decorator and font effect lists.
-	void OptimizeNodeProperties();
 
 
 	/// Returns the Keyframes of the given name, or null if it does not exist.
 	/// 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.
 	/// @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;
 	const Keyframes* GetKeyframes(const String& name) const;
 
 
-	/// Parses the decorator property from a string and returns a list of instanced decorators.
-	DecoratorsPtr InstanceDecoratorsFromString(const String& decorator_string_value, const SharedPtr<const PropertySource>& source) const;
-
-	/// Parses the font-effect property from a string and returns a list of instanced font-effects.
-	FontEffectsPtr InstanceFontEffectsFromString(const String& font_effect_string_value, const SharedPtr<const PropertySource>& source) const;
-
 	/// Get sprite located in any spritesheet within this stylesheet.
 	/// Get sprite located in any spritesheet within this stylesheet.
 	/// @lifetime The returned pointer becomes invalidated whenever the style sheet is re-generated. Do not store this pointer or references to subobjects around.
 	/// @lifetime The returned pointer becomes invalidated whenever the style sheet is re-generated. Do not store this pointer or references to subobjects around.
 	const Sprite* GetSprite(const String& name) const;
 	const Sprite* GetSprite(const String& name) const;
@@ -95,14 +85,12 @@ public:
 	/// Retrieve the hash key used to look-up applicable nodes in the node index.
 	/// Retrieve the hash key used to look-up applicable nodes in the node index.
 	static size_t NodeHash(const String& tag, const String& id);
 	static size_t NodeHash(const String& tag, const String& id);
 
 
+	/// Returns the @decorator of the given name, or null if it does not exist.
+	SharedPtr<Decorator> GetDecorator(const String& name) const;
+
 private:
 private:
 	StyleSheet();
 	StyleSheet();
 
 
-	using ElementDefinitionCache = UnorderedMap< size_t, SharedPtr<ElementDefinition> >;
-
-	/// Returns the Decorator of the given name, or null if it does not exist.
-	SharedPtr<Decorator> GetDecorator(const String& name) const;
-	
 	// Root level node, attributes from special nodes like "body" get added to this node
 	// Root level node, attributes from special nodes like "body" get added to this node
 	UniquePtr<StyleSheetNode> root;
 	UniquePtr<StyleSheetNode> root;
 
 
@@ -126,6 +114,7 @@ private:
 	NodeIndex styled_node_index;
 	NodeIndex styled_node_index;
 
 
 	// Index of node sets to element definitions.
 	// Index of node sets to element definitions.
+	using ElementDefinitionCache = UnorderedMap< size_t, SharedPtr<ElementDefinition> >;
 	mutable ElementDefinitionCache node_cache;
 	mutable ElementDefinitionCache node_cache;
 
 
 	friend Rml::StyleSheetParser;
 	friend Rml::StyleSheetParser;

+ 1 - 5
Include/RmlUi/Core/StyleSheetContainer.h

@@ -30,7 +30,6 @@
 #define RMLUI_CORE_STYLESHEETCONTAINER_H
 #define RMLUI_CORE_STYLESHEETCONTAINER_H
 
 
 #include "Traits.h"
 #include "Traits.h"
-#include "PropertyDictionary.h"
 #include "StyleSheetTypes.h"
 #include "StyleSheetTypes.h"
 
 
 namespace Rml {
 namespace Rml {
@@ -70,11 +69,8 @@ public:
 	/// Merge another style sheet container into this.
 	/// Merge another style sheet container into this.
 	void MergeStyleSheetContainer(const StyleSheetContainer& container);
 	void MergeStyleSheetContainer(const StyleSheetContainer& container);
 
 
-	/// Optimizes properties of the underlying style sheet(s) for faster retrieval.
-	void OptimizeNodeProperties();
-
 private:
 private:
-	Vector<MediaBlock> media_blocks;
+	MediaBlockList media_blocks;
 
 
 	StyleSheet* compiled_style_sheet = nullptr;
 	StyleSheet* compiled_style_sheet = nullptr;
 	UniquePtr<StyleSheet> combined_compiled_style_sheet;
 	UniquePtr<StyleSheet> combined_compiled_style_sheet;

+ 17 - 5
Include/RmlUi/Core/StyleSheetTypes.h

@@ -29,12 +29,13 @@
 #ifndef RMLUI_CORE_STYLESHEETTYPES_H
 #ifndef RMLUI_CORE_STYLESHEETTYPES_H
 #define RMLUI_CORE_STYLESHEETTYPES_H
 #define RMLUI_CORE_STYLESHEETTYPES_H
 
 
-#include "Traits.h"
+#include "Types.h"
 #include "PropertyDictionary.h"
 #include "PropertyDictionary.h"
-#include "Spritesheet.h"
 
 
 namespace Rml {
 namespace Rml {
 
 
+class Decorator;
+class DecoratorInstancer;
 class StyleSheet;
 class StyleSheet;
 
 
 struct KeyframeBlock {
 struct KeyframeBlock {
@@ -55,13 +56,24 @@ struct DecoratorSpecification {
 };
 };
 using DecoratorSpecificationMap = UnorderedMap<String, DecoratorSpecification>;
 using DecoratorSpecificationMap = UnorderedMap<String, DecoratorSpecification>;
 
 
+struct DecoratorDeclaration {
+	String type;
+	DecoratorInstancer* instancer;
+	PropertyDictionary properties;
+};
+struct DecoratorDeclarationList {
+	Vector<DecoratorDeclaration> list;
+	String value;
+};
+
 struct MediaBlock {
 struct MediaBlock {
 	MediaBlock() {}
 	MediaBlock() {}
-	MediaBlock(PropertyDictionary _properties, UniquePtr<StyleSheet> _stylesheet) : properties(std::move(_properties)), stylesheet(std::move(_stylesheet)) {}
+	MediaBlock(PropertyDictionary _properties, SharedPtr<StyleSheet> _stylesheet) : properties(std::move(_properties)), stylesheet(std::move(_stylesheet)) {}
 
 
-	PropertyDictionary properties;
-	UniquePtr<StyleSheet> stylesheet;
+	PropertyDictionary properties; // Media query properties
+	SharedPtr<StyleSheet> stylesheet;
 };
 };
+using MediaBlockList = Vector<MediaBlock>;
 
 
 } // namespace Rml
 } // namespace Rml
 #endif
 #endif

+ 2 - 7
Include/RmlUi/Core/Types.h

@@ -85,6 +85,7 @@ class FontEffect;
 struct Animation;
 struct Animation;
 struct Transition;
 struct Transition;
 struct TransitionList;
 struct TransitionList;
+struct DecoratorDeclarationList;
 struct Rectangle;
 struct Rectangle;
 enum class EventId : uint16_t;
 enum class EventId : uint16_t;
 enum class PropertyId : uint8_t;
 enum class PropertyId : uint8_t;
@@ -117,13 +118,7 @@ using ElementAttributes = Dictionary;
 using XMLAttributes = Dictionary;
 using XMLAttributes = Dictionary;
 
 
 using AnimationList = Vector<Animation>;
 using AnimationList = Vector<Animation>;
-using DecoratorList = Vector<SharedPtr<const Decorator>>;
 using FontEffectList = Vector<SharedPtr<const FontEffect>>;
 using FontEffectList = Vector<SharedPtr<const FontEffect>>;
-
-struct Decorators {
-	DecoratorList list;
-	String value;
-};
 struct FontEffects {
 struct FontEffects {
 	FontEffectList list;
 	FontEffectList list;
 	String value;
 	String value;
@@ -131,7 +126,7 @@ struct FontEffects {
 
 
 // Additional smart pointers
 // Additional smart pointers
 using TransformPtr = SharedPtr< Transform >;
 using TransformPtr = SharedPtr< Transform >;
-using DecoratorsPtr = SharedPtr<const Decorators>;
+using DecoratorsPtr = SharedPtr<const DecoratorDeclarationList>;
 using FontEffectsPtr = SharedPtr<const FontEffects>;
 using FontEffectsPtr = SharedPtr<const FontEffects>;
 
 
 // Data binding types
 // Data binding types

+ 15 - 0
Source/Core/DecoratorInstancer.cpp

@@ -62,4 +62,19 @@ const Sprite* DecoratorInstancerInterface::GetSprite(const String& name) const {
 	return style_sheet.GetSprite(name);
 	return style_sheet.GetSprite(name);
 }
 }
 
 
+Texture DecoratorInstancerInterface::GetTexture(const String& filename) const
+{
+	Texture texture;
+
+	if (!property_source)
+	{
+		Log::Message(Log::LT_WARNING, "Texture name '%s' in decorator could not be loaded, no property source available.", filename.c_str());
+		return texture;
+	}
+
+	texture.Set(filename, property_source->path);
+
+	return texture;
+}
+
 } // namespace Rml
 } // namespace Rml

+ 1 - 0
Source/Core/DecoratorTiled.cpp

@@ -28,6 +28,7 @@
 
 
 #include "DecoratorTiled.h"
 #include "DecoratorTiled.h"
 #include "../../Include/RmlUi/Core/Element.h"
 #include "../../Include/RmlUi/Core/Element.h"
+#include "../../Include/RmlUi/Core/ElementUtilities.h"
 #include "../../Include/RmlUi/Core/Math.h"
 #include "../../Include/RmlUi/Core/Math.h"
 #include "../../Include/RmlUi/Core/GeometryUtilities.h"
 #include "../../Include/RmlUi/Core/GeometryUtilities.h"
 #include "../../Include/RmlUi/Core/ElementUtilities.h"
 #include "../../Include/RmlUi/Core/ElementUtilities.h"

+ 6 - 8
Source/Core/DecoratorTiledInstancer.cpp

@@ -127,18 +127,16 @@ bool DecoratorTiledInstancer::GetTileProperties(DecoratorTiled::Tile* tiles, Tex
 			{
 			{
 				texture = previous_texture;
 				texture = previous_texture;
 			}
 			}
-			else if (src_property->source)
+			else
 			{
 			{
-				texture.Set(texture_name, src_property->source->path);
+				texture = instancer_interface.GetTexture(texture_name);
+
+				if (!texture)
+					return false;
+
 				previous_texture_name = texture_name;
 				previous_texture_name = texture_name;
 				previous_texture = texture;
 				previous_texture = texture;
 			}
 			}
-			else
-			{
-				auto& source = src_property->source;
-				Log::Message(Log::LT_WARNING, "Texture name '%s' is neither a valid sprite name nor a texture file. Specified in decorator at %s:%d.", texture_name.c_str(), source ? source->path.c_str() : "", source ? source->line_number : -1);
-				return false;
-			}
 		}
 		}
 
 
 		if (ids.fit != PropertyId::Invalid)
 		if (ids.fit != PropertyId::Invalid)

+ 10 - 0
Source/Core/Element.cpp

@@ -2805,4 +2805,14 @@ void Element::DirtyDpRatio()
 		GetChild(i)->DirtyDpRatio();
 		GetChild(i)->DirtyDpRatio();
 }
 }
 
 
+void Element::DirtyDecoratorsRecursive()
+{
+	GetElementDecoration()->DirtyDecorators();
+
+	// Now dirty all of our descendant decorators as well.
+	const int num_children = GetNumChildren(true);
+	for (int i = 0; i < num_children; ++i)
+		GetChild(i)->DirtyDecoratorsRecursive();
+}
+
 } // namespace Rml
 } // namespace Rml

+ 47 - 6
Source/Core/ElementDecoration.cpp

@@ -27,10 +27,12 @@
  */
  */
 
 
 #include "ElementDecoration.h"
 #include "ElementDecoration.h"
-#include "ElementDefinition.h"
 #include "../../Include/RmlUi/Core/Decorator.h"
 #include "../../Include/RmlUi/Core/Decorator.h"
 #include "../../Include/RmlUi/Core/Element.h"
 #include "../../Include/RmlUi/Core/Element.h"
+#include "../../Include/RmlUi/Core/ElementDocument.h"
 #include "../../Include/RmlUi/Core/Profiling.h"
 #include "../../Include/RmlUi/Core/Profiling.h"
+#include "../../Include/RmlUi/Core/DecoratorInstancer.h"
+#include "../../Include/RmlUi/Core/StyleSheet.h"
 
 
 namespace Rml {
 namespace Rml {
 
 
@@ -51,15 +53,54 @@ bool ElementDecoration::ReloadDecorators()
 	RMLUI_ZoneScopedC(0xB22222);
 	RMLUI_ZoneScopedC(0xB22222);
 	ReleaseDecorators();
 	ReleaseDecorators();
 
 
-	const Decorators* decorators = element->GetComputedValues().decorator.get();
-	if (!decorators)
+	if (!element->GetComputedValues().has_decorator)
 		return true;
 		return true;
 
 
-	for (const auto& decorator : decorators->list)
+	const Property* property = element->GetLocalProperty(PropertyId::Decorator);
+	if (!property || property->unit != Property::DECORATOR)
+		return false;
+
+	DecoratorsPtr decorators = property->Get<DecoratorsPtr>();
+	if (!decorators)
+		return false;
+
+	const StyleSheet* style_sheet = element->GetStyleSheet();
+	if (!style_sheet)
+		return false;
+
+	PropertySource document_source("", 0, "");
+	const PropertySource* source = property->source.get();
+
+	if (!source)
 	{
 	{
-		if (decorator)
+		if (ElementDocument* document = element->GetOwnerDocument())
 		{
 		{
-			LoadDecorator(decorator);
+			document_source.path = document->GetSourceURL();
+			source = &document_source;
+		}
+	}
+
+	for (const DecoratorDeclaration& decorator : decorators->list)
+	{
+		if (decorator.instancer)
+		{
+			RMLUI_ZoneScopedN("InstanceDecorator");
+			SharedPtr<const Decorator> decorator_instance = decorator.instancer->InstanceDecorator(decorator.type, decorator.properties, DecoratorInstancerInterface(*style_sheet, source));
+
+			if (decorator_instance)
+				LoadDecorator(std::move(decorator_instance));
+			else
+				Log::Message(Log::LT_WARNING, "Decorator '%s' in '%s' could not be instanced, declared at %s:%d", decorator.type.c_str(), decorators->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<const Decorator> decorator_instance = style_sheet->GetDecorator(decorator.type);
+
+			if (decorator_instance)
+				LoadDecorator(std::move(decorator_instance));
+			else
+				Log::Message(Log::LT_WARNING, "Decorator name '%s' could not be found in any @decorator rule, declared at %s:%d", decorator.type.c_str(), source ? source->path.c_str() : "", source ? source->line_number : -1);
 		}
 		}
 	}
 	}
 
 

+ 3 - 0
Source/Core/ElementDocument.cpp

@@ -238,7 +238,10 @@ void ElementDocument::DirtyMediaQueries()
 		const bool changed_style_sheet = style_sheet_container->UpdateCompiledStyleSheet(context->GetDensityIndependentPixelRatio(), Vector2f(context->GetDimensions()));
 		const bool changed_style_sheet = style_sheet_container->UpdateCompiledStyleSheet(context->GetDensityIndependentPixelRatio(), Vector2f(context->GetDimensions()));
 
 
 		if (changed_style_sheet)
 		if (changed_style_sheet)
+		{
 			GetStyle()->DirtyDefinition();
 			GetStyle()->DirtyDefinition();
+			DirtyDecoratorsRecursive();
+		}
 	}
 	}
 }
 }
 
 

+ 4 - 45
Source/Core/ElementStyle.cpp

@@ -641,7 +641,7 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S
 
 
 		values.pointer_events = parent_values->pointer_events;
 		values.pointer_events = parent_values->pointer_events;
 		
 		
-		values.font_effect = parent_values->font_effect;
+		values.has_font_effect = parent_values->has_font_effect;
 	}
 	}
 
 
 
 
@@ -901,53 +901,12 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S
 			break;
 			break;
 
 
 		case PropertyId::Decorator:
 		case PropertyId::Decorator:
-			if (p->unit == Property::DECORATOR)
-			{
-				values.decorator = p->Get<DecoratorsPtr>();
-			}
-			else if (p->unit == Property::STRING)
-			{
-				// Usually the decorator is converted from string after the style sheet is set on the ElementDocument. However, if the
-				// user sets a decorator on the element's style, we may still get a string here which must be parsed and instanced.
-				if (const StyleSheet* style_sheet = element->GetStyleSheet())
-				{
-					// The property source will not be set if the property is defined in inline style. However, we may need it in order to locate
-					// resource files (typically images). In this case, generate one from the document's source URL.
-					SharedPtr<const PropertySource> document_source;
-
-					if (!p->source)
-					{
-						if (ElementDocument* document = element->GetOwnerDocument())
-							document_source = MakeShared<PropertySource>(document->GetSourceURL(), 0, String());
-					}
-
-					const String& value = p->value.GetReference<String>();
-					values.decorator = style_sheet->InstanceDecoratorsFromString(value, p->source ? p->source : document_source);
-				}
-				else
-					values.decorator.reset();
-			}
-			else
-				values.decorator.reset();
+			values.has_decorator = (p->unit == Property::DECORATOR);
 			break;
 			break;
 		case PropertyId::FontEffect:
 		case PropertyId::FontEffect:
-			if (p->unit == Property::FONTEFFECT)
-			{
-				values.font_effect = p->Get<FontEffectsPtr>();
-			}
-			else if (p->unit == Property::STRING)
-			{
-				if (const StyleSheet* style_sheet = element->GetStyleSheet())
-				{
-					const String& value = p->value.GetReference<String>();
-					values.font_effect = style_sheet->InstanceFontEffectsFromString(value, p->source);
-				}
-				else
-					values.font_effect.reset();
-			}
-			else
-				values.font_effect.reset();
+			values.has_font_effect = (p->unit == Property::FONTEFFECT);
 			break;
 			break;
+
 		// Unhandled properties. Must be manually retrieved with 'GetProperty()'.
 		// Unhandled properties. Must be manually retrieved with 'GetProperty()'.
 		case PropertyId::FillImage:
 		case PropertyId::FillImage:
 		case PropertyId::CaretColor:
 		case PropertyId::CaretColor:

+ 6 - 2
Source/Core/ElementText.cpp

@@ -425,8 +425,12 @@ bool ElementText::UpdateFontEffects()
 
 
 	// Fetch the font-effect for this text element
 	// Fetch the font-effect for this text element
 	const FontEffectList* font_effects = &empty_font_effects;
 	const FontEffectList* font_effects = &empty_font_effects;
-	if (const FontEffects* effects = GetComputedValues().font_effect.get())
-		font_effects = &effects->list;
+	if (GetComputedValues().has_font_effect)
+	{
+		if (const Property* p = GetProperty(PropertyId::FontEffect))
+			if (FontEffectsPtr effects = p->Get<FontEffectsPtr>())
+				font_effects = &effects->list;
+	}
 
 
 	// Request a font layer configuration to match this set of effects. If this is different from
 	// Request a font layer configuration to match this set of effects. If this is different from
 	// our old configuration, then return true to indicate we'll need to regenerate geometry.
 	// our old configuration, then return true to indicate we'll need to regenerate geometry.

+ 0 - 1
Source/Core/Factory.cpp

@@ -69,7 +69,6 @@
 #include "FontEffectOutline.h"
 #include "FontEffectOutline.h"
 #include "FontEffectShadow.h"
 #include "FontEffectShadow.h"
 #include "PluginRegistry.h"
 #include "PluginRegistry.h"
-#include "PropertyParserColour.h"
 #include "StreamFile.h"
 #include "StreamFile.h"
 #include "StyleSheetFactory.h"
 #include "StyleSheetFactory.h"
 #include "TemplateCache.h"
 #include "TemplateCache.h"

+ 122 - 0
Source/Core/PropertyParserDecorator.cpp

@@ -0,0 +1,122 @@
+/*
+ * 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 "PropertyParserDecorator.h"
+#include "../../Include/RmlUi/Core/DecoratorInstancer.h"
+#include "../../Include/RmlUi/Core/Factory.h"
+#include "../../Include/RmlUi/Core/PropertySpecification.h"
+#include "../../Include/RmlUi/Core/Profiling.h"
+#include "../../Include/RmlUi/Core/StyleSheetTypes.h"
+
+namespace Rml {
+
+PropertyParserDecorator::PropertyParserDecorator()
+{}
+
+PropertyParserDecorator::~PropertyParserDecorator()
+{}
+
+bool PropertyParserDecorator::ParseValue(Property& property, const String& decorator_string_value, const ParameterMap& /*parameters*/) const
+{
+	// Decorators are declared as
+	//   decorator: <decorator-value>[, <decorator-value> ...];
+	// Where <decorator-value> is either a @decorator name:
+	//   decorator: invader-theme-background, ...;
+	// or is an anonymous decorator with inline properties
+	//   decorator: tiled-box( <shorthand properties> ), ...;
+
+	if (decorator_string_value.empty() || decorator_string_value == "none")
+	{
+		property.value = Variant();
+		property.unit = Property::UNKNOWN;
+		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, ',', '(', ')');
+
+	decorators.value = decorator_string_value;
+	decorators.list.reserve(decorator_string_list.size());
+
+	// Get or instance each decorator in the comma-separated string list
+	for (const String& decorator_string : decorator_string_list)
+	{
+		const size_t shorthand_open = decorator_string.find('(');
+		const size_t shorthand_close = decorator_string.rfind(')');
+		const bool invalid_parenthesis = (shorthand_open == String::npos || shorthand_close == String::npos || shorthand_open >= shorthand_close);
+
+		if (invalid_parenthesis)
+		{
+			// We found no parenthesis, that means the value must be a name of a @decorator rule.
+			decorators.list.emplace_back(DecoratorDeclaration{ decorator_string, nullptr, {} });
+		}
+		else
+		{
+			// Since we have parentheses it must be an anonymous decorator with inline properties
+			const String type = StringUtilities::StripWhitespace(decorator_string.substr(0, shorthand_open));
+
+			// Check for valid decorator type
+			DecoratorInstancer* instancer = Factory::GetDecoratorInstancer(type);
+			if (!instancer)
+			{
+				Log::Message(Log::LT_WARNING, "Decorator type '%s' not found.", type.c_str());
+				return false;
+			}
+
+			const String shorthand = decorator_string.substr(shorthand_open + 1, shorthand_close - shorthand_open - 1);
+			const PropertySpecification& specification = instancer->GetPropertySpecification();
+
+			// Parse the shorthand properties given by the 'decorator' shorthand property
+			PropertyDictionary properties;
+			if (!specification.ParsePropertyDeclaration(properties, "decorator", shorthand))
+			{
+				return false;
+			}
+
+			// Set unspecified values to their defaults
+			specification.SetPropertyDefaults(properties);
+
+			decorators.list.emplace_back(DecoratorDeclaration{ type, instancer, std::move(properties) });
+		}
+	}
+
+	if (decorators.list.empty())
+		return false;
+
+	property.value = Variant(MakeShared<DecoratorDeclarationList>(std::move(decorators)));
+	property.unit = Property::DECORATOR;
+
+	return true;
+}
+
+} // namespace Rml

+ 51 - 0
Source/Core/PropertyParserDecorator.h

@@ -0,0 +1,51 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef RMLUI_CORE_PROPERTYPARSERDECORATOR_H
+#define RMLUI_CORE_PROPERTYPARSERDECORATOR_H
+
+#include "../../Include/RmlUi/Core/PropertyParser.h"
+
+namespace Rml {
+
+/**
+	A property parser for the decorator property.
+ */
+
+class PropertyParserDecorator : public PropertyParser
+{
+public:
+	PropertyParserDecorator();
+	virtual ~PropertyParserDecorator();
+
+	/// Called to parse a decorator declaration.
+	bool ParseValue(Property& property, const String& value, const ParameterMap& parameters) const override;
+};
+
+} // namespace Rml
+#endif

+ 146 - 0
Source/Core/PropertyParserFontEffect.cpp

@@ -0,0 +1,146 @@
+/*
+ * 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 "PropertyParserFontEffect.h"
+#include "../../Include/RmlUi/Core/Factory.h"
+#include "../../Include/RmlUi/Core/FontEffect.h"
+#include "../../Include/RmlUi/Core/FontEffectInstancer.h"
+#include "../../Include/RmlUi/Core/PropertySpecification.h"
+#include "../../Include/RmlUi/Core/Profiling.h"
+#include "Utilities.h"
+#include <algorithm>
+
+namespace Rml {
+
+PropertyParserFontEffect::PropertyParserFontEffect()
+{}
+
+PropertyParserFontEffect::~PropertyParserFontEffect()
+{}
+
+bool PropertyParserFontEffect::ParseValue(Property& property, const String& font_effect_string_value, const ParameterMap& /*parameters*/) const
+{
+	// Font-effects are declared as
+	//   font-effect: <font-effect-value>[, <font-effect-value> ...];
+	// Where <font-effect-value> is declared with inline properties, e.g.
+	//   font-effect: outline( 1px black ), ...;
+
+	if (font_effect_string_value.empty() || font_effect_string_value == "none")
+	{
+		property.value = Variant();
+		property.unit = Property::UNKNOWN;
+		return true;
+	}
+
+	RMLUI_ZoneScoped;
+
+	FontEffects font_effects;
+
+	// Make sure we don't split inside the parenthesis since they may appear in decorator shorthands.
+	StringList font_effect_string_list;
+	StringUtilities::ExpandString(font_effect_string_list, font_effect_string_value, ',', '(', ')');
+
+	font_effects.value = font_effect_string_value;
+	font_effects.list.reserve(font_effect_string_list.size());
+
+	// Get or instance each decorator in the comma-separated string list
+	for (const String& font_effect_string : font_effect_string_list)
+	{
+		const size_t shorthand_open = font_effect_string.find('(');
+		const size_t shorthand_close = font_effect_string.rfind(')');
+		const bool invalid_parenthesis = (shorthand_open == String::npos || shorthand_close == String::npos || shorthand_open >= shorthand_close);
+
+		if (invalid_parenthesis)
+		{
+			// We found no parenthesis, font-effects can only be declared anonymously for now.
+			Log::Message(Log::LT_WARNING, "Invalid syntax for font-effect '%s'.", font_effect_string.c_str());
+			return false;
+		}
+		else
+		{
+			// Since we have parentheses it must be an anonymous decorator with inline properties
+			const String type = StringUtilities::StripWhitespace(font_effect_string.substr(0, shorthand_open));
+
+			// Check for valid font-effect type
+			FontEffectInstancer* instancer = Factory::GetFontEffectInstancer(type);
+			if (!instancer)
+			{
+				Log::Message(Log::LT_WARNING, "Font-effect type '%s' not found.", type.c_str());
+				return false;
+			}
+
+			const String shorthand = font_effect_string.substr(shorthand_open + 1, shorthand_close - shorthand_open - 1);
+			const PropertySpecification& specification = instancer->GetPropertySpecification();
+
+			// Parse the shorthand properties given by the 'font-effect' shorthand property
+			PropertyDictionary properties;
+			if (!specification.ParsePropertyDeclaration(properties, "font-effect", shorthand))
+			{
+				Log::Message(Log::LT_WARNING, "Could not parse font-effect value '%s'.", font_effect_string.c_str());
+				return false;
+			}
+
+			// Set unspecified values to their defaults
+			specification.SetPropertyDefaults(properties);
+
+			RMLUI_ZoneScopedN("InstanceFontEffect");
+			SharedPtr<FontEffect> font_effect = instancer->InstanceFontEffect(type, properties);
+			if (font_effect)
+			{
+				// Create a unique hash value for the given type and values
+				size_t fingerprint = Hash<String>{}(type);
+				for (const auto& id_value : properties.GetProperties())
+					Utilities::HashCombine(fingerprint, id_value.second.Get<String>());
+
+				font_effect->SetFingerprint(fingerprint);
+
+				font_effects.list.emplace_back(std::move(font_effect));
+			}
+			else
+			{
+				Log::Message(Log::LT_WARNING, "Font-effect '%s' could not be instanced.", font_effect_string.c_str());
+				return false;
+			}
+		}
+	}
+
+	if (font_effects.list.empty())
+		return false;
+
+	// Partition the list such that the back layer effects appear before the front layer effects
+	std::stable_partition(font_effects.list.begin(), font_effects.list.end(),
+		[](const SharedPtr<const FontEffect>& effect) { return effect->GetLayer() == FontEffect::Layer::Back; }
+	);
+
+	property.value = Variant(MakeShared<FontEffects>(std::move(font_effects)));
+	property.unit = Property::FONTEFFECT;
+
+	return true;
+}
+
+} // namespace Rml

+ 51 - 0
Source/Core/PropertyParserFontEffect.h

@@ -0,0 +1,51 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef RMLUI_CORE_PROPERTYPARSERFONTEFFECT_H
+#define RMLUI_CORE_PROPERTYPARSERFONTEFFECT_H
+
+#include "../../Include/RmlUi/Core/PropertyParser.h"
+
+namespace Rml {
+
+/**
+	A property parser for the font-effect property.
+ */
+
+class PropertyParserFontEffect : public PropertyParser
+{
+public:
+	PropertyParserFontEffect();
+	virtual ~PropertyParserFontEffect();
+
+	/// Called to parse a font-effect declaration.
+	bool ParseValue(Property& property, const String& value, const ParameterMap& parameters) const override;
+};
+
+} // namespace Rml
+#endif

+ 23 - 209
Source/Core/StyleSheet.cpp

@@ -28,17 +28,12 @@
 
 
 #include "../../Include/RmlUi/Core/StyleSheet.h"
 #include "../../Include/RmlUi/Core/StyleSheet.h"
 #include "ElementDefinition.h"
 #include "ElementDefinition.h"
-#include "StyleSheetFactory.h"
 #include "StyleSheetNode.h"
 #include "StyleSheetNode.h"
 #include "Utilities.h"
 #include "Utilities.h"
-#include "../../Include/RmlUi/Core/DecoratorInstancer.h"
 #include "../../Include/RmlUi/Core/Element.h"
 #include "../../Include/RmlUi/Core/Element.h"
-#include "../../Include/RmlUi/Core/Factory.h"
-#include "../../Include/RmlUi/Core/FontEffect.h"
 #include "../../Include/RmlUi/Core/Profiling.h"
 #include "../../Include/RmlUi/Core/Profiling.h"
 #include "../../Include/RmlUi/Core/PropertyDefinition.h"
 #include "../../Include/RmlUi/Core/PropertyDefinition.h"
 #include "../../Include/RmlUi/Core/StyleSheetSpecification.h"
 #include "../../Include/RmlUi/Core/StyleSheetSpecification.h"
-#include "../../Include/RmlUi/Core/FontEffectInstancer.h"
 #include <algorithm>
 #include <algorithm>
 
 
 namespace Rml {
 namespace Rml {
@@ -67,38 +62,42 @@ UniquePtr<StyleSheet> StyleSheet::CombineStyleSheet(const StyleSheet& other_shee
 	UniquePtr<StyleSheet> new_sheet = UniquePtr<StyleSheet>(new StyleSheet());
 	UniquePtr<StyleSheet> new_sheet = UniquePtr<StyleSheet>(new StyleSheet());
 	
 	
 	new_sheet->root = root->DeepCopy();
 	new_sheet->root = root->DeepCopy();
-	new_sheet->root->MergeHierarchy(other_sheet.root.get(), specificity_offset);
+	new_sheet->specificity_offset = specificity_offset;
+	new_sheet->keyframes = keyframes;
+	new_sheet->decorator_map = decorator_map;
+	new_sheet->spritesheet_list = spritesheet_list;
+
+	new_sheet->MergeStyleSheet(other_sheet);
+
+	return new_sheet;
+}
+
+void StyleSheet::MergeStyleSheet(const StyleSheet& other_sheet)
+{
+	RMLUI_ZoneScoped;
+
+	root->MergeHierarchy(other_sheet.root.get(), specificity_offset);
+	specificity_offset += other_sheet.specificity_offset;
 
 
 	// Any matching @keyframe names are overridden as per CSS rules
 	// Any matching @keyframe names are overridden as per CSS rules
-	new_sheet->keyframes.reserve(keyframes.size() + other_sheet.keyframes.size());
-	new_sheet->keyframes = keyframes;
+	keyframes.reserve(keyframes.size() + other_sheet.keyframes.size());
 	for (auto& other_keyframes : other_sheet.keyframes)
 	for (auto& other_keyframes : other_sheet.keyframes)
 	{
 	{
-		new_sheet->keyframes[other_keyframes.first] = other_keyframes.second;
+		keyframes[other_keyframes.first] = other_keyframes.second;
 	}
 	}
 
 
 	// Copy over the decorators, and replace any matching decorator names from other_sheet
 	// Copy over the decorators, and replace any matching decorator names from other_sheet
-	new_sheet->decorator_map.reserve(decorator_map.size() + other_sheet.decorator_map.size());
-	new_sheet->decorator_map = decorator_map;
-	for (auto& other_decorator: other_sheet.decorator_map)
+	decorator_map.reserve(decorator_map.size() + other_sheet.decorator_map.size());
+	for (auto& other_decorator : other_sheet.decorator_map)
 	{
 	{
-		new_sheet->decorator_map[other_decorator.first] = other_decorator.second;
+		decorator_map[other_decorator.first] = other_decorator.second;
 	}
 	}
 
 
-	new_sheet->spritesheet_list.Reserve(
+	spritesheet_list.Reserve(
 		spritesheet_list.NumSpriteSheets() + other_sheet.spritesheet_list.NumSpriteSheets(),
 		spritesheet_list.NumSpriteSheets() + other_sheet.spritesheet_list.NumSpriteSheets(),
 		spritesheet_list.NumSprites() + other_sheet.spritesheet_list.NumSprites()
 		spritesheet_list.NumSprites() + other_sheet.spritesheet_list.NumSprites()
 	);
 	);
-	new_sheet->spritesheet_list = spritesheet_list;
-	new_sheet->spritesheet_list.Merge(other_sheet.spritesheet_list);
-
-	new_sheet->specificity_offset = specificity_offset + other_sheet.specificity_offset;
-	return new_sheet;
-}
-
-UniquePtr<StyleSheet> StyleSheet::Clone() const
-{
-	return CombineStyleSheet(StyleSheet{});
+	spritesheet_list.Merge(other_sheet.spritesheet_list);
 }
 }
 
 
 // Builds the node index for a combined style sheet.
 // Builds the node index for a combined style sheet.
@@ -110,13 +109,6 @@ void StyleSheet::BuildNodeIndex()
 	root->SetStructurallyVolatileRecursive(false);
 	root->SetStructurallyVolatileRecursive(false);
 }
 }
 
 
-// Builds the node index for a combined style sheet.
-void StyleSheet::OptimizeNodeProperties()
-{
-	RMLUI_ZoneScoped;
-	root->OptimizeProperties(*this);
-}
-
 // Returns the Keyframes of the given name, or null if it does not exist.
 // 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
 {
 {
@@ -139,184 +131,6 @@ const Sprite* StyleSheet::GetSprite(const String& name) const
 	return spritesheet_list.GetSprite(name);
 	return spritesheet_list.GetSprite(name);
 }
 }
 
 
-DecoratorsPtr StyleSheet::InstanceDecoratorsFromString(const String& decorator_string_value, const SharedPtr<const PropertySource>& source) const
-{
-	// Decorators are declared as
-	//   decorator: <decorator-value>[, <decorator-value> ...];
-	// Where <decorator-value> is either a @decorator name:
-	//   decorator: invader-theme-background, ...;
-	// or is an anonymous decorator with inline properties
-	//   decorator: tiled-box( <shorthand properties> ), ...;
-
-	if (decorator_string_value.empty() || decorator_string_value == "none")
-		return nullptr;
-
-	RMLUI_ZoneScoped;
-	Decorators decorators;
-	const char* source_path = (source ? source->path.c_str() : "");
-	const int source_line_number = (source ? source->line_number : 0);
-
-	// 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, ',', '(', ')');
-
-	decorators.value = decorator_string_value;
-	decorators.list.reserve(decorator_string_list.size());
-
-	// Get or instance each decorator in the comma-separated string list
-	for (const String& decorator_string : decorator_string_list)
-	{
-		const size_t shorthand_open = decorator_string.find('(');
-		const size_t shorthand_close = decorator_string.rfind(')');
-		const bool invalid_parenthesis = (shorthand_open == String::npos || shorthand_close == String::npos || shorthand_open >= shorthand_close);
-
-		if (invalid_parenthesis)
-		{
-			// We found no parenthesis, that means the value must be a name of a @decorator rule, look it up
-			SharedPtr<Decorator> decorator = GetDecorator(decorator_string);
-			if (decorator)
-				decorators.list.emplace_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", decorator_string.c_str(), source_path, source_line_number);
-		}
-		else
-		{
-			// Since we have parentheses it must be an anonymous decorator with inline properties
-			const String type = StringUtilities::StripWhitespace(decorator_string.substr(0, shorthand_open));
-
-			// Check for valid decorator type
-			DecoratorInstancer* instancer = Factory::GetDecoratorInstancer(type);
-			if (!instancer)
-			{
-				Log::Message(Log::LT_WARNING, "Decorator type '%s' not found, declared at %s:%d", type.c_str(), source_path, source_line_number);
-				continue;
-			}
-
-			const String shorthand = decorator_string.substr(shorthand_open + 1, shorthand_close - shorthand_open - 1);
-			const PropertySpecification& specification = instancer->GetPropertySpecification();
-
-			// Parse the shorthand properties given by the 'decorator' shorthand property
-			PropertyDictionary properties;
-			if (!specification.ParsePropertyDeclaration(properties, "decorator", shorthand))
-			{
-				Log::Message(Log::LT_WARNING, "Could not parse decorator value '%s' at %s:%d", decorator_string.c_str(), source_path, source_line_number);
-				continue;
-			}
-
-			// Set unspecified values to their defaults
-			specification.SetPropertyDefaults(properties);
-			
-			properties.SetSourceOfAllProperties(source);
-
-			RMLUI_ZoneScopedN("InstanceDecorator");
-			SharedPtr<Decorator> decorator = instancer->InstanceDecorator(type, properties, DecoratorInstancerInterface(*this));
-
-			if (decorator)
-				decorators.list.emplace_back(std::move(decorator));
-			else
-			{
-				Log::Message(Log::LT_WARNING, "Decorator '%s' could not be instanced, declared at %s:%d", decorator_string.c_str(), source_path, source_line_number);
-				continue;
-			}
-		}
-	}
-
-	return MakeShared<Decorators>(std::move(decorators));
-}
-
-FontEffectsPtr StyleSheet::InstanceFontEffectsFromString(const String& font_effect_string_value, const SharedPtr<const PropertySource>& source) const
-{	
-	// Font-effects are declared as
-	//   font-effect: <font-effect-value>[, <font-effect-value> ...];
-	// Where <font-effect-value> is declared with inline properties, e.g.
-	//   font-effect: outline( 1px black ), ...;
-
-	if (font_effect_string_value.empty() || font_effect_string_value == "none")
-		return nullptr;
-
-	RMLUI_ZoneScoped;
-	const char* source_path = (source ? source->path.c_str() : "");
-	const int source_line_number = (source ? source->line_number : 0);
-
-	FontEffects font_effects;
-
-	// Make sure we don't split inside the parenthesis since they may appear in decorator shorthands.
-	StringList font_effect_string_list;
-	StringUtilities::ExpandString(font_effect_string_list, font_effect_string_value, ',', '(', ')');
-
-	font_effects.value = font_effect_string_value;
-	font_effects.list.reserve(font_effect_string_list.size());
-
-	// Get or instance each decorator in the comma-separated string list
-	for (const String& font_effect_string : font_effect_string_list)
-	{
-		const size_t shorthand_open = font_effect_string.find('(');
-		const size_t shorthand_close = font_effect_string.rfind(')');
-		const bool invalid_parenthesis = (shorthand_open == String::npos || shorthand_close == String::npos || shorthand_open >= shorthand_close);
-
-		if (invalid_parenthesis)
-		{
-			// We found no parenthesis, font-effects can only be declared anonymously for now.
-			Log::Message(Log::LT_WARNING, "Invalid syntax for font-effect '%s', declared at %s:%d", font_effect_string.c_str(), source_path, source_line_number);
-		}
-		else
-		{
-			// Since we have parentheses it must be an anonymous decorator with inline properties
-			const String type = StringUtilities::StripWhitespace(font_effect_string.substr(0, shorthand_open));
-
-			// Check for valid font-effect type
-			FontEffectInstancer* instancer = Factory::GetFontEffectInstancer(type);
-			if (!instancer)
-			{
-				Log::Message(Log::LT_WARNING, "Font-effect type '%s' not found, declared at %s:%d", type.c_str(), source_path, source_line_number);
-				continue;
-			}
-
-			const String shorthand = font_effect_string.substr(shorthand_open + 1, shorthand_close - shorthand_open - 1);
-			const PropertySpecification& specification = instancer->GetPropertySpecification();
-
-			// Parse the shorthand properties given by the 'font-effect' shorthand property
-			PropertyDictionary properties;
-			if (!specification.ParsePropertyDeclaration(properties, "font-effect", shorthand))
-			{
-				Log::Message(Log::LT_WARNING, "Could not parse font-effect value '%s' at %s:%d", font_effect_string.c_str(), source_path, source_line_number);
-				continue;
-			}
-
-			// Set unspecified values to their defaults
-			specification.SetPropertyDefaults(properties);
-
-			properties.SetSourceOfAllProperties(source);
-
-			RMLUI_ZoneScopedN("InstanceFontEffect");
-			SharedPtr<FontEffect> font_effect = instancer->InstanceFontEffect(type, properties);
-			if (font_effect)
-			{
-				// Create a unique hash value for the given type and values
-				size_t fingerprint = Hash<String>{}(type);
-				for (const auto& id_value : properties.GetProperties())
-					Utilities::HashCombine(fingerprint, id_value.second.Get<String>());
-
-				font_effect->SetFingerprint(fingerprint);
-
-				font_effects.list.emplace_back(std::move(font_effect));
-			}
-			else
-			{
-				Log::Message(Log::LT_WARNING, "Font-effect '%s' could not be instanced, declared at %s:%d", font_effect_string.c_str(), source_path, source_line_number);
-				continue;
-			}
-		}
-	}
-
-	// Partition the list such that the back layer effects appear before the front layer effects
-	std::stable_partition(font_effects.list.begin(), font_effects.list.end(), 
-		[](const SharedPtr<const FontEffect>& effect) { return effect->GetLayer() == FontEffect::Layer::Back; }
-	);
-
-	return MakeShared<FontEffects>(std::move(font_effects));
-}
-
 size_t StyleSheet::NodeHash(const String& tag, const String& id)
 size_t StyleSheet::NodeHash(const String& tag, const String& id)
 {
 {
 	size_t seed = 0;
 	size_t seed = 0;

+ 28 - 10
Source/Core/StyleSheetContainer.cpp

@@ -149,8 +149,10 @@ bool StyleSheetContainer::UpdateCompiledStyleSheet(float dp_ratio, Vector2f vp_d
 			MediaBlock& media_block = media_blocks[index];
 			MediaBlock& media_block = media_blocks[index];
 			if (!first_sheet)
 			if (!first_sheet)
 				first_sheet = media_block.stylesheet.get();
 				first_sheet = media_block.stylesheet.get();
+			else if (!new_sheet)
+				new_sheet = first_sheet->CombineStyleSheet(*media_block.stylesheet);
 			else
 			else
-				new_sheet = (new_sheet ? new_sheet.get() : first_sheet)->CombineStyleSheet(*media_block.stylesheet);
+				new_sheet->MergeStyleSheet(*media_block.stylesheet);
 		}
 		}
 
 
 		if (!first_sheet)
 		if (!first_sheet)
@@ -163,7 +165,6 @@ bool StyleSheetContainer::UpdateCompiledStyleSheet(float dp_ratio, Vector2f vp_d
 		combined_compiled_style_sheet = std::move(new_sheet);
 		combined_compiled_style_sheet = std::move(new_sheet);
 
 
 		compiled_style_sheet->BuildNodeIndex();
 		compiled_style_sheet->BuildNodeIndex();
-		compiled_style_sheet->OptimizeNodeProperties();
 	}
 	}
 
 
 	active_media_block_indices = std::move(new_active_media_block_indices);
 	active_media_block_indices = std::move(new_active_media_block_indices);
@@ -184,7 +185,7 @@ SharedPtr<StyleSheetContainer> StyleSheetContainer::CombineStyleSheetContainer(c
 
 
 	for (const MediaBlock& media_block : media_blocks)
 	for (const MediaBlock& media_block : media_blocks)
 	{
 	{
-		new_sheet->media_blocks.emplace_back(media_block.properties, media_block.stylesheet->Clone());
+		new_sheet->media_blocks.emplace_back(media_block.properties, media_block.stylesheet);
 	}
 	}
 
 
 	new_sheet->MergeStyleSheetContainer(container);
 	new_sheet->MergeStyleSheetContainer(container);
@@ -199,16 +200,33 @@ void StyleSheetContainer::MergeStyleSheetContainer(const StyleSheetContainer& ot
 	// Style sheet container must not be merged after it's been compiled. This will invalidate references to the compiled style sheet.
 	// Style sheet container must not be merged after it's been compiled. This will invalidate references to the compiled style sheet.
 	RMLUI_ASSERT(!compiled_style_sheet);
 	RMLUI_ASSERT(!compiled_style_sheet);
 
 
-	for (const MediaBlock& block_other : other.media_blocks)
+	auto it_other_begin = other.media_blocks.begin();
+
+#if 0
+	// If the last block here has the same media requirements as the first block in other, we can safely merge them
+	// while retaining correct specificity of all properties. This is essentially an optimization to avoid more
+	// style sheet merging later on.
+	if (!media_blocks.empty() && !other.media_blocks.empty())
 	{
 	{
-		media_blocks.emplace_back(block_other.properties, block_other.stylesheet->Clone());
+		MediaBlock& block_local = media_blocks.back();
+		const MediaBlock& block_other = other.media_blocks.front();
+		if (block_local.properties.GetProperties() == block_other.properties.GetProperties())
+		{
+			// Now we can safely merge the two style sheets.
+			block_local.stylesheet = block_local.stylesheet->CombineStyleSheet(*block_other.stylesheet);
+
+			// And we need to skip the first media block in the 'other' style sheet, since we merged it just now.
+			++it_other_begin;
+		}
 	}
 	}
-}
+#endif
 
 
-void StyleSheetContainer::OptimizeNodeProperties()
-{
-	for (MediaBlock& block : media_blocks)
-		block.stylesheet->OptimizeNodeProperties();
+	// Add all the other blocks into ours.
+	for (auto it = it_other_begin; it != other.media_blocks.end(); ++it)
+	{
+		const MediaBlock& block_other = *it;
+		media_blocks.emplace_back(block_other.properties, block_other.stylesheet);
+	}
 }
 }
 
 
 } // namespace Rml
 } // namespace Rml

+ 1 - 5
Source/Core/StyleSheetFactory.cpp

@@ -193,11 +193,7 @@ UniquePtr<const StyleSheetContainer> StyleSheetFactory::LoadStyleSheetContainer(
 	if (stream->Open(sheet))
 	if (stream->Open(sheet))
 	{
 	{
 		new_style_sheet = MakeUnique<StyleSheetContainer>();
 		new_style_sheet = MakeUnique<StyleSheetContainer>();
-		if (new_style_sheet->LoadStyleSheetContainer(stream.get()))
-		{
-			new_style_sheet->OptimizeNodeProperties();
-		}
-		else
+		if (!new_style_sheet->LoadStyleSheetContainer(stream.get()))
 		{
 		{
 			new_style_sheet.reset();
 			new_style_sheet.reset();
 		}
 		}

+ 0 - 47
Source/Core/StyleSheetNode.cpp

@@ -140,53 +140,6 @@ void StyleSheetNode::BuildIndex(StyleSheet::NodeIndex& styled_node_index) const
 	}
 	}
 }
 }
 
 
-// Builds up a style sheet's index recursively.
-void StyleSheetNode::OptimizeProperties(const StyleSheet& style_sheet)
-{
-	// Turn any decorator and font-effect properties from String to DecoratorList / FontEffectList.
-	// This is essentially an optimization, it will work fine to skip this step and let ElementStyle::ComputeValues() do all the work.
-	// However, when we do it here, we only need to do it once.
-	if (properties.GetNumProperties() > 0)
-	{
-		// Decorators
-		if (const Property* property = properties.GetProperty(PropertyId::Decorator))
-		{
-			if (property->unit == Property::STRING)
-			{
-				const String string_value = property->Get<String>();
-
-				if (DecoratorsPtr decorators = style_sheet.InstanceDecoratorsFromString(string_value, property->source))
-				{
-					Property new_property = *property;
-					new_property.value = std::move(decorators);
-					new_property.unit = Property::DECORATOR;
-					properties.SetProperty(PropertyId::Decorator, new_property);
-				}
-			}
-		}
-
-		// Font-effects
-		if (const Property* property = properties.GetProperty(PropertyId::FontEffect))
-		{
-			if (property->unit == Property::STRING)
-			{
-				const String string_value = property->Get<String>();
-				FontEffectsPtr font_effects = style_sheet.InstanceFontEffectsFromString(string_value, property->source);
-
-				Property new_property = *property;
-				new_property.value = std::move(font_effects);
-				new_property.unit = Property::FONTEFFECT;
-				properties.SetProperty(PropertyId::FontEffect, new_property);
-			}
-		}
-	}
-
-	for (const auto& child : children)
-	{
-		child->OptimizeProperties(style_sheet);
-	}
-}
-
 bool StyleSheetNode::SetStructurallyVolatileRecursive(bool ancestor_is_structural_pseudo_class)
 bool StyleSheetNode::SetStructurallyVolatileRecursive(bool ancestor_is_structural_pseudo_class)
 {
 {
 	// If any ancestor or descendant is a structural pseudo class, then we are structurally volatile.
 	// If any ancestor or descendant is a structural pseudo class, then we are structurally volatile.

+ 0 - 2
Source/Core/StyleSheetNode.h

@@ -77,8 +77,6 @@ public:
 	bool SetStructurallyVolatileRecursive(bool ancestor_is_structurally_volatile);
 	bool SetStructurallyVolatileRecursive(bool ancestor_is_structurally_volatile);
 	/// Builds up a style sheet's index recursively.
 	/// Builds up a style sheet's index recursively.
 	void BuildIndex(StyleSheet::NodeIndex& styled_node_index) const;
 	void BuildIndex(StyleSheet::NodeIndex& styled_node_index) const;
-	/// Optimizes some properties recursively for faster retrieval. In particular, decorators and font effects.
-	void OptimizeProperties(const StyleSheet& style_sheet);
 
 
 	/// Imports properties from a single rule definition into the node's properties and sets the
 	/// Imports properties from a single rule definition into the node's properties and sets the
 	/// appropriate specificity on them. Any existing attributes sharing a key with a new attribute
 	/// appropriate specificity on them. Any existing attributes sharing a key with a new attribute

+ 1 - 1
Source/Core/StyleSheetParser.cpp

@@ -402,7 +402,7 @@ bool StyleSheetParser::ParseDecoratorBlock(const String& at_name, DecoratorSpeci
 	property_specification.SetPropertyDefaults(properties);
 	property_specification.SetPropertyDefaults(properties);
 	properties.SetSourceOfAllProperties(source);
 	properties.SetSourceOfAllProperties(source);
 
 
-	SharedPtr<Decorator> decorator = decorator_instancer->InstanceDecorator(decorator_type, properties, DecoratorInstancerInterface(style_sheet));
+	SharedPtr<Decorator> decorator = decorator_instancer->InstanceDecorator(decorator_type, properties, DecoratorInstancerInterface(style_sheet, source.get()));
 	if (!decorator)
 	if (!decorator)
 	{
 	{
 		Log::Message(Log::LT_WARNING, "Could not instance decorator of type '%s' declared at %s:%d.", decorator_type.c_str(), stream_file_name.c_str(), line_number);
 		Log::Message(Log::LT_WARNING, "Could not instance decorator of type '%s' declared at %s:%d.", decorator_type.c_str(), stream_file_name.c_str(), line_number);

+ 1 - 4
Source/Core/StyleSheetParser.h

@@ -29,20 +29,17 @@
 #ifndef RMLUI_CORE_STYLESHEETPARSER_H
 #ifndef RMLUI_CORE_STYLESHEETPARSER_H
 #define RMLUI_CORE_STYLESHEETPARSER_H
 #define RMLUI_CORE_STYLESHEETPARSER_H
 
 
-#include "../../Include/RmlUi/Core/StyleSheet.h"
 #include "../../Include/RmlUi/Core/Types.h"
 #include "../../Include/RmlUi/Core/Types.h"
+#include "../../Include/RmlUi/Core/StyleSheetTypes.h"
 
 
 namespace Rml {
 namespace Rml {
 
 
 class PropertyDictionary;
 class PropertyDictionary;
 class Stream;
 class Stream;
-class StyleSheetContainer;
-struct MediaBlock;
 class StyleSheetNode;
 class StyleSheetNode;
 class AbstractPropertyParser;
 class AbstractPropertyParser;
 struct PropertySource;
 struct PropertySource;
 using StyleSheetNodeListRaw = Vector<StyleSheetNode*>;
 using StyleSheetNodeListRaw = Vector<StyleSheetNode*>;
-using MediaBlockList = Vector<MediaBlock>;
 
 
 /**
 /**
 	Helper class for parsing a style sheet into its memory representation.
 	Helper class for parsing a style sheet into its memory representation.

+ 8 - 2
Source/Core/StyleSheetSpecification.cpp

@@ -33,6 +33,8 @@
 #include "PropertyParserAnimation.h"
 #include "PropertyParserAnimation.h"
 #include "PropertyParserRatio.h"
 #include "PropertyParserRatio.h"
 #include "PropertyParserColour.h"
 #include "PropertyParserColour.h"
+#include "PropertyParserDecorator.h"
+#include "PropertyParserFontEffect.h"
 #include "PropertyParserKeyword.h"
 #include "PropertyParserKeyword.h"
 #include "PropertyParserString.h"
 #include "PropertyParserString.h"
 #include "PropertyParserTransform.h"
 #include "PropertyParserTransform.h"
@@ -55,6 +57,8 @@ struct DefaultStyleSheetParsers {
 	PropertyParserAnimation animation = PropertyParserAnimation(PropertyParserAnimation::ANIMATION_PARSER);
 	PropertyParserAnimation animation = PropertyParserAnimation(PropertyParserAnimation::ANIMATION_PARSER);
 	PropertyParserAnimation transition = PropertyParserAnimation(PropertyParserAnimation::TRANSITION_PARSER);
 	PropertyParserAnimation transition = PropertyParserAnimation(PropertyParserAnimation::TRANSITION_PARSER);
 	PropertyParserColour color = PropertyParserColour();
 	PropertyParserColour color = PropertyParserColour();
+	PropertyParserDecorator decorator = PropertyParserDecorator();
+	PropertyParserFontEffect font_effect = PropertyParserFontEffect();
 	PropertyParserTransform transform = PropertyParserTransform();
 	PropertyParserTransform transform = PropertyParserTransform();
 	PropertyParserRatio ratio = PropertyParserRatio();
 	PropertyParserRatio ratio = PropertyParserRatio();
 	PropertyParserNumber resolution = PropertyParserNumber(Property::X);
 	PropertyParserNumber resolution = PropertyParserNumber(Property::X);
@@ -249,6 +253,8 @@ void StyleSheetSpecification::RegisterDefaultParsers()
 	RegisterParser("animation", &default_parsers->animation);
 	RegisterParser("animation", &default_parsers->animation);
 	RegisterParser("transition", &default_parsers->transition);
 	RegisterParser("transition", &default_parsers->transition);
 	RegisterParser("color", &default_parsers->color);
 	RegisterParser("color", &default_parsers->color);
+	RegisterParser("decorator", &default_parsers->decorator);
+	RegisterParser("font_effect", &default_parsers->font_effect);
 	RegisterParser("transform", &default_parsers->transform);
 	RegisterParser("transform", &default_parsers->transform);
 	RegisterParser("ratio", &default_parsers->ratio);
 	RegisterParser("ratio", &default_parsers->ratio);
 	RegisterParser("resolution", &default_parsers->resolution);
 	RegisterParser("resolution", &default_parsers->resolution);
@@ -410,8 +416,8 @@ void StyleSheetSpecification::RegisterDefaultProperties()
 	RegisterProperty(PropertyId::Transition, "transition", "none", false, false).AddParser("transition");
 	RegisterProperty(PropertyId::Transition, "transition", "none", false, false).AddParser("transition");
 	RegisterProperty(PropertyId::Animation, "animation", "none", false, false).AddParser("animation");
 	RegisterProperty(PropertyId::Animation, "animation", "none", false, false).AddParser("animation");
 
 
-	RegisterProperty(PropertyId::Decorator, "decorator", "", false, false).AddParser("string");
-	RegisterProperty(PropertyId::FontEffect, "font-effect", "", true, false).AddParser("string");
+	RegisterProperty(PropertyId::Decorator, "decorator", "", false, false).AddParser("decorator");
+	RegisterProperty(PropertyId::FontEffect, "font-effect", "", true, false).AddParser("font_effect");
 
 
 	// Rare properties (not added to computed values)
 	// Rare properties (not added to computed values)
 	RegisterProperty(PropertyId::FillImage, "fill-image", "", false, false).AddParser("string");
 	RegisterProperty(PropertyId::FillImage, "fill-image", "", false, false).AddParser("string");

+ 2 - 0
Source/Core/TypeConverter.cpp

@@ -28,9 +28,11 @@
 
 
 #include "../../Include/RmlUi/Core/TypeConverter.h"
 #include "../../Include/RmlUi/Core/TypeConverter.h"
 #include "../../Include/RmlUi/Core/StyleSheetSpecification.h"
 #include "../../Include/RmlUi/Core/StyleSheetSpecification.h"
+#include "../../Include/RmlUi/Core/StyleSheetTypes.h"
 #include "../../Include/RmlUi/Core/Animation.h"
 #include "../../Include/RmlUi/Core/Animation.h"
 #include "../../Include/RmlUi/Core/Transform.h"
 #include "../../Include/RmlUi/Core/Transform.h"
 #include "../../Include/RmlUi/Core/TransformPrimitive.h"
 #include "../../Include/RmlUi/Core/TransformPrimitive.h"
+#include "../../Include/RmlUi/Core/PropertyDictionary.h"
 #include "TransformUtilities.h"
 #include "TransformUtilities.h"
 
 
 namespace Rml {
 namespace Rml {