Browse Source

Changed the tiled-decorators to take 'x y width height' properties instead of two coordinates, optimize decorator instantiation, especially when using sprites.
See DecoratorTiledInstancer for the new properties.

Michael Ragazzon 6 years ago
parent
commit
08bdba69b3

+ 3 - 1
Include/Rocket/Core/Decorator.h

@@ -88,7 +88,9 @@ protected:
 
 private:
 	// Stores a list of textures in use by this decorator.
-	std::vector< Texture > textures;
+	// Optimized for the common case of a single texture.
+	Texture first_texture;
+	std::vector< Texture > additional_textures;
 };
 
 }

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

@@ -50,6 +50,7 @@ ROCKETCORE_API String CreateString(size_t max_size, const char* format, ...);
 
 ROCKETCORE_API String ToLower(const String& string);
 ROCKETCORE_API String Replace(String subject, const String& search, const String& replace);
+ROCKETCORE_API String Replace(String subject, char search, char replace);
 
 ROCKETCORE_API WString ToWideString(const String& str);
 ROCKETCORE_API String ToUTF8(const WString& wstr);

+ 16 - 0
Include/Rocket/Core/Vector2.h

@@ -89,10 +89,18 @@ class Vector2
 		/// @param[in] rhs The scalar value to multiply by.
 		/// @return The result of the scale.
 		inline Vector2 operator*(Type rhs) const;
+		/// Returns the result of element-wise multiplication.
+		/// @param[in] rhs The vector to multiply by.
+		/// @return The result of the multiplication.
+		inline Vector2 operator*(const Vector2& rhs) const;
 		/// Returns the result of dividing this vector by a scalar.
 		/// @param[in] rhs The scalar value to divide by.
 		/// @return The result of the scale.
 		inline Vector2 operator/(Type rhs) const;
+		/// Returns the result of element-wise division.
+		/// @param[in] rhs The vector to divide by.
+		/// @return The result of the division.
+		inline Vector2 operator/(const Vector2& rhs) const;
 
 		/// Adds another vector to this in-place.
 		/// @param[in] rhs The vector to add.
@@ -106,10 +114,18 @@ class Vector2
 		/// @param[in] rhs The value to scale this vector's components by.
 		/// @return This vector, post-operation.
 		inline Vector2& operator*=(const Type& rhs);
+		/// Element-wise multiplication in-place.
+		/// @param[in] rhs The vector to multiply.
+		/// @return This vector, post-operation.
+		inline Vector2& operator*=(const Vector2& rhs);
 		/// Scales this vector in-place by the inverse of a value.
 		/// @param[in] rhs The value to divide this vector's components by.
 		/// @return This vector, post-operation.
 		inline Vector2& operator/=(const Type& rhs);
+		/// Element-wise division in-place.
+		/// @param[in] rhs The vector to divide by.
+		/// @return This vector, post-operation.
+		inline Vector2& operator/=(const Vector2& rhs);
 
 		/// Equality operator.
 		/// @param[in] rhs The vector to compare this against.

+ 29 - 0
Include/Rocket/Core/Vector2.inl

@@ -136,6 +136,12 @@ Vector2< Type > Vector2< Type >::operator*(Type rhs) const
 	return Vector2(x * rhs, y * rhs);
 }
 
+template<typename Type>
+Vector2< Type > Vector2<Type>::operator*(const Vector2& rhs) const
+{
+	return Vector2(x * rhs.x, y * rhs.y);
+}
+
 // Returns the result of dividing this vector by a scalar.
 template < typename Type >
 Vector2< Type > Vector2< Type >::operator/(Type rhs) const
@@ -143,6 +149,12 @@ Vector2< Type > Vector2< Type >::operator/(Type rhs) const
 	return Vector2(x / rhs, y / rhs);
 }
 
+template<typename Type>
+Vector2< Type > Vector2<Type>::operator/(const Vector2& rhs) const
+{
+	return Vector2(x / rhs.x, y / rhs.y);
+}
+
 // Adds another vector to this in-place.
 template < typename Type >
 Vector2< Type >& Vector2< Type >::operator+=(const Vector2 & rhs)
@@ -173,6 +185,15 @@ Vector2< Type >& Vector2< Type >::operator*=(const Type & rhs)
 	return *this;
 }
 
+template<typename Type>
+Vector2< Type >& Vector2<Type>::operator*=(const Vector2& rhs)
+{
+	x *= rhs.x;
+	y *= rhs.y;
+
+	return *this;
+}
+
 // Scales this vector in-place by the inverse of a value.
 template < typename Type >
 Vector2< Type >& Vector2< Type >::operator/=(const Type & rhs)
@@ -183,6 +204,14 @@ Vector2< Type >& Vector2< Type >::operator/=(const Type & rhs)
 	return *this;
 }
 
+template<typename Type>
+Vector2< Type >& Vector2<Type>::operator/=(const Vector2& rhs)
+{
+	x /= rhs.x;
+	y /= rhs.y;
+	return *this;
+}
+
 // Equality operator.
 template < typename Type >
 bool Vector2< Type >::operator==(const Vector2 & rhs) const

+ 25 - 12
Source/Core/Decorator.cpp

@@ -45,18 +45,21 @@ Decorator::~Decorator()
 // Attempts to load a texture into the list of textures in use by the decorator.
 int Decorator::LoadTexture(const String& texture_name, const String& rcss_path)
 {
-	for (size_t i = 0; i < textures.size(); i++)
+	if (texture_name == first_texture.GetSource())
+		return 0;
+
+	for (size_t i = 0; i < additional_textures.size(); i++)
 	{
-		if (texture_name == textures[i].GetSource())
-			return (int) i;
+		if (texture_name == additional_textures[i].GetSource())
+			return (int)i + 1;
 	}
 
 	Texture texture;
 	if (!texture.Load(texture_name, rcss_path))
 		return -1;
 
-	textures.push_back(texture);
-	return (int)textures.size() - 1;
+	additional_textures.push_back(texture);
+	return (int)additional_textures.size();
 }
 
 int Decorator::AddTexture(const Texture& texture)
