浏览代码

WIP adding spritesheets

Michael Ragazzon 6 年之前
父节点
当前提交
446e823e02

+ 1 - 1
Include/Rocket/Core/DecoratorInstancer.h

@@ -79,7 +79,7 @@ protected:
 	/// @param[in] properties A comma-separated list of the properties this definition is shorthand for. The order in which they are specified here is the order in which the values will be processed.
 	/// @param[in] properties A comma-separated list of the properties this definition is shorthand for. The order in which they are specified here is the order in which the values will be processed.
 	/// @param[in] type The type of shorthand to declare.
 	/// @param[in] type The type of shorthand to declare.
 	/// @param True if all the property names exist, false otherwise.
 	/// @param True if all the property names exist, false otherwise.
-	bool RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type);
+	ShorthandId RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type);
 
 
 	// Releases the instancer.
 	// Releases the instancer.
 	virtual void OnReferenceDeactivate();
 	virtual void OnReferenceDeactivate();

+ 1 - 1
Include/Rocket/Core/FontEffectInstancer.h

@@ -80,7 +80,7 @@ protected:
 	/// @param[in] properties A comma-separated list of the properties this definition is shorthand for. The order in which they are specified here is the order in which the values will be processed.
 	/// @param[in] properties A comma-separated list of the properties this definition is shorthand for. The order in which they are specified here is the order in which the values will be processed.
 	/// @param[in] type The type of shorthand to declare.
 	/// @param[in] type The type of shorthand to declare.
 	/// @param True if all the property names exist, false otherwise.
 	/// @param True if all the property names exist, false otherwise.
-	bool RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type);
+	ShorthandId RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type);
 
 
 	// Releases the instancer.
 	// Releases the instancer.
 	virtual void OnReferenceDeactivate();
 	virtual void OnReferenceDeactivate();

+ 1 - 1
Include/Rocket/Core/PropertySpecification.h

@@ -184,7 +184,7 @@ public:
 	/// @param[in] type The type of shorthand to declare.
 	/// @param[in] type The type of shorthand to declare.
 	/// @param[in] id If 'Invalid' then automatically assigns a new id, otherwise assigns the given id.
 	/// @param[in] id If 'Invalid' then automatically assigns a new id, otherwise assigns the given id.
 	/// @param True if all the property names exist, false otherwise.
 	/// @param True if all the property names exist, false otherwise.
-	bool RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type, ShorthandId id = ShorthandId::Invalid);
+	ShorthandId RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type, ShorthandId id = ShorthandId::Invalid);
 	/// Returns a shorthand definition.
 	/// Returns a shorthand definition.
 	/// @param[in] shorthand_name The name of the desired shorthand.
 	/// @param[in] shorthand_name The name of the desired shorthand.
 	/// @return The appropriate shorthand definition if it could be found, NULL otherwise.
 	/// @return The appropriate shorthand definition if it could be found, NULL otherwise.

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

@@ -32,6 +32,7 @@
 #include "ReferenceCountable.h"
 #include "ReferenceCountable.h"
 #include <set>
 #include <set>
 #include "PropertyDictionary.h"
 #include "PropertyDictionary.h"
