Browse Source

Can now use a shorthand to declare decorators inside elements.

Michael Ragazzon 6 years ago
parent
commit
bb949df66b

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

@@ -219,6 +219,7 @@ public:
 	/// @param[in] name The name of the property to fetch the value for.
 	/// @param[in] name The name of the property to fetch the value for.
 	/// @return The value of this property for this element, or NULL if this property has not been explicitly defined for this element.
 	/// @return The value of this property for this element, or NULL if this property has not been explicitly defined for this element.
 	const Property* GetLocalProperty(const String& name);
 	const Property* GetLocalProperty(const String& name);
+	const Property* GetLocalProperty(PropertyId id);
 	/// Returns the local properties, excluding any properties from local class.
 	/// Returns the local properties, excluding any properties from local class.
 	/// @return The local properties for this element, or NULL if no properties defined
 	/// @return The local properties for this element, or NULL if no properties defined
 	const PropertyMap* GetLocalProperties();
 	const PropertyMap* GetLocalProperties();

+ 4 - 2
Include/Rocket/Core/PropertySpecification.h

@@ -124,8 +124,10 @@ enum class ShorthandType
 	Replicate,
 	Replicate,
 	// For 'padding', 'margin', etc; up to four properties are expected.
 	// For 'padding', 'margin', etc; up to four properties are expected.
 	Box,
 	Box,
-	// Recursively resolves the full value string on each property, whether it is a normal property or another shorthand.
-	Recursive
+	// Repeatedly resolves the full value string on each property, whether it is a normal property or another shorthand.
+	Recursive,
+	// Comma-separated list of properties or shorthands, the number of declared values must match the specified.
+	RecursiveCommaSeparated
 };
 };
 
 
 enum class ShorthandItemType { Invalid, Property, Shorthand };
 enum class ShorthandItemType { Invalid, Property, Shorthand };

+ 7 - 0
Include/Rocket/Core/StringUtilities.h

@@ -49,6 +49,13 @@ public:
 	/// @param[in] string String to expand.
 	/// @param[in] string String to expand.
 	/// @param[in] delimiter Delimiter found between entries in the string list.
 	/// @param[in] delimiter Delimiter found between entries in the string list.
 	static void ExpandString(StringList& string_list, const String& string, const char delimiter = ',');
 	static void ExpandString(StringList& string_list, const String& string, const char delimiter = ',');
+	/// Expands character-delimited list of values with custom quote characters.
+	/// @param[out] string_list Resulting list of values.
+	/// @param[in] string String to expand.
+	/// @param[in] delimiter Delimiter found between entries in the string list.
+	/// @param[in] quote_character Begin quote
+	/// @param[in] unquote_character End quote
+	static void ExpandString(StringList& string_list, const String& string, const char delimiter, char quote_character, char unquote_character);
 	/// Joins a list of string values into a single string separated by a character delimiter.
 	/// Joins a list of string values into a single string separated by a character delimiter.
 	/// @param[out] string Resulting concatenated string.
 	/// @param[out] string Resulting concatenated string.
 	/// @param[in] string_list Input list of string values.
 	/// @param[in] string_list Input list of string values.

+ 2 - 0
Include/Rocket/Core/StyleSheet.h

@@ -90,6 +90,8 @@ public:
 	/// Returns the Decorator of the given name, or null if it does not exist.
 	/// Returns the Decorator of the given name, or null if it does not exist.
 	Decorator* GetDecorator(const String& name) const;
 	Decorator* GetDecorator(const String& name) const;
 
 
+	Decorator* GetOrInstanceDecorator(const String& decorator_value, const String& source_file, int source_line_number);
+
 	/// Returns the compiled element definition for a given element hierarchy. A reference count will be added for the
 	/// Returns the compiled element definition for a given element hierarchy. A reference count will be added for the
 	/// caller, so another should not be added. The definition should be released by removing the reference count.
 	/// caller, so another should not be added. The definition should be released by removing the reference count.
 	ElementDefinition* GetElementDefinition(const Element* element) const;
 	ElementDefinition* GetElementDefinition(const Element* element) const;

+ 1 - 2
Samples/basic/drag/data/icon.rcss

@@ -10,8 +10,7 @@ icon
     padding: 60px 10px 0px 10px;
     padding: 60px 10px 0px 10px;
     margin: 10px;
     margin: 10px;
     
     