@@ -64,21 +67,31 @@ int Decorator::AddTexture(const Texture& texture)
 	if (!texture)
 		return -1;
 
-	auto it = std::find(textures.begin(), textures.end(), texture);
-	if (it != textures.end())
-		return (int)(it - textures.begin());
+	if (!first_texture)
+		first_texture = texture;
+
+	if (first_texture == texture)
+		return 0;
+
+	auto it = std::find(additional_textures.begin(), additional_textures.end(), texture);
+	if (it != additional_textures.end())
+		return (int)(it - additional_textures.begin()) + 1;
 
-	textures.push_back(texture);
-	return (int)textures.size() - 1;
+	additional_textures.push_back(texture);
+	return (int)additional_textures.size();
 }
 
 // Returns one of the decorator's previously loaded textures.
 const Texture* Decorator::GetTexture(int index) const
 {
-	if (index < 0 || index >= (int) textures.size())
+	if (index == 0)
+		return &first_texture;
+	
+	index -= 1;
+	if (index < 0 || index >= (int)additional_textures.size())
 		return nullptr;
 
-	return &(textures[index]);
+	return &(additional_textures[index]);
 }
 
 

+ 37 - 37
Source/Core/DecoratorTiled.cpp

@@ -48,58 +48,58 @@ static Vector2f oriented_texcoords[6][2] = {{Vector2f(0, 0), Vector2f(1, 1)},
 													   {Vector2f(1, 0), Vector2f(0, 1)},
 													   {Vector2f(0, 1), Vector2f(1, 0)}};
 
-DecoratorTiled::Tile::Tile()
+DecoratorTiled::Tile::Tile() : position(0, 0), size(1, 1)
 {
 	texture_index = -1;
 	repeat_mode = STRETCH;
 	orientation = ROTATE_0_CW;
 
-	texcoords[0].x = 0;
-	texcoords[0].y = 0;
-	texcoords[1].x = 1;
-	texcoords[1].y = 1;
-
-	texcoords_absolute[0][0] = false;
-	texcoords_absolute[0][1] = false;
-	texcoords_absolute[1][0] = false;
-	texcoords_absolute[1][1] = false;
+	position_absolute[0] = false;
+	position_absolute[1] = false;
+	size_absolute[0] = false;
+	size_absolute[1] = false;
 }
 
-struct TileDataMapCmp {
-	RenderInterface* find;
-
-	using Element = std::pair< RenderInterface*, DecoratorTiled::Tile::TileData >;
-	bool operator() (const Element& el) const {
-		return el.first == find;
-	}
-};
 
 // Calculates the tile's dimensions from the texture and texture coordinates.
 void DecoratorTiled::Tile::CalculateDimensions(Element* element, const Texture& texture)
 {
 	RenderInterface* render_interface = element->GetRenderInterface();
-	TileDataMap::iterator data_iterator = std::find_if(data.begin(), data.end(), TileDataMapCmp{ render_interface });
+	auto data_iterator = data.find(render_interface);
 	if (data_iterator == data.end())
 	{
 		TileData new_data;
-		Vector2i texture_dimensions = texture.GetDimensions(render_interface);
+		const Vector2i texture_dimensions_i = texture.GetDimensions(render_interface);
+		const Vector2f texture_dimensions((float)texture_dimensions_i.x, (float)texture_dimensions_i.y);
 
-		for (int i = 0; i < 2; i++)
+		if (texture_dimensions.x == 0 || texture_dimensions.y == 0)
 		{
-			new_data.texcoords[i] = texcoords[i];
-
-			if (texcoords_absolute[i][0] &&
-				texture_dimensions.x > 0)
-				new_data.texcoords[i].x /= texture_dimensions.x;
-			if (texcoords_absolute[i][1] &&
-				texture_dimensions.y > 0)
-				new_data.texcoords[i].y /= texture_dimensions.y;
+			new_data.size = Vector2f(0, 0);
+			new_data.texcoords[0] = Vector2f(0, 0);
+			new_data.texcoords[1] = Vector2f(0, 0);
 		}
+		else
+		{
+			// Need to scale the coordinates to normalized units and 'size' to absolute size (pixels)
+			for(int i = 0; i < 2; i++)
+			{
+				float size_relative = size[i];
+				new_data.size[i] = Math::AbsoluteValue(size[i]);
 
-		new_data.dimensions.x = Math::AbsoluteValue((new_data.texcoords[1].x * texture_dimensions.x) - (new_data.texcoords[0].x * texture_dimensions.x));
-		new_data.dimensions.y = Math::AbsoluteValue((new_data.texcoords[1].y * texture_dimensions.y) - (new_data.texcoords[0].y * texture_dimensions.y));
+				if (size_absolute[i])
+					size_relative /= texture_dimensions[i];
+				else
+					new_data.size[i] *= texture_dimensions[i];
+				
+				new_data.texcoords[0][i] = position[i];
+				if (position_absolute[i])
+					new_data.texcoords[0][i] /= texture_dimensions[i];
 
-		data.emplace_back( render_interface,  new_data );
+				new_data.texcoords[1][i] = size_relative + new_data.texcoords[0][i];
+			}
+		}
+
+		data.emplace( render_interface, new_data );
 	}
 }
 