+#include "Texture.h"
 
 
 namespace Rocket {
 namespace Rocket {
 namespace Core {
 namespace Core {
@@ -40,6 +41,7 @@ class Element;
 class ElementDefinition;
 class ElementDefinition;
 class StyleSheetNode;
 class StyleSheetNode;
 class Decorator;
 class Decorator;
+struct Spritesheet;
 
 
 struct KeyframeBlock {
 struct KeyframeBlock {
 	float normalized_time;  // [0, 1]
 	float normalized_time;  // [0, 1]
@@ -57,6 +59,78 @@ struct DecoratorSpecification {
 	Decorator* decorator = nullptr;
 	Decorator* decorator = nullptr;
 };
 };
 
 
+struct Sprite {
+	Rectangle rectangle;
+	Spritesheet* sprite_sheet;
+	String name;
+};
+
+struct Spritesheet {
+	String name;
+	String image_source;
+	String definition_source;
+	int definition_line_number;
+	Texture texture;
+	std::vector<std::unique_ptr<Sprite>> sprites;
+
+	Spritesheet(const String& name, const String& definition_source, int definition_line_number) : name(name), definition_source(definition_source), definition_line_number(definition_line_number) {}
+
+	void AddSprite(const String& name, Rectangle rectangle)
+	{
+		sprites.emplace_back(new Sprite{ rectangle, this, name });
+	}
+};
+
+using SpriteMap = UnorderedMap<String, const Sprite*>;
+using SpritesheetMap = UnorderedMap<String, std::unique_ptr<Spritesheet>>;
+
+class SpriteSheetList {
+public:
+	bool AddSpriteSheet(std::unique_ptr<Spritesheet> in_sprite_sheet) {
+		ROCKET_ASSERT(in_sprite_sheet);
+		Spritesheet& spritesheet = *in_sprite_sheet;
+		auto result = spritesheet_map.emplace(spritesheet.name, std::move(in_sprite_sheet));
+		if (!result.second)
+		{
+			Log::Message(Log::LT_WARNING, "Spritesheet '%s' has the same name as another spritesheet, skipped. See %s:%d", spritesheet.name.c_str(), spritesheet.definition_source.c_str(), spritesheet.definition_line_number);
+			return false;
+		}
+
+		auto& sprites = spritesheet.sprites;
+		for (auto it = sprites.begin(); it != sprites.end();)
+		{
+			ROCKET_ASSERT(*it);
+			const String& name = (*it)->name;
+			auto result = sprite_map.emplace(name, it->get());
+			if (!result.second)
+			{
+				Log::Message(Log::LT_WARNING, "Sprite '%s' has the same name as another sprite, skipped. See %s:%d", name.c_str(), spritesheet.definition_source.c_str(), spritesheet.definition_line_number);
+				it = sprites.erase(it);
+			}
+			else
+				++it;
+		}
+
+		// Load the texture
+		spritesheet.texture.Load(spritesheet.image_source, spritesheet.definition_source);
+		
+		return true;
+	}
+
+	const Sprite* GetSprite(const String& name)
+	{
+		auto it = sprite_map.find(name);
+		if (it != sprite_map.end())
+			return it->second;
+		return nullptr;
+	}
+
+
+private:
+	SpritesheetMap spritesheet_map;
+	SpriteMap sprite_map;
+};
+
 using DecoratorSpecificationMap = UnorderedMap<String, DecoratorSpecification>;
 using DecoratorSpecificationMap = UnorderedMap<String, DecoratorSpecification>;
 
 
 
 
@@ -117,6 +191,8 @@ private:
 	// Name of every @decorator mapped to their specification
 	// Name of every @decorator mapped to their specification
 	DecoratorSpecificationMap decorator_map;
 	DecoratorSpecificationMap decorator_map;
 
 
+	SpriteSheetList sprite_sheets;
+
 	// Map of only nodes with actual style information.
 	// Map of only nodes with actual style information.
 	NodeIndex styled_node_index;
 	NodeIndex styled_node_index;
 	// Map of every node, even empty, un-styled, nodes.
 	// Map of every node, even empty, un-styled, nodes.

+ 2 - 2
Include/Rocket/Core/StyleSheetSpecification.h

@@ -86,7 +86,7 @@ public:
 	/// @param[in] properties A comma-separated list of the properties this definition is shorthand for. The order in which they are specified here is the order in which the values will be processed.
 	/// @param[in] properties A comma-separated list of the properties this definition is shorthand for. The order in which they are specified here is the order in which the values will be processed.
 	/// @param[in] type The type of shorthand to declare.
 	/// @param[in] type The type of shorthand to declare.
 	/// @param True if all the property names exist, false otherwise.
 	/// @param True if all the property names exist, false otherwise.
-	static bool RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type);
+	static ShorthandId RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type);
 	/// Returns a shorthand definition.
 	/// Returns a shorthand definition.
 	/// @param[in] shorthand_name The name of the desired shorthand.
 	/// @param[in] shorthand_name The name of the desired shorthand.
 	/// @return The appropriate shorthand definition if it could be found, NULL otherwise.
 	/// @return The appropriate shorthand definition if it could be found, NULL otherwise.
@@ -116,7 +116,7 @@ private:
 	~StyleSheetSpecification();
 	~StyleSheetSpecification();
 
 
 	PropertyDefinition& RegisterProperty(PropertyId id, const String& property_name, const String& default_value, bool inherited, bool forces_layout = false);
 	PropertyDefinition& RegisterProperty(PropertyId id, const String& property_name, const String& default_value, bool inherited, bool forces_layout = false);
-	bool RegisterShorthand(ShorthandId id, const String& shorthand_name, const String& property_names, ShorthandType type);
+	ShorthandId RegisterShorthand(ShorthandId id, const String& shorthand_name, const String& property_names, ShorthandType type);
 
 
 	// Registers Rocket's default parsers.
 	// Registers Rocket's default parsers.
 	void RegisterDefaultParsers();
 	void RegisterDefaultParsers();

+ 5 - 0
Include/Rocket/Core/Types.h

@@ -92,6 +92,11 @@ typedef Vector3< float > Vector3f;
 typedef Vector4< int > Vector4i;
 typedef Vector4< int > Vector4i;
 typedef Vector4< float > Vector4f;
 typedef Vector4< float > Vector4f;
 
 
+struct Rectangle {
+	Rectangle(float x = 0, float y = 0, float width = 0, float height = 0) : x(x), y(y), width(width), height(height) {}
+	float x, y, width, height;
+};
+
 typedef Matrix4< float, ColumnMajorStorage< float > > ColumnMajorMatrix4f;
 typedef Matrix4< float, ColumnMajorStorage< float > > ColumnMajorMatrix4f;
 typedef Matrix4< float, RowMajorStorage< float > > RowMajorMatrix4f;
 typedef Matrix4< float, RowMajorStorage< float > > RowMajorMatrix4f;
 typedef ColumnMajorMatrix4f Matrix4f;
 typedef ColumnMajorMatrix4f Matrix4f;

+ 23 - 1
Samples/assets/invader.rcss

@@ -46,12 +46,30 @@ div#title_bar div#icon
 	height: 39px;
 	height: 39px;
 }
 }
 
 
+@spritesheet theme {
+	src: invader.tga;
+	title-bar-l: 147px 0px 82px 85px;
+	title-bar-c: 229px 0px 1px 85px;
+	title-bar-r: 231px 0px 15px 85px;
+	
+	window-tl: 0px 0px 133px 140px;
+	window-t:  134px 0px 1px 140px;
+	window-tr: 136px 0px 10px 140px;
+	window-l:  0px 139px 10px 1px;
+	window-c:  11px 139px 12px 1px;
+	window-r:  10px 139px -10px 1px; /* mirrored left */
+	window-bl: 0px 140px 11px 11px;
+	window-b:  11px 140px 1px 11px;
+	window-br: 136px 140px 10px 11px;
+}
+
 @decorator title_bar : tiled-horizontal {
 @decorator title_bar : tiled-horizontal {
 	left-image: invader.tga 147px 0px 229px 85px;
 	left-image: invader.tga 147px 0px 229px 85px;
 	center-image: invader.tga stretch 229px 0px 230px 85px;
 	center-image: invader.tga stretch 229px 0px 230px 85px;
 	right-image: invader.tga 231px 0px 246px 85px;
 	right-image: invader.tga 231px 0px 246px 85px;
 }
 }
 
 
+
 div#title_bar span
 div#title_bar span
 {
 {
 	padding-left: 85px;
 	padding-left: 85px;
@@ -87,7 +105,11 @@ div#window
 	width: auto;
 	width: auto;
 	padding: 10px 15px;
 	padding: 10px 15px;
 
 
-	decorator: window;
+	decorator: tiled-box(
+		window-tl, window-t stretch, window-tr, 
+		window-l stretch, window-c stretch, window-r stretch,
+		window-bl, window-b stretch, window-br
+	);
 }
 }
 
 
 div#content
 div#content

+ 1 - 1
Source/Core/DecoratorInstancer.cpp

@@ -52,7 +52,7 @@ PropertyDefinition& DecoratorInstancer::RegisterProperty(const String& property_
 }
 }
 
 
 // Registers a shorthand property definition.
 // Registers a shorthand property definition.