-    background-decorator: image;
-    background-image: ../../../assets/present.tga 0px 0px 100px 100px;
+    decorator: image( ../../../assets/present.tga 0px 0px 100px 100px );
     
     
     font-size: 12px;
     font-size: 12px;
     text-align: center;
     text-align: center;

+ 2 - 0
Source/Core/DecoratorTiledBoxInstancer.cpp

@@ -45,6 +45,8 @@ DecoratorTiledBoxInstancer::DecoratorTiledBoxInstancer()
 	RegisterTileProperty("bottom-image", true);
 	RegisterTileProperty("bottom-image", true);
 
 
 	RegisterTileProperty("center-image", true);
 	RegisterTileProperty("center-image", true);
+
+	RegisterShorthand("decorator", "top-left-image, top-image, top-right-image, left-image, center-image, right-image, bottom-left-image, bottom-image, bottom-right-image", ShorthandType::RecursiveCommaSeparated);
 }
 }
 
 
 DecoratorTiledBoxInstancer::~DecoratorTiledBoxInstancer()
 DecoratorTiledBoxInstancer::~DecoratorTiledBoxInstancer()

+ 1 - 0
Source/Core/DecoratorTiledHorizontalInstancer.cpp

@@ -37,6 +37,7 @@ DecoratorTiledHorizontalInstancer::DecoratorTiledHorizontalInstancer()
 	RegisterTileProperty("left-image", false);
 	RegisterTileProperty("left-image", false);
 	RegisterTileProperty("right-image", false);
 	RegisterTileProperty("right-image", false);
 	RegisterTileProperty("center-image", true);
 	RegisterTileProperty("center-image", true);
+	RegisterShorthand("decorator", "left-image, center-image, right-image", ShorthandType::RecursiveCommaSeparated);
 }
 }
 
 
 DecoratorTiledHorizontalInstancer::~DecoratorTiledHorizontalInstancer()
 DecoratorTiledHorizontalInstancer::~DecoratorTiledHorizontalInstancer()

+ 1 - 0
Source/Core/DecoratorTiledImageInstancer.cpp

@@ -35,6 +35,7 @@ namespace Core {
 DecoratorTiledImageInstancer::DecoratorTiledImageInstancer()
 DecoratorTiledImageInstancer::DecoratorTiledImageInstancer()
 {
 {
 	RegisterTileProperty("image", false);
 	RegisterTileProperty("image", false);
+	RegisterShorthand("decorator", "image", ShorthandType::Recursive);
 }
 }
 
 
 DecoratorTiledImageInstancer::~DecoratorTiledImageInstancer()
 DecoratorTiledImageInstancer::~DecoratorTiledImageInstancer()

+ 6 - 0
Source/Core/DecoratorTiledInstancer.cpp

@@ -82,6 +82,12 @@ void DecoratorTiledInstancer::GetTileProperties(size_t tile_index, DecoratorTile
 	const Property* texture_property = properties.GetProperty(ids.src);
 	const Property* texture_property = properties.GetProperty(ids.src);
 	texture_name = texture_property->Get< String >();
 	texture_name = texture_property->Get< String >();
 	rcss_path = texture_property->source;
 	rcss_path = texture_property->source;
+
+	// Declaring the name 'none' is the same as an empty string. This gives an easy way to skip certain
+	// tiles in a shorthand since we can't always declare an empty string.
+	static const String none_texture_name = "none";
+	if (texture_name == none_texture_name)
+		texture_name.clear();
 }
 }
 
 
 // Loads a single texture coordinate value from the properties.
 // Loads a single texture coordinate value from the properties.

+ 1 - 0
Source/Core/DecoratorTiledVerticalInstancer.cpp

@@ -37,6 +37,7 @@ DecoratorTiledVerticalInstancer::DecoratorTiledVerticalInstancer()
 	RegisterTileProperty("top-image", false);
 	RegisterTileProperty("top-image", false);
 	RegisterTileProperty("bottom-image", false);
 	RegisterTileProperty("bottom-image", false);
 	RegisterTileProperty("center-image", true);
 	RegisterTileProperty("center-image", true);