@@ -107,11 +107,11 @@ void DecoratorTiled::Tile::CalculateDimensions(Element* element, const Texture&
 Vector2f DecoratorTiled::Tile::GetDimensions(Element* element)
 {
 	RenderInterface* render_interface = element->GetRenderInterface();
-	TileDataMap::iterator data_iterator = std::find_if(data.begin(), data.end(), TileDataMapCmp{ render_interface });
+	auto data_iterator = data.find(render_interface);
 	if (data_iterator == data.end())
 		return Vector2f(0, 0);
 
-	return data_iterator->second.dimensions;
+	return data_iterator->second.size;
 }
 
 // Generates geometry to render this tile across a surface.
@@ -125,8 +125,8 @@ void DecoratorTiled::Tile::GenerateGeometry(std::vector< Vertex >& vertices, std
 
     // Apply opacity
     quad_colour.alpha = (byte)(opacity * (float)quad_colour.alpha);
-	
-	TileDataMap::iterator data_iterator = std::find_if(data.begin(), data.end(), TileDataMapCmp{ render_interface });
+
+	auto data_iterator = data.find(render_interface);
 	if (data_iterator == data.end())
 		return;
 
@@ -216,7 +216,7 @@ void DecoratorTiled::Tile::GenerateGeometry(std::vector< Vertex >& vertices, std
 		tile_position.y = surface_origin.y + (float) tile_dimensions.y * y;
 
 		Vector2f tile_size;
-		tile_size.y = (float) (y < num_tiles[1] - 1 ? data.dimensions.y : final_tile_dimensions.y);
+		tile_size.y = (float) (y < num_tiles[1] - 1 ? data.size.y : final_tile_dimensions.y);
 
 		// Squish the texture coordinates in the y if we're clamping and this is the last in a double-tile.
 		Vector2f tile_texcoords[2];

+ 9 - 5
Source/Core/DecoratorTiled.h

@@ -101,15 +101,19 @@ public:
 
 		struct TileData
 		{
-			Vector2f dimensions;
-			Vector2f texcoords[2];
+			Vector2f size; // 'px' units
+			Vector2f texcoords[2]; // relative units
 		};
 
-		typedef std::vector< std::pair< RenderInterface*, TileData > > TileDataMap;
+		using TileDataMap = SmallUnorderedMap< RenderInterface*, TileData >;
 
 		int texture_index;
-		Vector2f texcoords[2];
-		bool texcoords_absolute[2][2];
+
+		// Position and size within the texture, absolute or relative units
+		Vector2f position, size;
+
+		// Absolute is 'px' units, otherwise relative to the dimensions of the texture
+		bool position_absolute[2], size_absolute[2];
 
 		mutable TileDataMap data;
 

+ 3 - 8
Source/Core/DecoratorTiledBox.cpp

@@ -60,18 +60,13 @@ DecoratorTiledBox::~DecoratorTiledBox()
 }
 
 // Initialises the tiles for the decorator.
-bool DecoratorTiledBox::Initialise(const Tile* _tiles, const String* _texture_names, const String* _rcss_paths)
+bool DecoratorTiledBox::Initialise(const Tile* _tiles, const Texture* _textures)
 {
 	// Load the textures.
 	for (int i = 0; i < 9; i++)
 	{
-		if (!_texture_names[i].empty())
-		{
-			tiles[i] = _tiles[i];
-			tiles[i].texture_index = LoadTexture(_texture_names[i], _rcss_paths[i]);
-			if (tiles[i].texture_index < 0)
-				return false;
-		}
+		tiles[i] = _tiles[i];
+		tiles[i].texture_index = AddTexture(_textures[i]);
 	}
 
 	// If only one side of the left / right edges have been configured, then mirror the tile for the other side.

+ 3 - 4
Source/Core/DecoratorTiledBox.h

@@ -45,10 +45,9 @@ public:
 
 	/// Initialises the tiles for the decorator.
 	/// @param[in] tiles The declaration for all eight tiles.
-	/// @param[in] texture_names The application-specific path to the texture for the eight tiles.
-	/// @param[in] rcss_paths The paths to the RCSS files that defined the texture sources.
-	/// @return True if all the images loaded (or are pending loading) and are of compatible sizes, false otherwise.
-	bool Initialise(const Tile* tiles, const String* texture_names, const String* rcss_paths);
+	/// @param[in] textures The textures for the eight tiles.
+	/// @return True if all the tiles and textures are properly specified.
+	bool Initialise(const Tile* tiles, const Texture* textures);
 
 	/// Called on a decorator to generate any required per-element data for a newly decorated element.
 	virtual DecoratorDataHandle GenerateElementData(Element* element);

+ 7 - 8
Source/Core/DecoratorTiledBoxInstancer.cpp

@@ -32,7 +32,7 @@
 namespace Rocket {
 namespace Core {
 
-DecoratorTiledBoxInstancer::DecoratorTiledBoxInstancer()
+DecoratorTiledBoxInstancer::DecoratorTiledBoxInstancer() : DecoratorTiledInstancer(9)
 {
 	RegisterTileProperty("top-left-image", false);
 	RegisterTileProperty("top-right-image", false);
@@ -61,17 +61,16 @@ std::shared_ptr<Decorator>DecoratorTiledBoxInstancer::InstanceDecorator(const St
 	constexpr size_t num_tiles = 9;
 
 	DecoratorTiled::Tile tiles[num_tiles];
-	String texture_names[num_tiles];
-	String rcss_paths[num_tiles];
+	Texture textures[num_tiles];
 
-	for(size_t i = 0; i < num_tiles; i++)
-		GetTileProperties(i, tiles[i], texture_names[i], rcss_paths[i], properties, interface);
+	if (!GetTileProperties(tiles, textures, num_tiles, properties, interface))
+		return nullptr;
 
 	auto decorator = std::make_shared<DecoratorTiledBox>();
-	if (decorator->Initialise(tiles, texture_names, rcss_paths))
-		return decorator;
+	if (!decorator->Initialise(tiles, textures))
+		return nullptr;
 
-	return nullptr;
+	return decorator;
 }
 
 }

+ 3 - 10
Source/Core/DecoratorTiledHorizontal.cpp

@@ -60,20 +60,13 @@ DecoratorTiledHorizontal::~DecoratorTiledHorizontal()
 }
 
 // Initialises the tiles for the decorator.
-bool DecoratorTiledHorizontal::Initialise(const Tile* _tiles, const String* _texture_names, const String* _rcss_paths)
+bool DecoratorTiledHorizontal::Initialise(const Tile* _tiles, const Texture* _textures)
 {
 	// Load the textures.
 	for (int i = 0; i < 3; i++)
 	{
-		if (!_texture_names[i].empty())
-		{
-			tiles[i] = _tiles[i];
-			tiles[i].texture_index = LoadTexture(_texture_names[i], _rcss_paths[i]);
-			if (tiles[i].texture_index < 0)
-				return false;
-		}
-		else
-			tiles[i].texture_index = -1;
+		tiles[i] = _tiles[i];
+		tiles[i].texture_index = AddTexture(_textures[i]);
 	}
 
 	// If only one side of the decorator has been configured, then mirror the texture for the other side.

+ 4 - 5
Source/Core/DecoratorTiledHorizontal.h

@@ -44,11 +44,10 @@ public:
 	virtual ~DecoratorTiledHorizontal();
 
 	/// Initialises the tiles for the decorator.
-	/// @param tiles[in] The declaration for all three tiles.
-	/// @param texture_names[in] The application-specific path to the texture for the three tiles.
-	/// @param rcss_paths[in] The paths to the RCSS files that defined the texture sources.
-	/// @return True if all the images loaded (or are pending loading) and are of compatible sizes, false otherwise.
-	bool Initialise(const Tile* tiles, const String* texture_names, const String* rcss_paths);
+	/// @param[in] tiles The declaration for all three tiles.
+	/// @param[in] textures The textures for the three tiles.
+	/// @return True if all the tiles and textures are properly specified.
+	bool Initialise(const Tile* tiles, const Texture* textures);
 
 	/// Called on a decorator to generate any required per-element data for a newly decorated element.
 	virtual DecoratorDataHandle GenerateElementData(Element* element);

+ 7 - 8
Source/Core/DecoratorTiledHorizontalInstancer.cpp

@@ -32,7 +32,7 @@
 namespace Rocket {
 namespace Core {
 
-DecoratorTiledHorizontalInstancer::DecoratorTiledHorizontalInstancer()
+DecoratorTiledHorizontalInstancer::DecoratorTiledHorizontalInstancer() : DecoratorTiledInstancer(3)
 {
 	RegisterTileProperty("left-image", false);
 	RegisterTileProperty("right-image", false);
@@ -51,17 +51,16 @@ std::shared_ptr<Decorator> DecoratorTiledHorizontalInstancer::InstanceDecorator(
 	constexpr size_t num_tiles = 3;
 
 	DecoratorTiled::Tile tiles[num_tiles];
-	String texture_names[num_tiles];
-	String rcss_paths[num_tiles];
+	Texture textures[num_tiles];
 
-	for (size_t i = 0; i < num_tiles; i++)
-		GetTileProperties(i, tiles[i], texture_names[i], rcss_paths[i], properties, interface);
+	if (!GetTileProperties(tiles, textures, num_tiles, properties, interface))
+		return nullptr;
 
 	auto decorator = std::make_shared<DecoratorTiledHorizontal>();
-	if (decorator->Initialise(tiles, texture_names, rcss_paths))
-		return decorator;
+	if (!decorator->Initialise(tiles, textures))
+		return nullptr;
 
-	return nullptr;
+	return decorator;
 }
 
 

+ 0 - 8
Source/Core/DecoratorTiledImage.cpp

@@ -42,14 +42,6 @@ DecoratorTiledImage::~DecoratorTiledImage()
 {
 }
 
-// Initialises the tiles for the decorator.
-bool DecoratorTiledImage::Initialise(const Tile& _tile, const String& _texture_name, const String& _rcss_path)
-{
-	tile = _tile;
-	tile.texture_index = LoadTexture(_texture_name, _rcss_path);
-	return (tile.texture_index >= 0);
-}
-
 bool DecoratorTiledImage::Initialise(const Tile& _tile, const Texture& _texture)
 {
 	tile = _tile;

+ 2 - 4
Source/Core/DecoratorTiledImage.h

@@ -45,10 +45,8 @@ public:
 
 	/// Initialises the tile for the decorator.
 	/// @param tile[in] The declaration for the tile.
-	/// @param texture_name[in] The application-specific path to the texture for the tile.
-	/// @param rcss_path[in] The path to the RCSS file that defined the texture source.
-	/// @return True if the image loaded (or are pending loading) and are of compatible sizes, false otherwise.
-	bool Initialise(const Tile& tile, const String& texture_names, const String& rcss_path);
+	/// @param texture[in] The texture to apply to the tile.
+	/// @return True if the image is valid, false otherwise.
 	bool Initialise(const Tile& tile, const Texture& texture);
 
 	/// Called on a decorator to generate any required per-element data for a newly decorated element.

+ 7 - 7
Source/Core/DecoratorTiledImageInstancer.cpp

@@ -32,7 +32,7 @@
 namespace Rocket {
 namespace Core {
 
-DecoratorTiledImageInstancer::DecoratorTiledImageInstancer()
+DecoratorTiledImageInstancer::DecoratorTiledImageInstancer() : DecoratorTiledInstancer(1)
 {
 	RegisterTileProperty("image", false);
 	RegisterShorthand("decorator", "image", ShorthandType::Recursive);
@@ -48,17 +48,17 @@ std::shared_ptr<Decorator> DecoratorTiledImageInstancer::InstanceDecorator(const
 	ROCKET_UNUSED(name);
 
 	DecoratorTiled::Tile tile;
-	String texture_name;
-	String rcss_path;
+	Texture texture;
 
-	GetTileProperties(0, tile, texture_name, rcss_path, properties, interface);
+	if (!GetTileProperties(&tile, &texture, 1, properties, interface))
+		return nullptr;
 	
 	auto decorator = std::make_shared<DecoratorTiledImage>();
 
-	if (decorator->Initialise(tile, texture_name, rcss_path))
-		return decorator;
+	if (!decorator->Initialise(tile, texture))
+		return nullptr;
 
-	return nullptr;
+	return decorator;
 }
 
 }

+ 118 - 70
Source/Core/DecoratorTiledInstancer.cpp

@@ -32,106 +32,154 @@
 namespace Rocket {
 namespace Core {
 
-DecoratorTiledInstancer::~DecoratorTiledInstancer()
+DecoratorTiledInstancer::DecoratorTiledInstancer(size_t num_tiles)
 {
+	tile_property_ids.reserve(num_tiles);
 }
 
 // Adds the property declarations for a tile.
 void DecoratorTiledInstancer::RegisterTileProperty(const String& name, bool register_repeat_modes)
 {
-	TilePropertyIds tile = {};
+	TilePropertyIds ids = {};
 
-	tile.src = RegisterProperty(CreateString(32, "%s-src", name.c_str()), "").AddParser("string").GetId();
-	tile.s_begin = RegisterProperty(CreateString(32, "%s-s-begin", name.c_str()), "0").AddParser("length").GetId();
-	tile.s_end = RegisterProperty(CreateString(32, "%s-s-end", name.c_str()), "1").AddParser("length").GetId();
-	tile.t_begin = RegisterProperty(CreateString(32, "%s-t-begin", name.c_str()), "0").AddParser("length").GetId();
-	tile.t_end = RegisterProperty(CreateString(32, "%s-t-end", name.c_str()), "1").AddParser("length").GetId();
-	RegisterShorthand(CreateString(32, "%s-s", name.c_str()), CreateString(64, "%s-s-begin, %s-s-end", name.c_str(), name.c_str()), ShorthandType::FallThrough);
-	RegisterShorthand(CreateString(32, "%s-t", name.c_str()), CreateString(64, "%s-t-begin, %s-t-end", name.c_str(), name.c_str()), ShorthandType::FallThrough);
+	ids.src = RegisterProperty(CreateString(32, "%s-src", name.c_str()), "").AddParser("string").GetId();
+	ids.x = RegisterProperty(CreateString(32, "%s-x", name.c_str()), "0px").AddParser("number_length_percent").GetId();
+	ids.y = RegisterProperty(CreateString(32, "%s-y", name.c_str()), "0px").AddParser("number_length_percent").GetId();
+	ids.width = RegisterProperty(CreateString(32, "%s-width", name.c_str()), "0px").AddParser("number_length_percent").GetId();
+	ids.height = RegisterProperty(CreateString(32, "%s-height", name.c_str()), "0px").AddParser("number_length_percent").GetId();
+	RegisterShorthand(CreateString(32, "%s-pos", name.c_str()), CreateString(64, "%s-x, %s-y", name.c_str(), name.c_str()), ShorthandType::FallThrough);
+	RegisterShorthand(CreateString(32, "%s-size", name.c_str()), CreateString(64, "%s-width, %s-height", name.c_str(), name.c_str()), ShorthandType::FallThrough);
 
 	if (register_repeat_modes)
 	{
-		tile.repeat = RegisterProperty(CreateString(32, "%s-repeat", name.c_str()), "stretch")
+		ids.repeat = RegisterProperty(CreateString(32, "%s-repeat", name.c_str()), "stretch")
 			.AddParser("keyword", "stretch, clamp-stretch, clamp-truncate, repeat-stretch, repeat-truncate")
 			.GetId();
-		RegisterShorthand(name, CreateString(256, "%s-src, %s-repeat, %s-s-begin, %s-t-begin, %s-s-end, %s-t-end", name.c_str(), name.c_str(), name.c_str(), name.c_str(), name.c_str(), name.c_str()), ShorthandType::FallThrough);
+		RegisterShorthand(name, CreateString(256, "%s-src, %s-repeat, %s-x, %s-y, %s-width, %s-height", name.c_str(), name.c_str(), name.c_str(), name.c_str(), name.c_str(), name.c_str()), ShorthandType::FallThrough);
 	}
 	else
-		RegisterShorthand(name, CreateString(256, "%s-src, %s-s-begin, %s-t-begin, %s-s-end, %s-t-end", name.c_str(), name.c_str(), name.c_str(), name.c_str(), name.c_str()), ShorthandType::FallThrough);
+	{
+		ids.repeat = PropertyId::Invalid;
+		RegisterShorthand(name, CreateString(256, "%s-src, %s-x, %s-y, %s-width, %s-height", name.c_str(), name.c_str(), name.c_str(), name.c_str(), name.c_str()), ShorthandType::FallThrough);
+	}
 
-	// @performance: Reserve number of tiles on construction
-	tile_property_ids.push_back(tile);
+	tile_property_ids.push_back(ids);
 }
 
-// Retrieves all the properties for a tile from the property dictionary.
-void DecoratorTiledInstancer::GetTileProperties(size_t tile_index, DecoratorTiled::Tile& tile, String& texture_name, String& rcss_path, const PropertyDictionary& properties, const DecoratorInstancerInterface& interface)
+
+// Loads a single texture coordinate value from the properties.
+static void LoadTexCoord(const Property& property, float& tex_coord, bool& tex_coord_absolute)
 {
-	ROCKET_ASSERT(tile_index < tile_property_ids.size());
-
-	const TilePropertyIds& ids = tile_property_ids[tile_index];
-
-	const Property* texture_property = properties.GetProperty(ids.src);
-	texture_name = texture_property->Get< String >();
-	rcss_path = texture_property->source;
-	
-	// Declaring the name 'auto' 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 = "auto";
-	if (texture_name == none_texture_name)
-		texture_name.clear();
-
-	// @performance / @todo: We want some way to determine sprite or image instead of always doing the lookup as a sprite name.
-	// @performance: We already have the texture loaded in the spritesheet, very unnecessary to return as name and then convert to texture again.
-	if (const Sprite * sprite = interface.GetSprite(texture_name))
+	tex_coord = property.value.Get< float >();
+	if (property.unit == Property::PX)
 	{
-		texture_name = sprite->sprite_sheet->image_source;
-		rcss_path = sprite->sprite_sheet->definition_source;
-
-		tile.texcoords[0].x = sprite->rectangle.x;
-		tile.texcoords[0].y = sprite->rectangle.y;
-		tile.texcoords[1].x = sprite->rectangle.x + sprite->rectangle.width;
-		tile.texcoords[1].y = sprite->rectangle.y + sprite->rectangle.height;
-
-		tile.texcoords_absolute[0][0] = true;
-		tile.texcoords_absolute[0][1] = true;
-		tile.texcoords_absolute[1][0] = true;
-		tile.texcoords_absolute[1][1] = true;
+		tex_coord_absolute = true;
 	}
 	else
 	{
-		LoadTexCoord(properties, ids.s_begin, tile.texcoords[0].x, tile.texcoords_absolute[0][0]);
-		LoadTexCoord(properties, ids.t_begin, tile.texcoords[0].y, tile.texcoords_absolute[0][1]);
-		LoadTexCoord(properties, ids.s_end, tile.texcoords[1].x, tile.texcoords_absolute[1][0]);
-		LoadTexCoord(properties, ids.t_end, tile.texcoords[1].y, tile.texcoords_absolute[1][1]);
-	}
-
-	if(ids.repeat != PropertyId::Invalid)
-	{
-		const Property* repeat_property = properties.GetProperty(ids.repeat);
-		ROCKET_ASSERT(repeat_property);
-		tile.repeat_mode = (DecoratorTiled::TileRepeatMode) repeat_property->value.Get< int >();
+		tex_coord_absolute = false;
+		if (property.unit == Property::PERCENT)
+			tex_coord *= 0.01f;
 	}
 }
 
-// Loads a single texture coordinate value from the properties.
-void DecoratorTiledInstancer::LoadTexCoord(const PropertyDictionary& properties, PropertyId id, float& tex_coord, bool& tex_coord_absolute)
+
+// Retrieves all the properties for a tile from the property dictionary.
+bool DecoratorTiledInstancer::GetTileProperties(DecoratorTiled::Tile* tiles, Texture* textures, size_t num_tiles_and_textures, const PropertyDictionary& properties, const DecoratorInstancerInterface& interface) const
 {
-	const Property* property = properties.GetProperty(id);
+	ROCKET_ASSERT(num_tiles_and_textures == tile_property_ids.size());
 
-	// May fail if we forgot to set default values before instancing the tile
-	ROCKET_ASSERT(property);
+	String previous_texture_name;
+	Texture previous_texture;
 
-	tex_coord = property->value.Get< float >();
-	if (property->unit == Property::PX)
-	{
-		tex_coord_absolute = true;
-	}
-	else
+	for(size_t i = 0; i < num_tiles_and_textures; i++)
 	{
-		tex_coord_absolute = false;
-		if (property->unit == Property::PERCENT)
-			tex_coord *= 0.01f;
+		const TilePropertyIds& ids = tile_property_ids[i];
+
+		const Property* src_property = properties.GetProperty(ids.src);
+		const String texture_name = src_property->Get< String >();
+
+		if (texture_name == "window-tl")
+		{
+			int i = 0;
+		}
+
+		// Skip the tile if it has no source name.
+		// Declaring the name 'auto' 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 auto_str = "auto";
+		if (texture_name.empty() || texture_name == auto_str)
+			continue;
+
+		// A tile is always either a sprite or a texture with position and dimensions specified.
+		bool src_is_sprite = false;
+
+		// We are required to set default values before instancing the tile, thus, all properties should always be dereferencable.
+		// If the debugger captures a zero-dereference, check that all properties for every tile is set and default values are set just before instancing.
+		const Property& width_property = *properties.GetProperty(ids.width);
+		float width = width_property.Get<float>();
+		if (width == 0.0f)
+		{
+			// A sprite always has its own dimensions, thus, we let zero width/height define that it is a sprite.
+			src_is_sprite = true;
+		}
+
+		DecoratorTiled::Tile& tile = tiles[i];
+		Texture& texture = textures[i];
+
+		if (src_is_sprite)
+		{
+			if (const Sprite * sprite = interface.GetSprite(texture_name))
+			{
+				tile.position.x = sprite->rectangle.x;
+				tile.position.y = sprite->rectangle.y;
+				tile.size.x = sprite->rectangle.width;
+				tile.size.y = sprite->rectangle.height;
+
+				tile.position_absolute[0] = tile.position_absolute[1] = true;
+				tile.size_absolute[0] = tile.size_absolute[1] = true;
+
+				texture = sprite->sprite_sheet->texture;
+			}
+			else
+			{
+				Log::Message(Log::LT_WARNING, "The sprite '%s' given in decorator could not be found, declared at %s:%d", texture_name.c_str(), src_property->source.c_str(), src_property->source_line_number);
+				return false;
+			}
+		}
+		else
+		{
+			LoadTexCoord(*properties.GetProperty(ids.x), tile.position.x, tile.position_absolute[0]);
+			LoadTexCoord(*properties.GetProperty(ids.y), tile.position.y, tile.position_absolute[1]);
+			LoadTexCoord(width_property, tile.size.x, tile.size_absolute[0]);
+			LoadTexCoord(*properties.GetProperty(ids.height), tile.size.y, tile.size_absolute[1]);
+
+			// Since the common use case is to specify the same texture for all tiles, we
+			// check the previous texture first before fetching from the global database.
+			if (texture_name == previous_texture_name)
+			{
+				texture = previous_texture;
+			}
+			else if (texture.Load(texture_name, src_property->source))
+			{
+				previous_texture_name = texture_name;
+				previous_texture = texture;
+			}
+			else
+			{
+				Log::Message(Log::LT_WARNING, "Could not load texture '%s' given in decorator at %s:%d", texture_name.c_str(), src_property->source.c_str(), src_property->source_line_number);
+				return false;
+			}
+		}
+
+		if(ids.repeat != PropertyId::Invalid)
+		{
+			const Property& repeat_property = *properties.GetProperty(ids.repeat);
+			tile.repeat_mode = (DecoratorTiled::TileRepeatMode)repeat_property.value.Get< int >();
+		}
 	}
+
+	return true;
 }
 
 }

+ 7 - 9
Source/Core/DecoratorTiledInstancer.h

@@ -40,30 +40,28 @@ class StyleSheet;
 	@author Peter Curry
  */
 
+
 class DecoratorTiledInstancer : public DecoratorInstancer
 {
 public:
-	virtual ~DecoratorTiledInstancer();
+	DecoratorTiledInstancer(size_t num_tiles);
 
 protected:
 	/// Adds the property declarations for a tile.
 	/// @param[in] name The name of the tile property.
 	/// @param[in] register_repeat_modes If true, the tile will have the repeat modes registered.
 	void RegisterTileProperty(const String& name, bool register_repeat_modes);
+
 	/// Retrieves all the properties for a tile from the property dictionary.
 	/// @param[out] tile The tile structure for storing the tile properties.
-	/// @param[out] texture_name Holds the name of the texture declared for the tile (if one exists).
-	/// @param[out] rcss_path The path of the RCSS file that generated the texture path.
+	/// @param[out] textures Holds the textures declared for the tile.
 	/// @param[in] properties The user-defined list of parameters for the decorator.
-	/// @param[in] name The name of the tile to fetch the properties for.
-	void GetTileProperties(size_t tile_index, DecoratorTiled::Tile& tile, String& texture_name, String& rcss_path, const PropertyDictionary& properties, const DecoratorInstancerInterface& interface);
+	/// @param[in] interface An interface for querying the active style sheet.
+	bool GetTileProperties(DecoratorTiled::Tile* tiles, Texture* textures, size_t num_tiles_and_textures, const PropertyDictionary& properties, const DecoratorInstancerInterface& interface) const;
 
 private:
-	// Loads a single texture coordinate value from the properties.
-	void LoadTexCoord(const PropertyDictionary& properties, PropertyId id, float& tex_coord, bool& tex_coord_absolute);
-
 	struct TilePropertyIds {
-		PropertyId src, s_begin, s_end, t_begin, t_end, repeat;
+		PropertyId src, repeat, x, y, width, height;
 	};
 
 	std::vector<TilePropertyIds> tile_property_ids;

+ 3 - 10
Source/Core/DecoratorTiledVertical.cpp

@@ -61,20 +61,13 @@ DecoratorTiledVertical::~DecoratorTiledVertical()
 }
 
 // Initialises the tiles for the decorator.
-bool DecoratorTiledVertical::Initialise(const Tile* _tiles, const String* _texture_names, const String* _rcss_paths)
+bool DecoratorTiledVertical::Initialise(const Tile* _tiles, const Texture* _textures)
 {
 	// Load the textures.
 	for (int i = 0; i < 3; i++)
 	{
-		if (!_texture_names[i].empty())
-		{
-			tiles[i] = _tiles[i];
-			tiles[i].texture_index = LoadTexture(_texture_names[i], _rcss_paths[i]);
-			if (tiles[i].texture_index < 0)
-				return false;
-		}
-		else
-			tiles[i].texture_index = -1;
+		tiles[i] = _tiles[i];
+		tiles[i].texture_index = AddTexture(_textures[i]);
 	}
 
 	// If only one side of the decorator has been configured, then mirror the texture for the other side.

+ 4 - 5
Source/Core/DecoratorTiledVertical.h

@@ -44,11 +44,10 @@ public:
 	virtual ~DecoratorTiledVertical();
 
 	/// Initialises the tiles for the decorator.
-	/// @param tiles[in] The declaration for all three tiles.
-	/// @param texture_names[in] The application-specific path to the texture for the three tiles.
-	/// @param rcss_paths[in] The paths to the RCSS files that defined the texture sources.
-	/// @return True if all the images loaded (or are pending loading) and are of compatible sizes, false otherwise.
-	bool Initialise(const Tile* tiles, const String* texture_names, const String* rcss_paths);
+	/// @param[in] tiles The declaration for all three tiles.
+	/// @param[in] textures The textures for the three tiles.
+	/// @return True if all the tiles and textures are properly specified.
+	bool Initialise(const Tile* tiles, const Texture* textures);
 
 	/// Called on a decorator to generate any required per-element data for a newly decorated element.
 	virtual DecoratorDataHandle GenerateElementData(Element* element);

+ 7 - 8
Source/Core/DecoratorTiledVerticalInstancer.cpp

@@ -32,7 +32,7 @@
 namespace Rocket {
 namespace Core {
 
-DecoratorTiledVerticalInstancer::DecoratorTiledVerticalInstancer()
+DecoratorTiledVerticalInstancer::DecoratorTiledVerticalInstancer() : DecoratorTiledInstancer(3)
 {
 	RegisterTileProperty("top-image", false);
 	RegisterTileProperty("bottom-image", false);
@@ -51,17 +51,16 @@ std::shared_ptr<Decorator> DecoratorTiledVerticalInstancer::InstanceDecorator(co
 	constexpr size_t num_tiles = 3;
 
 	DecoratorTiled::Tile tiles[num_tiles];
-	String texture_names[num_tiles];
-	String rcss_paths[num_tiles];
+	Texture textures[num_tiles];
 
-	for (size_t i = 0; i < num_tiles; i++)
-		GetTileProperties(i, tiles[i], texture_names[i], rcss_paths[i], properties, interface);
+	if (!GetTileProperties(tiles, textures, num_tiles, properties, interface))
+		return nullptr;
 
 	auto decorator = std::make_shared<DecoratorTiledVertical>();
-	if (decorator->Initialise(tiles, texture_names, rcss_paths))
-		return decorator;
+	if (!decorator->Initialise(tiles, textures))
+		return nullptr;
 
-	return nullptr;
+	return decorator;
 }
 
 }

+ 2 - 2
Source/Core/DocumentHeader.cpp

@@ -57,9 +57,9 @@ void DocumentHeader::MergePaths(StringList& target, const StringList& source, co
 	for (size_t i = 0; i < source.size(); i++)
 	{
 		String joined_path;
-		Rocket::Core::GetSystemInterface()->JoinPath(joined_path, Replace(source_path, "|", ":"), Replace(source[i], "|", ":"));
+		Rocket::Core::GetSystemInterface()->JoinPath(joined_path, Replace(source_path, '|', ':'), Replace(source[i], '|', ':'));
 
-		target.push_back(Replace(joined_path, ":", "|"));
+		target.push_back(Replace(joined_path, ':', '|'));
 	}
 }
 

+ 1 - 1
Source/Core/Element.cpp

@@ -368,7 +368,7 @@ String Element::GetAddress(bool include_pseudo_classes) const
 	String classes = style->GetClassNames();
 	if (!classes.empty())
 	{
-		classes = Replace(classes, ".", " ");
+		classes = Replace(classes, '.', ' ');
 		address += ".";
 		address += classes;
 	}

+ 2 - 2
Source/Core/StreamFile.cpp

@@ -48,14 +48,14 @@ StreamFile::~StreamFile()
 /// Attempts to open the stream pointing at a given URL.
 bool StreamFile::Open(const String& path)
 {
-	String url_safe_path = Replace(path, ":", "|");
+	String url_safe_path = Replace(path, ':', '|');
 	SetStreamDetails(URL(url_safe_path), Stream::MODE_READ);
 
 	if (file_handle)
 		Close();
 
 	// Fix the path if a leading colon has been replaced with a pipe.
-	String fixed_path = Replace(path, "|", ":");
+	String fixed_path = Replace(path, '|', ':');
 	file_handle = GetFileInterface()->Open(fixed_path);
 	if (!file_handle)
 	{

+ 11 - 0
Source/Core/String.cpp

@@ -108,6 +108,17 @@ String Replace(String subject, const String& search, const String& replace)
 	return subject;
 }
 
+String Replace(String subject, char search, char replace)
+{
+	const size_t size = subject.size();
+	for (size_t i = 0; i < size; i++)
+	{
+		if (subject[i] == search)
+			subject[i] = replace;
+	}
+	return subject;
+}
+
 
 }
 }

+ 1 - 1
Source/Core/StyleSheetParser.cpp

@@ -323,7 +323,7 @@ int StyleSheetParser::Parse(StyleSheetNode* node, Stream* _stream, const StyleSh
 	int rule_count = 0;
 	line_number = 0;
 	stream = _stream;
-	stream_file_name = Replace(stream->GetSourceURL().GetURL(), "|", ":");
+	stream_file_name = Replace(stream->GetSourceURL().GetURL(), '|', ':');
 
 	enum class State { Global, AtRuleIdentifier, KeyframeBlock, Invalid };
 	State state = State::Global;

+ 6 - 6
Source/Core/SystemInterface.cpp

@@ -106,8 +106,8 @@ void SystemInterface::JoinPath(String& translated_path, const String& document_p
 	}
 
 	// If the path is a Windows-style absolute path, return it directly.
-	size_t drive_pos = path.find(":");
-	size_t slash_pos = Math::Min(path.find("/"), path.find("\\"));
+	size_t drive_pos = path.find(':');
+	size_t slash_pos = Math::Min(path.find('/'), path.find('\\'));
 	if (drive_pos != String::npos &&
 		drive_pos < slash_pos)
 	{
@@ -117,16 +117,16 @@ void SystemInterface::JoinPath(String& translated_path, const String& document_p
 
 	// Strip off the referencing document name.
 	translated_path = document_path;
-	translated_path = Replace(translated_path, "\\", "/");
-	size_t file_start = translated_path.rfind("/");
+	translated_path = Replace(translated_path, '\\', '/');
+	size_t file_start = translated_path.rfind('/');
 	if (file_start != String::npos)
 		translated_path.resize(file_start + 1);
 	else
 		translated_path.clear();
 
 	// Append the paths and send through URL to removing any '..'.
-	URL url(Replace(translated_path, ":", "|") + Replace(path, "\\", "/"));
-	translated_path = Replace(url.GetPathedFileName(), "|", ":");
+	URL url(Replace(translated_path, ':', '|') + Replace(path, '\\', '/'));
+	translated_path = Replace(url.GetPathedFileName(), '|', ':');
 }
 	
 // Activate keyboard (for touchscreen devices)

+ 2 - 2
Source/Core/TextureDatabase.cpp

@@ -62,10 +62,10 @@ void TextureDatabase::Shutdown()
 TextureResource* TextureDatabase::Fetch(const String& source, const String& source_directory)
 {
 	String path;
-	if (source.substr(0, 1) == "?")
+	if (source.size() > 0 && source[0] == '?')
 		path = source;
 	else
-		GetSystemInterface()->JoinPath(path, Replace(source_directory, "|", ":"), source);
+		GetSystemInterface()->JoinPath(path, Replace(source_directory, '|', ':'), source);
 
 	TextureMap::iterator iterator = instance->textures.find(path);
 	if (iterator != instance->textures.end())

+ 1 - 1
Source/Core/TextureResource.h

@@ -77,7 +77,7 @@ private:
 	String source;
 
 	typedef std::pair< TextureHandle, Vector2i > TextureData;
-	typedef UnorderedMap< RenderInterface*, TextureData > TextureDataMap;
+	typedef SmallUnorderedMap< RenderInterface*, TextureData > TextureDataMap;
 	mutable TextureDataMap texture_data;
 };