-bool DecoratorInstancer::RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type)
+ShorthandId DecoratorInstancer::RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type)
 {
 {
 	return properties.RegisterShorthand(shorthand_name, property_names, type);
 	return properties.RegisterShorthand(shorthand_name, property_names, type);
 }
 }

+ 1 - 1
Source/Core/FontEffectInstancer.cpp

@@ -56,7 +56,7 @@ PropertyDefinition& FontEffectInstancer::RegisterProperty(const String& property
 }
 }
 
 
 // Registers a shorthand property definition.
 // Registers a shorthand property definition.
-bool FontEffectInstancer::RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type)
+ShorthandId FontEffectInstancer::RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type)
 {
 {
 	return properties.RegisterShorthand(shorthand_name, property_names, type);
 	return properties.RegisterShorthand(shorthand_name, property_names, type);
 }
 }

+ 10 - 8
Source/Core/PropertySpecification.cpp

@@ -35,9 +35,11 @@
 namespace Rocket {
 namespace Rocket {
 namespace Core {
 namespace Core {
 
 
-PropertySpecification::PropertySpecification(size_t reserve_num_properties, size_t reserve_num_shorthands) 
-	: properties(reserve_num_properties, nullptr), shorthands(reserve_num_shorthands, nullptr), property_map(reserve_num_properties), shorthand_map(reserve_num_shorthands)
+PropertySpecification::PropertySpecification(size_t reserve_num_properties, size_t reserve_num_shorthands) : 
+	// Increment reserve numbers by one because the 'invalid' property occupies the first element
+	properties(reserve_num_properties + 1, nullptr), shorthands(reserve_num_shorthands + 1, nullptr), property_map(reserve_num_properties + 1), shorthand_map(reserve_num_shorthands + 1)
 {
 {
+	property_names.reserve(reserve_num_properties);
 }
 }
 
 
 PropertySpecification::~PropertySpecification()
 PropertySpecification::~PropertySpecification()
@@ -112,7 +114,7 @@ const PropertyNameList& PropertySpecification::GetRegisteredInheritedProperties(
 }
 }
 
 
 // Registers a shorthand property definition.
 // Registers a shorthand property definition.
-bool PropertySpecification::RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type, ShorthandId id)
+ShorthandId PropertySpecification::RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type, ShorthandId id)
 {
 {
 	if (id == ShorthandId::Invalid)
 	if (id == ShorthandId::Invalid)
 		id = shorthand_map.GetOrCreateId(shorthand_name);
 		id = shorthand_map.GetOrCreateId(shorthand_name);
@@ -138,10 +140,10 @@ bool PropertySpecification::RegisterShorthand(const String& shorthand_name, cons
 	}
 	}
 
 
 	if (id_list.empty() || id_list.size() != property_list.size())
 	if (id_list.empty() || id_list.size() != property_list.size())
-		return false;
+		return ShorthandId::Invalid;
 
 
 	if (id_list.empty())
 	if (id_list.empty())
-		return false;
+		return ShorthandId::Invalid;
 
 
 	// Construct the new shorthand definition and resolve its properties.
 	// Construct the new shorthand definition and resolve its properties.
 	std::unique_ptr<ShorthandDefinition> property_shorthand(new ShorthandDefinition());
 	std::unique_ptr<ShorthandDefinition> property_shorthand(new ShorthandDefinition());
@@ -165,7 +167,7 @@ bool PropertySpecification::RegisterShorthand(const String& shorthand_name, cons
 		if (item.type == ShorthandItemType::Invalid)
 		if (item.type == ShorthandItemType::Invalid)
 		{
 		{
 			Log::Message(Log::LT_ERROR, "Shorthand property '%s' was registered with invalid property '%s'.", shorthand_name.c_str(), property_list[i].c_str());
 			Log::Message(Log::LT_ERROR, "Shorthand property '%s' was registered with invalid property '%s'.", shorthand_name.c_str(), property_list[i].c_str());
-			return false;
+			return ShorthandId::Invalid;
 		}
 		}
 		property_shorthand->items.push_back(item);
 		property_shorthand->items.push_back(item);
 	}
 	}
@@ -182,7 +184,7 @@ bool PropertySpecification::RegisterShorthand(const String& shorthand_name, cons
 		if (shorthands[index])
 		if (shorthands[index])
 		{
 		{
 			Log::Message(Log::LT_ERROR, "The shorthand '%s' already exists, ignoring.", shorthand_name.c_str());
 			Log::Message(Log::LT_ERROR, "The shorthand '%s' already exists, ignoring.", shorthand_name.c_str());
-			return false;
+			return ShorthandId::Invalid;
 		}
 		}
 	}
 	}
 	else
 	else