+	RegisterShorthand("decorator", "top-image, center-image, bottom-image", ShorthandType::RecursiveCommaSeparated);
 }
 }
 
 
 DecoratorTiledVerticalInstancer::~DecoratorTiledVerticalInstancer()
 DecoratorTiledVerticalInstancer::~DecoratorTiledVerticalInstancer()

+ 5 - 0
Source/Core/Element.cpp

@@ -652,6 +652,11 @@ const Property* Element::GetLocalProperty(const String& name)
 	return style->GetLocalProperty(StyleSheetSpecification::GetPropertyId(name));
 	return style->GetLocalProperty(StyleSheetSpecification::GetPropertyId(name));
 }
 }
 
 
+const Property* Element::GetLocalProperty(PropertyId id)
+{
+	return style->GetLocalProperty(id);
+}
+
 const PropertyMap * Element::GetLocalProperties()
 const PropertyMap * Element::GetLocalProperties()
 {
 {
 	return style->GetLocalProperties();
 	return style->GetLocalProperties();

+ 8 - 5
Source/Core/ElementDecoration.cpp

@@ -50,21 +50,24 @@ bool ElementDecoration::ReloadDecorators()
 {
 {
 	ReleaseDecorators();
 	ReleaseDecorators();
 
 
-	const StyleSheet* stylesheet = element->GetStyleSheet();
+	StyleSheet* stylesheet = element->GetStyleSheet();
 	if (!stylesheet)
 	if (!stylesheet)
 		return true;
 		return true;
 
 
-	const String& decorator_value = element->GetComputedValues().decorator;
-	if (decorator_value.empty())
+	const Property* property = element->GetLocalProperty(PropertyId::Decorator);
+	if (!property)
 		return true;
 		return true;
 
 
+	String decorator_value = property->Get<String>();
+
 	// @performance: Can optimize for the case of only one decorator
 	// @performance: Can optimize for the case of only one decorator
 	StringList decorator_list;
 	StringList decorator_list;
-	StringUtilities::ExpandString(decorator_list, decorator_value);
+	// We use custom quote characters as we don't want to split on any commas inside parenthesis, which may appear in decorator shorthands.
+	StringUtilities::ExpandString(decorator_list, decorator_value, ',', '(', ')');
 
 
 	for (const String& name : decorator_list)
 	for (const String& name : decorator_list)
 	{
 	{
-		Decorator* decorator = stylesheet->GetDecorator(name);
+		Decorator* decorator = stylesheet->GetOrInstanceDecorator(name, property->source, property->source_line_number);
 
 
 		if (decorator)
 		if (decorator)
 			LoadDecorator(decorator);
 			LoadDecorator(decorator);

+ 29 - 2
Source/Core/PropertySpecification.cpp

@@ -154,9 +154,9 @@ bool PropertySpecification::RegisterShorthand(const String& shorthand_name, cons
 			if(property)
 			if(property)
 				item = ShorthandItem(id_list[i].property_id, property);
 				item = ShorthandItem(id_list[i].property_id, property);
 		}
 		}
-		else if (id_list[i].type == ShorthandItemType::Shorthand && type == ShorthandType::Recursive)
+		else if (id_list[i].type == ShorthandItemType::Shorthand && (type == ShorthandType::Recursive || type == ShorthandType::RecursiveCommaSeparated))
 		{
 		{
-			// The recursive type (and only that) can hold other shorthands
+			// The recursive types (and only those) can hold other shorthands
 			const ShorthandDefinition* shorthand = GetShorthand(id_list[i].shorthand_id);
 			const ShorthandDefinition* shorthand = GetShorthand(id_list[i].shorthand_id);
 			if (shorthand)
 			if (shorthand)
 				item = ShorthandItem(id_list[i].shorthand_id, shorthand);
 				item = ShorthandItem(id_list[i].shorthand_id, shorthand);
@@ -316,6 +316,33 @@ bool PropertySpecification::ParseShorthandDeclaration(PropertyDictionary& dictio
 		if (!result)
 		if (!result)
 			return false;
 			return false;
 	}
 	}
+	else if (shorthand_definition->type == ShorthandType::RecursiveCommaSeparated)
+	{
+		bool result = true;
+
+		StringList subvalues;
+		StringUtilities::ExpandString(subvalues, property_value);
+
+		if (shorthand_definition->items.size() != subvalues.size())
+		{
+			// We must declare all subvalues
+			return false;
+		}
+
+		for (size_t i = 0; i < shorthand_definition->items.size(); i++)
+		{
+			const ShorthandItem& item = shorthand_definition->items[i];
+			if (item.type == ShorthandItemType::Property)
+				result &= ParsePropertyDeclaration(dictionary, item.property_id, subvalues[i], source_file, source_line_number);
+			else if (item.type == ShorthandItemType::Shorthand)
+				result &= ParseShorthandDeclaration(dictionary, item.shorthand_id, subvalues[i], source_file, source_line_number);
+			else
+				result = false;
+		}
+
+		if (!result)
+			return false;
+	}
 	else
 	else
 	{
 	{
 		size_t value_index = 0;
 		size_t value_index = 0;

+ 50 - 3
Source/Core/StringUtilities.cpp

@@ -39,7 +39,7 @@ void StringUtilities::ExpandString(StringList& string_list, const String& string
 	char quote = 0;
 	char quote = 0;
 	bool last_char_delimiter = true;
 	bool last_char_delimiter = true;
 	const char* ptr = string.c_str();
 	const char* ptr = string.c_str();
-	const char* start_ptr = NULL;
+	const char* start_ptr = nullptr;
 	const char* end_ptr = ptr;
 	const char* end_ptr = ptr;
 
 
 	size_t num_delimiter_values = std::count(string.begin(), string.end(), delimiter);
 	size_t num_delimiter_values = std::count(string.begin(), string.end(), delimiter);
@@ -48,7 +48,7 @@ void StringUtilities::ExpandString(StringList& string_list, const String& string
 		string_list.push_back(StripWhitespace(string));
 		string_list.push_back(StripWhitespace(string));
 		return;
 		return;
 	}
 	}
-	string_list.reserve(num_delimiter_values + 1);
+	string_list.reserve(string_list.size() + num_delimiter_values + 1);
 
 
 	while (*ptr)
 	while (*ptr)
 	{
 	{
@@ -71,7 +71,7 @@ void StringUtilities::ExpandString(StringList& string_list, const String& string
 			else
 			else
 				string_list.emplace_back();
 				string_list.emplace_back();
 			last_char_delimiter = true;
 			last_char_delimiter = true;
-			start_ptr = NULL;
+			start_ptr = nullptr;
 		}
 		}
 		// Otherwise if its not white space or we're in quote mode, advance the pointers
 		// Otherwise if its not white space or we're in quote mode, advance the pointers
 		else if (!isspace(*ptr) || quote)
 		else if (!isspace(*ptr) || quote)
@@ -90,6 +90,53 @@ void StringUtilities::ExpandString(StringList& string_list, const String& string
 		string_list.emplace_back(start_ptr, end_ptr + 1);
 		string_list.emplace_back(start_ptr, end_ptr + 1);
 }
 }
 
 
+
+void StringUtilities::ExpandString(StringList& string_list, const String& string, const char delimiter, char quote_character, char unquote_character)
+{
+	int quote_mode_depth = 0;
+	const char* ptr = string.c_str();
+	const char* start_ptr = nullptr;
+	const char* end_ptr = ptr;
+
+	while (*ptr)
+	{
+		// Increment the quote depth for each quote character encountered
+		if (*ptr == quote_character)
+		{
+			++quote_mode_depth;
+		}
+		// And decrement it for every unquote character
+		else if (*ptr == unquote_character)
+		{
+			--quote_mode_depth;
+		}
+
+		// If we encouter a delimiter while not in quote mode, add the item to the list
+		if (*ptr == delimiter && quote_mode_depth == 0)
+		{
+			if (start_ptr)
+				string_list.emplace_back(start_ptr, end_ptr + 1);
+			else
+				string_list.emplace_back();
+			start_ptr = nullptr;
+		}
+		// Otherwise if its not white space or we're in quote mode, advance the pointers
+		else if (!isspace(*ptr) || quote_mode_depth > 0)
+		{
+			if (!start_ptr)
+				start_ptr = ptr;
+			end_ptr = ptr;
+		}
+
+		ptr++;
+	}
+
+	// If there's data pending, add it.
+	if (start_ptr)
+		string_list.emplace_back(start_ptr, end_ptr + 1);
+}
+
+
 // Joins a list of string values into a single string separated by a character delimiter.
 // Joins a list of string values into a single string separated by a character delimiter.
 void StringUtilities::JoinString(String& string, const StringList& string_list, const char delimiter)
 void StringUtilities::JoinString(String& string, const StringList& string_list, const char delimiter)
 {
 {

+ 54 - 0
Source/Core/StyleSheet.cpp

@@ -142,6 +142,60 @@ Decorator* StyleSheet::GetDecorator(const String& name) const
 	return it->second.decorator;
 	return it->second.decorator;
 }
 }
 
 
+Decorator* StyleSheet::GetOrInstanceDecorator(const String& decorator_value, const String& source_file, int source_line_number)
+{
+	// Try to find a decorator declared with @decorator or otherwise previously instanced shorthand decorator.
+	auto it_find = decorator_map.find(decorator_value);
+	if (it_find != decorator_map.end())
+	{
+		return it_find->second.decorator;
+	}
+
+	// The decorator does not exist, try to instance a new one from a shorthand decorator value declared as:
+	//   decorator: <type>( <shorthand> );
+	// where <type> is the decorator type and the value <shorthand> is applied to its "decorator"-shorthand.
+
+	// Check syntax
+	size_t shorthand_open = decorator_value.find('(');
+	size_t shorthand_close = decorator_value.rfind(')');
+	if (shorthand_open == String::npos || shorthand_close == String::npos || shorthand_open >= shorthand_close)
+		return nullptr;
+
+	String type = StringUtilities::StripWhitespace(decorator_value.substr(0, shorthand_open));
+
+	// Check for valid decorator type
+	const PropertySpecification* specification = Factory::GetDecoratorPropertySpecification(type);
+	if (!specification)
+		return nullptr;
+
+	String shorthand = decorator_value.substr(shorthand_open + 1, shorthand_close - shorthand_open - 1);
+
+	// Parse the shorthand properties
+	PropertyDictionary properties;
+	if (!specification->ParsePropertyDeclaration(properties, "decorator", shorthand, source_file, source_line_number))
+	{
+		Log::Message(Log::LT_WARNING, "Could not parse decorator value '%s' at %s:%d", decorator_value.c_str(), source_file.c_str(), source_line_number);
+		return nullptr;
+	}
+
+	specification->SetPropertyDefaults(properties);
+
+	Decorator* decorator = Factory::InstanceDecorator(type, properties);
+	if (!decorator)
+		return nullptr;
+
+	// Insert decorator into map
+	auto result = decorator_map.emplace(decorator_value, DecoratorSpecification{ type, properties, decorator });
+	
+	if (!result.second)
+	{
+		decorator->RemoveReference();
+		return nullptr;
+	}
+
+	return decorator;
+}
+
 // Returns the compiled element definition for a given element hierarchy.
 // Returns the compiled element definition for a given element hierarchy.
 ElementDefinition* StyleSheet::GetElementDefinition(const Element* element) const
 ElementDefinition* StyleSheet::GetElementDefinition(const Element* element) const
 {
 {

+ 16 - 59
Source/Core/StyleSheetParser.cpp

@@ -227,12 +227,10 @@ int StyleSheetParser::Parse(StyleSheetNode* node, KeyframesMap& keyframes, Decor
 	stream = _stream;
 	stream = _stream;
 	stream_file_name = Replace(stream->GetSourceURL().GetURL(), "|", ":");
 	stream_file_name = Replace(stream->GetSourceURL().GetURL(), "|", ":");
 
 
-	enum class State { Global, AtRuleIdentifier, AtRuleBlock, Invalid };
+	enum class State { Global, AtRuleIdentifier, KeyframeBlock, Invalid };
 	State state = State::Global;
 	State state = State::Global;
 
 
 	// At-rules given by the following syntax in global space: @identifier name { block }
 	// At-rules given by the following syntax in global space: @identifier name { block }
-	enum class AtRule { None, Keyframes, Decorator };
-	AtRule at_rule = AtRule::None;
 	String at_rule_name;
 	String at_rule_name;
 
 
 	// Look for more styles while data is available
 	// Look for more styles while data is available
@@ -281,21 +279,18 @@ int StyleSheetParser::Parse(StyleSheetNode* node, KeyframesMap& keyframes, Decor
 
 
 					if (at_rule_identifier == KEYFRAMES)
 					if (at_rule_identifier == KEYFRAMES)
 					{
 					{
-						at_rule = AtRule::Keyframes;
-						state = State::AtRuleBlock;
+						state = State::KeyframeBlock;
 					}
 					}
 					else if (at_rule_identifier == "decorator")
 					else if (at_rule_identifier == "decorator")
 					{
 					{
 						ParseDecoratorBlock(decorator_map, at_rule_name);
 						ParseDecoratorBlock(decorator_map, at_rule_name);
 						
 						
-						at_rule = AtRule::None;
 						at_rule_name.clear();
 						at_rule_name.clear();
 						state = State::Global;
 						state = State::Global;
 					}
 					}
 					else
 					else
 					{
 					{
 						// Invalid identifier, should ignore
 						// Invalid identifier, should ignore
-						at_rule = AtRule::None;
 						at_rule_name.clear();
 						at_rule_name.clear();
 						state = State::Global;
 						state = State::Global;
 						Log::Message(Log::LT_WARNING, "Invalid at-rule identifier '%s' found in stylesheet at %s:%d", at_rule_identifier.c_str(), stream_file_name.c_str(), line_number);
 						Log::Message(Log::LT_WARNING, "Invalid at-rule identifier '%s' found in stylesheet at %s:%d", at_rule_identifier.c_str(), stream_file_name.c_str(), line_number);
@@ -309,65 +304,27 @@ int StyleSheetParser::Parse(StyleSheetNode* node, KeyframesMap& keyframes, Decor
 				}
 				}
 			}
 			}
 			break;
 			break;
-			case State::AtRuleBlock:
+			case State::KeyframeBlock:
 			{
 			{
-				switch (at_rule)
-				{
-				case AtRule::Keyframes:
+				if (token == '{')
 				{
 				{
-					if (token == '{')
-					{
-						// Each keyframe in keyframes has its own block which is processed here
-						state = State::AtRuleBlock;
-
-						PropertyDictionary properties;
-						if (!ReadProperties(properties, StyleSheetSpecification::GetPropertySpecification()))
-							continue;
+					// Each keyframe in keyframes has its own block which is processed here
+					PropertyDictionary properties;
+					if (!ReadProperties(properties, StyleSheetSpecification::GetPropertySpecification()))
+						continue;
 
 
-						if (!ParseKeyframeBlock(keyframes, at_rule_name, pre_token_str, properties))
-							continue;
-					}
-					else if (token == '}')
-					{
-						at_rule = AtRule::None;
-						at_rule_name.clear();
-						state = State::Global;
-					}
-					else
-					{
-						Log::Message(Log::LT_WARNING, "Invalid character '%c' found while parsing keyframe block in stylesheet at %s:%d", token, stream_file_name.c_str(), line_number);
-						state = State::Invalid;
-					}
+					if (!ParseKeyframeBlock(keyframes, at_rule_name, pre_token_str, properties))
+						continue;
 				}
 				}
-				break;
-				case AtRule::Decorator:
+				else if (token == '}')
 				{
 				{
-					if (token == '{')
-					{
-						// Process the decorator
-						ParseDecoratorBlock(decorator_map, at_rule_name);
-
-					}
-					else
-					{
-						Log::Message(Log::LT_WARNING, "Invalid character '%c' found while parsing decorator block in stylesheet at %s:%d", token, stream_file_name.c_str(), line_number);
-						state = State::Invalid;
-					}
+					at_rule_name.clear();
+					state = State::Global;
 				}
 				}
-				break;
-				case AtRule::None:
+				else
 				{
 				{
-					// Invalid at-rule, trying to continue
-					if (token == '}')
-					{
-						at_rule = AtRule::None;
-						at_rule_name.clear();
-						state = State::Global;
-					}
-				}
-				break;
-				default:
-					ROCKET_ERROR;
+					Log::Message(Log::LT_WARNING, "Invalid character '%c' found while parsing keyframe block in stylesheet at %s:%d", token, stream_file_name.c_str(), line_number);
+					state = State::Invalid;
 				}
 				}
 			}
 			}
 			break;
 			break;