@@ -192,7 +194,7 @@ bool PropertySpecification::RegisterShorthand(const String& shorthand_name, cons
 	}
 	}
 
 
 	shorthands[index] = property_shorthand.release();
 	shorthands[index] = property_shorthand.release();
-	return true;
+	return id;
 }
 }
 
 
 // Returns a shorthand definition.
 // Returns a shorthand definition.

+ 1 - 1
Source/Core/StyleSheet.cpp

@@ -77,7 +77,7 @@ StyleSheet::~StyleSheet()
 bool StyleSheet::LoadStyleSheet(Stream* stream)
 bool StyleSheet::LoadStyleSheet(Stream* stream)
 {
 {
 	StyleSheetParser parser;
 	StyleSheetParser parser;
-	specificity_offset = parser.Parse(root, keyframes, decorator_map, stream);
+	specificity_offset = parser.Parse(root, keyframes, decorator_map, sprite_sheets, stream);
 	return specificity_offset >= 0;
 	return specificity_offset >= 0;
 }
 }
 
 

+ 126 - 7
Source/Core/StyleSheetParser.cpp

@@ -30,6 +30,7 @@
 #include <algorithm>
 #include <algorithm>
 #include "StyleSheetFactory.h"
 #include "StyleSheetFactory.h"
 #include "StyleSheetNode.h"
 #include "StyleSheetNode.h"
+#include "ComputeProperty.h"
 #include "../../Include/Rocket/Core/Log.h"
 #include "../../Include/Rocket/Core/Log.h"
 #include "../../Include/Rocket/Core/StreamMemory.h"
 #include "../../Include/Rocket/Core/StreamMemory.h"
 #include "../../Include/Rocket/Core/StyleSheet.h"
 #include "../../Include/Rocket/Core/StyleSheet.h"
@@ -38,6 +39,90 @@
 namespace Rocket {
 namespace Rocket {
 namespace Core {
 namespace Core {
 
 
+
+class AbstractPropertyParser {
+public:
+	virtual bool Parse(const String& name, const String& value, const String& stream_file_name, int rule_line_number) = 0;
+};
+
+/*
+ *  PropertySpecificationParser just passes the parsing to a property specification. Usually
+ *    the main stylesheet specification, except for e.g. @decorator blocks.
+*/
+class PropertySpecificationParser : public AbstractPropertyParser {
+private:
+	PropertyDictionary& properties;
+	const PropertySpecification& specification;
+
+public:
+	PropertySpecificationParser(PropertyDictionary& properties, const PropertySpecification& specification) : properties(properties), specification(specification) {}
+
+	bool Parse(const String& name, const String& value, const String& stream_file_name, int rule_line_number) override
+	{
+		return specification.ParsePropertyDeclaration(properties, name, value, stream_file_name, rule_line_number);
+	}
+};
+
+/*
+ *  Spritesheets need a special parser because its property names are really arbitrary keys,
+ *    while its values are always rectangles. Thus, it must be parsed with a special "rectangle" parser
+ *    for every name-value pair.
+*/
+class SpritesheetPropertyParser : public AbstractPropertyParser {
+private:
+	Spritesheet* spritesheet = nullptr;
+	PropertyDictionary properties;
+	PropertySpecification specification;
+	PropertyId id_rx, id_ry, id_rw, id_rh;
+	ShorthandId id_rectangle;
+public:
+	SpritesheetPropertyParser() : specification(4, 1) 
+	{
+		id_rx = specification.RegisterProperty("rectangle-y", "", false, false).AddParser("length").GetId();
+		id_ry = specification.RegisterProperty("rectangle-w", "", false, false).AddParser("length").GetId();
+		id_rw = specification.RegisterProperty("rectangle-h", "", false, false).AddParser("length").GetId();
+		id_rh = specification.RegisterProperty("rectangle-x", "", false, false).AddParser("length").GetId();
+		id_rectangle = specification.RegisterShorthand("rectangle", "rectangle-x, rectangle-y, rectangle-w, rectangle-h", ShorthandType::FallThrough);
+	}
+
+	void SetSpritesheet(Spritesheet* in_spritesheet) {
+		spritesheet = in_spritesheet;
+	}
+
+	bool Parse(const String& name, const String& value, const String& stream_file_name, int rule_line_number) override
+	{
+		ROCKET_ASSERT(spritesheet);
+
+		static const String str_src = "src";
+		static const String str_rectangle = "rectangle";
+		if (name == str_src)
+		{
+			spritesheet->image_source = value;
+		}
+		else
+		{
+			if (!specification.ParseShorthandDeclaration(properties, id_rectangle, value, stream_file_name, rule_line_number))
+				return false;
+
+			Rectangle rectangle;
+			if (auto property = properties.GetProperty(id_rx))
+				rectangle.x = ComputeAbsoluteLength(*property, 1.f);
+			if (auto property = properties.GetProperty(id_ry))
+				rectangle.y = ComputeAbsoluteLength(*property, 1.f);
+			if (auto property = properties.GetProperty(id_rw))
+				rectangle.width = ComputeAbsoluteLength(*property, 1.f);
+			if (auto property = properties.GetProperty(id_rh))
+				rectangle.height = ComputeAbsoluteLength(*property, 1.f);
+
+			spritesheet->AddSprite(name, rectangle);
+		}
+
+		return true;
+	}
+};
+
+
+
 StyleSheetParser::StyleSheetParser()
 StyleSheetParser::StyleSheetParser()
 {
 {
 	line_number = 0;
 	line_number = 0;
@@ -202,7 +287,8 @@ bool StyleSheetParser::ParseDecoratorBlock(DecoratorSpecificationMap& decorator_
 		}
 		}
 	}
 	}
 
 
-	if (!ReadProperties(properties, *property_specification))
+	PropertySpecificationParser parser(properties, *property_specification);
+	if (!ReadProperties(parser))
 		return false;
 		return false;
 
 
 	// Set non-defined properties to their defaults
 	// Set non-defined properties to their defaults
@@ -220,7 +306,7 @@ bool StyleSheetParser::ParseDecoratorBlock(DecoratorSpecificationMap& decorator_
 	return true;
 	return true;
 }
 }
 
 
-int StyleSheetParser::Parse(StyleSheetNode* node, KeyframesMap& keyframes, DecoratorSpecificationMap& decorator_map, Stream* _stream)
+int StyleSheetParser::Parse(StyleSheetNode* node, KeyframesMap& keyframes, DecoratorSpecificationMap& decorator_map, SpriteSheetList& sprite_sheets, Stream* _stream)
 {
 {
 	int rule_count = 0;
 	int rule_count = 0;
 	line_number = 0;
 	line_number = 0;
@@ -248,7 +334,8 @@ int StyleSheetParser::Parse(StyleSheetNode* node, KeyframesMap& keyframes, Decor
 				{
 				{
 					// Read the attributes
 					// Read the attributes
 					PropertyDictionary properties;
 					PropertyDictionary properties;
-					if (!ReadProperties(properties, StyleSheetSpecification::GetPropertySpecification()))
+					PropertySpecificationParser parser(properties, StyleSheetSpecification::GetPropertySpecification());
+					if (!ReadProperties(parser))
 						continue;
 						continue;
 
 
 					StringList style_name_list;
 					StringList style_name_list;
@@ -288,6 +375,36 @@ int StyleSheetParser::Parse(StyleSheetNode* node, KeyframesMap& keyframes, Decor
 						at_rule_name.clear();
 						at_rule_name.clear();
 						state = State::Global;
 						state = State::Global;
 					}
 					}
+					else if (at_rule_identifier == "spritesheet")
+					{
+						// This is reasonably heavy to initialize, so we make it static
+						static SpritesheetPropertyParser spritesheet_property_parser;
+
+						std::unique_ptr<Spritesheet> sprite_sheet(new Spritesheet(at_rule_name, stream_file_name, (int)line_number));
+						spritesheet_property_parser.SetSpritesheet(sprite_sheet.get());
+
+						ReadProperties(spritesheet_property_parser);
+
+						if (sprite_sheet->sprites.empty())
+						{
+							Log::Message(Log::LT_WARNING, "Spritesheet with name '%s' had no sprites defined, ignored. At %s:%d", at_rule_name.c_str(), stream_file_name.c_str(), line_number);
+						}
+						else if (sprite_sheet->name.empty())
+						{
+							Log::Message(Log::LT_WARNING, "No name given for @spritesheet at %s:%d", stream_file_name.c_str(), line_number);
+						}
+						else if (sprite_sheet->image_source.empty())
+						{
+							Log::Message(Log::LT_WARNING, "No image source (property 'src') specified for spritesheet '%s'. At %s:%d", at_rule_name.c_str(), stream_file_name.c_str(), line_number);
+						}
+						else
+						{
+							sprite_sheets.AddSpriteSheet(std::move(sprite_sheet));
+						}
+
+						at_rule_name.clear();
+						state = State::Global;
+					}
 					else
 					else
 					{
 					{
 						// Invalid identifier, should ignore
 						// Invalid identifier, should ignore
@@ -310,7 +427,7 @@ int StyleSheetParser::Parse(StyleSheetNode* node, KeyframesMap& keyframes, Decor
 				{
 				{
 					// Each keyframe in keyframes has its own block which is processed here
 					// Each keyframe in keyframes has its own block which is processed here
 					PropertyDictionary properties;
 					PropertyDictionary properties;
-					if (!ReadProperties(properties, StyleSheetSpecification::GetPropertySpecification()))
+					PropertySpecificationParser parser(properties, StyleSheetSpecification::GetPropertySpecification());
 						continue;
 						continue;
 
 
 					if (!ParseKeyframeBlock(keyframes, at_rule_name, pre_token_str, properties))
 					if (!ParseKeyframeBlock(keyframes, at_rule_name, pre_token_str, properties))
@@ -350,13 +467,15 @@ int StyleSheetParser::Parse(StyleSheetNode* node, KeyframesMap& keyframes, Decor
 bool StyleSheetParser::ParseProperties(PropertyDictionary& parsed_properties, const String& properties)
 bool StyleSheetParser::ParseProperties(PropertyDictionary& parsed_properties, const String& properties)
 {
 {
 	stream = new StreamMemory((const byte*)properties.c_str(), properties.size());
 	stream = new StreamMemory((const byte*)properties.c_str(), properties.size());
-	bool success = ReadProperties(parsed_properties, StyleSheetSpecification::GetPropertySpecification());
+	PropertySpecificationParser parser(parsed_properties, StyleSheetSpecification::GetPropertySpecification());
+	bool success = ReadProperties(parser);
 	stream->RemoveReference();
 	stream->RemoveReference();
 	stream = NULL;
 	stream = NULL;
 	return success;
 	return success;
 }
 }
 
 
-bool StyleSheetParser::ReadProperties(PropertyDictionary& properties, const PropertySpecification& property_specification)
+
+bool StyleSheetParser::ReadProperties(AbstractPropertyParser& property_parser)
 {
 {
 	int rule_line_number = (int)line_number;
 	int rule_line_number = (int)line_number;
 	String name;
 	String name;
@@ -407,7 +526,7 @@ bool StyleSheetParser::ReadProperties(PropertyDictionary& properties, const Prop
 				{
 				{
 					value = StringUtilities::StripWhitespace(value);
 					value = StringUtilities::StripWhitespace(value);
 
 
-					if (!property_specification.ParsePropertyDeclaration(properties, name, value, stream_file_name, rule_line_number))
+					if (!property_parser.Parse(name, value, stream_file_name, rule_line_number))
 						Log::Message(Log::LT_WARNING, "Syntax error parsing property declaration '%s: %s;' in %s: %d.", name.c_str(), value.c_str(), stream_file_name.c_str(), line_number);
 						Log::Message(Log::LT_WARNING, "Syntax error parsing property declaration '%s: %s;' in %s: %d.", name.c_str(), value.c_str(), stream_file_name.c_str(), line_number);
 
 
 					name.clear();
 					name.clear();

+ 3 - 3
Source/Core/StyleSheetParser.h

@@ -32,9 +32,9 @@ namespace Rocket {
 namespace Core {
 namespace Core {
 
 
 class PropertyDictionary;
 class PropertyDictionary;
-class PropertySpecification;
 class Stream;
 class Stream;
 class StyleSheetNode;
 class StyleSheetNode;
+class AbstractPropertyParser;
 
 
 /**
 /**
 	Helper class for parsing a style sheet into its memory representation.
 	Helper class for parsing a style sheet into its memory representation.
@@ -52,7 +52,7 @@ public:
 	/// @param node The root node the stream will be parsed into
 	/// @param node The root node the stream will be parsed into
 	/// @param stream The stream to read
 	/// @param stream The stream to read
 	/// @return The number of parsed rules, or -1 if an error occured.
 	/// @return The number of parsed rules, or -1 if an error occured.
-	int Parse(StyleSheetNode* node, KeyframesMap& keyframes, DecoratorSpecificationMap& decorator_map, Stream* stream);
+	int Parse(StyleSheetNode* node, KeyframesMap& keyframes, DecoratorSpecificationMap& decorator_map, SpriteSheetList& sprite_sheets, Stream* stream);
 
 
 	/// Parses the given string into the property dictionary
 	/// Parses the given string into the property dictionary
 	/// @param parsed_properties The properties dictionary the properties will be read into
 	/// @param parsed_properties The properties dictionary the properties will be read into
@@ -76,7 +76,7 @@ private:
 	// Parses properties from the parse buffer into the dictionary
 	// Parses properties from the parse buffer into the dictionary
 	// @param properties The dictionary to store the properties in
 	// @param properties The dictionary to store the properties in
 	// @param property_specification The specification used to parse the values. Normally the default stylesheet specification, but not for e.g. all at-rules such as decorators.
 	// @param property_specification The specification used to parse the values. Normally the default stylesheet specification, but not for e.g. all at-rules such as decorators.
-	bool ReadProperties(PropertyDictionary& properties, const PropertySpecification& property_specification);
+	bool ReadProperties(AbstractPropertyParser& property_parser);
 
 
 	// Import properties into the stylesheet node
 	// Import properties into the stylesheet node
 	// @param node Node to import into
 	// @param node Node to import into

+ 2 - 2
Source/Core/StyleSheetSpecification.cpp

@@ -63,7 +63,7 @@ PropertyDefinition& StyleSheetSpecification::RegisterProperty(PropertyId id, con
 	return properties.RegisterProperty(property_name, default_value, inherited, forces_layout, id);
 	return properties.RegisterProperty(property_name, default_value, inherited, forces_layout, id);
 }
 }
 
 
-bool StyleSheetSpecification::RegisterShorthand(ShorthandId id, const String& shorthand_name, const String& property_names, ShorthandType type)
+ShorthandId StyleSheetSpecification::RegisterShorthand(ShorthandId id, const String& shorthand_name, const String& property_names, ShorthandType type)
 {
 {
 	return properties.RegisterShorthand(shorthand_name, property_names, type, id);
 	return properties.RegisterShorthand(shorthand_name, property_names, type, id);
 }
 }
@@ -143,7 +143,7 @@ const PropertyNameList & StyleSheetSpecification::GetRegisteredInheritedProperti
 }
 }
 
 
 // Registers a shorthand property definition.
 // Registers a shorthand property definition.
-bool StyleSheetSpecification::RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type)
+ShorthandId StyleSheetSpecification::RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type)
 {
 {
 	ROCKET_ASSERTMSG(instance->properties.property_map.GetId(shorthand_name) == PropertyId::Invalid, "Custom shorthand name matches a property name, please make a unique name.");
 	ROCKET_ASSERTMSG(instance->properties.property_map.GetId(shorthand_name) == PropertyId::Invalid, "Custom shorthand name matches a property name, please make a unique name.");
 	ROCKET_ASSERTMSG((size_t)instance->properties.shorthand_map.GetId(shorthand_name) < (size_t)ShorthandId::FirstCustomId, "Custom shorthand name matches an internal shorthand, please make a unique name for the given shorthand property.");
 	ROCKET_ASSERTMSG((size_t)instance->properties.shorthand_map.GetId(shorthand_name) < (size_t)ShorthandId::FirstCustomId, "Custom shorthand name matches an internal shorthand, please make a unique name for the given shorthand property.");