Browse Source

Specifying rendered edge size on ninepatch decorator and scaling by dp-ratio.

Michael Ragazzon 6 years ago
parent
commit
13514fed0b

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

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

+ 7 - 6
Samples/basic/demo/data/demo.rml

@@ -91,7 +91,8 @@ panel
 }
 #decorators button.ninepatch
 {
-	decorator: ninepatch(button, button-inner);
+	/* The edges will be scaled by the current dp-ratio. */
+	decorator: ninepatch(button, button-inner, 1.0);
 }
 #decorators button.ninepatch:hover
 {
@@ -118,12 +119,12 @@ panel
 		<button class="ninepatch">Ninepatch</button>
 	</div>
 	<div>	
-		<button class="big">Big</button>
-		<button class="big ninepatch">Big Ninepatch</button>
+		<button class="big">Image</button>
+		<button class="big ninepatch">Ninepatch</button>
 	</div>
 	<div>	
-		<button class="small">Small</button>
-		<button class="small ninepatch">Small N</button>
+		<button class="small">Image</button>
+		<button class="small ninepatch">Ninepatch</button>
 	</div>
 	<div>	
 		<button class="tiny"></button>
@@ -140,7 +141,7 @@ panel
 </panel>
 <tab>Controls</tab>
 <panel>
-	<div>Yype something here: <input style="vertical-align: -7px;" type="text" value="Sample text"/></div>
+	<div>Type something here: <input style="vertical-align: -7px;" type="text" value="Sample text"/></div>
 </panel>
 </tabset>
 

+ 2 - 0
Samples/basic/demo/src/main.cpp

@@ -234,6 +234,8 @@ int main(int RMLUI_UNUSED_PARAMETER(argc), char** RMLUI_UNUSED_PARAMETER(argv))
 	Rml::Debugger::Initialise(context);
 	Input::SetContext(context);
 	shell_renderer->SetContext(context);
+	
+	context->SetDensityIndependentPixelRatio(1.0f);
 
 	EventInstancer event_listener_instancer;
 	Rml::Core::Factory::RegisterEventListenerInstancer(&event_listener_instancer);

+ 51 - 14
Source/Core/DecoratorNinePatch.cpp

@@ -30,6 +30,7 @@
 #include "DecoratorNinePatch.h"
 #include "../../Include/RmlUi/Core/Element.h"
 #include "../../Include/RmlUi/Core/Geometry.h"
+#include "../../Include/RmlUi/Core/ElementUtilities.h"
 
 namespace Rml {
 namespace Core {
@@ -42,10 +43,14 @@ DecoratorNinePatch::~DecoratorNinePatch()
 {
 }
 
-bool DecoratorNinePatch::Initialise(const Rectangle& _rect_outer, const Rectangle& _rect_inner, const Texture& _texture)
+bool DecoratorNinePatch::Initialise(const Rectangle& _rect_outer, const Rectangle& _rect_inner, const std::array<Property, 4>* _edges, const Texture& _texture)
 {
 	rect_outer = _rect_outer;
 	rect_inner = _rect_inner;
+
+	if (_edges)
+		edges = std::make_unique< std::array<Property, 4> >(*_edges);
+
 	int texture_index = AddTexture(_texture);
 	return (texture_index >= 0);
 }
@@ -85,14 +90,30 @@ DecoratorDataHandle DecoratorNinePatch::GenerateElementData(Element* element) co
 		tex_coords[i] = tex_pos[i] / texture_dimensions;
 
 	// Surface position [0, surface_dimensions]
-	// Need to keep the four corner patches at their native pixel size, but stretch the inner patches.
+	// Need to keep the corner patches at their native pixel size, but stretch the inner patches.
 	Vector2f surface_pos[4];
 	surface_pos[0] = { 0, 0 };
 	surface_pos[1] = tex_pos[1] - tex_pos[0];
 	surface_pos[2] = surface_dimensions - (tex_pos[3] - tex_pos[2]);
 	surface_pos[3] = surface_dimensions;
 
-	// In case the surface dimensions are less than the size of the edges, we need to scale down the corner rectangles, one dimension at a time.
+	// Change the size of the edges if specified.
+	if (edges)
+	{
+		const float dp_ratio = ElementUtilities::GetDensityIndependentPixelRatio(element);
+		float lengths[4]; // top, right, bottom, left
+		lengths[0] = element->ResolveLengthPercentage(&(*edges)[0], dp_ratio * (surface_pos[1].y - surface_pos[0].y));
+		lengths[1] = element->ResolveLengthPercentage(&(*edges)[1], dp_ratio * (surface_pos[3].x - surface_pos[2].x));
+		lengths[2] = element->ResolveLengthPercentage(&(*edges)[2], dp_ratio * (surface_pos[3].y - surface_pos[2].y));
+		lengths[3] = element->ResolveLengthPercentage(&(*edges)[3], dp_ratio * (surface_pos[1].x - surface_pos[0].x));
+
+		surface_pos[1].y = lengths[0];
+		surface_pos[2].x = surface_dimensions.x - lengths[1];
+		surface_pos[2].y = surface_dimensions.y - lengths[2];
+		surface_pos[1].x = lengths[3];
+	}
+
+	// In case the surface dimensions are less than the size of the corners, we need to scale down the corner rectangles, one dimension at a time.
 	const Vector2f surface_center_size = surface_pos[2] - surface_pos[1];
 	for (int i = 0; i < 2; i++)
 	{
@@ -156,32 +177,52 @@ void DecoratorNinePatch::RenderElement(Element* element, DecoratorDataHandle ele
 
 
 
-
 DecoratorNinePatchInstancer::DecoratorNinePatchInstancer()
 {
 	sprite_outer_id = RegisterProperty("outer", "").AddParser("string").GetId();
 	sprite_inner_id = RegisterProperty("inner", "").AddParser("string").GetId();
+	edge_ids[0] = RegisterProperty("edge-top", "0px").AddParser("number_length_percent").GetId();
+	edge_ids[1] = RegisterProperty("edge-right", "0px").AddParser("number_length_percent").GetId();
+	edge_ids[2] = RegisterProperty("edge-bottom", "0px").AddParser("number_length_percent").GetId();
+	edge_ids[3] = RegisterProperty("edge-left", "0px").AddParser("number_length_percent").GetId();
+
+	RegisterShorthand("edge", "edge-top, edge-right, edge-bottom, edge-left", ShorthandType::Box);
 	
 	RMLUI_ASSERT(sprite_outer_id != PropertyId::Invalid && sprite_inner_id != PropertyId::Invalid);
 
-	RegisterShorthand("decorator", "outer, inner", ShorthandType::RecursiveCommaSeparated);
+	RegisterShorthand("decorator", "outer, inner, edge?", ShorthandType::RecursiveCommaSeparated);
 }
 
 DecoratorNinePatchInstancer::~DecoratorNinePatchInstancer()
 {
 }
 
-
 SharedPtr<Decorator> DecoratorNinePatchInstancer::InstanceDecorator(const String& RMLUI_UNUSED_PARAMETER(name), const PropertyDictionary& properties, const DecoratorInstancerInterface& interface)
 {
 	RMLUI_UNUSED(name);
 
+	bool edges_set = false;
+	std::array<Property,4> edges;
+	for (int i = 0; i < 4; i++)
+	{
+		edges[i] = *properties.GetProperty(edge_ids[i]);
+		if (edges[i].value.Get(0.0f) != 0.0f)
+		{
+			edges_set = true;
+		}
+		// Change numbers to percent because we cannot resolve this later unless we make some changes to the resolve procedures.
+		if (edges[i].unit == Property::NUMBER)
+		{
+			edges[i].value = edges[i].value.Get(0.0f) * 100.f;
+			edges[i].unit = Property::PERCENT;
+		}
+	}
+
 	const Sprite* sprite_outer = nullptr;
 	const Sprite* sprite_inner = nullptr;
 
 	{
-		const Property* property = properties.GetProperty(sprite_outer_id);
-		const String sprite_name = property->Get< String >();
+		const String sprite_name = properties.GetProperty(sprite_outer_id)->Get< String >();
 		sprite_outer = interface.GetSprite(sprite_name);
 		if (!sprite_outer)
 		{
@@ -190,8 +231,7 @@ SharedPtr<Decorator> DecoratorNinePatchInstancer::InstanceDecorator(const String
 		}
 	}
 	{
-		const Property* property = properties.GetProperty(sprite_inner_id);
-		const String sprite_name = property->Get< String >();
+		const String sprite_name = properties.GetProperty(sprite_inner_id)->Get< String >();
 		sprite_inner = interface.GetSprite(sprite_name);
 		if (!sprite_inner)
 		{
@@ -208,14 +248,11 @@ SharedPtr<Decorator> DecoratorNinePatchInstancer::InstanceDecorator(const String
 
 	auto decorator = std::make_shared<DecoratorNinePatch>();
 
-	if (!decorator->Initialise(sprite_outer->rectangle, sprite_inner->rectangle, sprite_outer->sprite_sheet->texture))
+	if (!decorator->Initialise(sprite_outer->rectangle, sprite_inner->rectangle, (edges_set ? &edges : nullptr), sprite_outer->sprite_sheet->texture))
 		return nullptr;
 
 	return decorator;
 }
 
-
-
-
 }
 }

+ 4 - 2
Source/Core/DecoratorNinePatch.h

@@ -31,6 +31,7 @@
 
 #include "../../Include/RmlUi/Core/Decorator.h"
 #include "../../Include/RmlUi/Core/DecoratorInstancer.h"
+#include "../../Include/RmlUi/Core/Property.h"
 
 namespace Rml {
 namespace Core {
@@ -41,7 +42,7 @@ public:
 	DecoratorNinePatch();
 	virtual ~DecoratorNinePatch();
 
-	bool Initialise(const Rectangle& rect_outer, const Rectangle& rect_inner, const Texture& texture);
+	bool Initialise(const Rectangle& rect_outer, const Rectangle& rect_inner, const std::array<Property, 4>* _edges, const Texture& texture);
 
 	DecoratorDataHandle GenerateElementData(Element* element) const override;
 	void ReleaseElementData(DecoratorDataHandle element_data) const override;
@@ -50,6 +51,7 @@ public:
 
 private:
 	Rectangle rect_outer, rect_inner;
+	UniquePtr<std::array<Property,4>> edges;
 };
 
 
@@ -64,10 +66,10 @@ public:
 
 private:
 	PropertyId sprite_outer_id, sprite_inner_id;
+	PropertyId edge_ids[4];
 
 };
 
-
 }
 }
 

+ 1 - 1
Source/Core/DecoratorTiledImageInstancer.cpp

@@ -36,7 +36,7 @@ namespace Core {
 DecoratorTiledImageInstancer::DecoratorTiledImageInstancer() : DecoratorTiledInstancer(1)
 {
 	RegisterTileProperty("image", false);
-	RegisterShorthand("decorator", "image", ShorthandType::Recursive);
+	RegisterShorthand("decorator", "image", ShorthandType::RecursiveRepeat);
 }
 
 DecoratorTiledImageInstancer::~DecoratorTiledImageInstancer()

+ 4 - 3
Source/Core/PropertyShorthandDefinition.h

@@ -41,9 +41,9 @@ enum class ShorthandItemType { Invalid, Property, Shorthand };
 
 // Each entry in a shorthand points either to another shorthand or a property
 struct ShorthandItem {
-	ShorthandItem() : type(ShorthandItemType::Invalid), property_id(PropertyId::Invalid), property_definition(nullptr) {}
-	ShorthandItem(PropertyId id, const PropertyDefinition* definition) : type(ShorthandItemType::Property), property_id(id), property_definition(definition) {}
-	ShorthandItem(ShorthandId id, const ShorthandDefinition* definition) : type(ShorthandItemType::Shorthand), shorthand_id(id), shorthand_definition(definition) {}
+	ShorthandItem() : type(ShorthandItemType::Invalid), property_id(PropertyId::Invalid), property_definition(nullptr), optional(false) {}
+	ShorthandItem(PropertyId id, const PropertyDefinition* definition, bool optional) : type(ShorthandItemType::Property), property_id(id), property_definition(definition), optional(optional) {}
+	ShorthandItem(ShorthandId id, const ShorthandDefinition* definition, bool optional) : type(ShorthandItemType::Shorthand), shorthand_id(id), shorthand_definition(definition), optional(optional) {}
 
 	ShorthandItemType type;
 	union {
@@ -54,6 +54,7 @@ struct ShorthandItem {
 		const PropertyDefinition* property_definition;
 		const ShorthandDefinition* shorthand_definition;
 	};
+	bool optional;
 };
 
 // A list of shorthands or properties

+ 31 - 17
Source/Core/PropertySpecification.cpp

@@ -131,16 +131,24 @@ ShorthandId PropertySpecification::RegisterShorthand(const String& shorthand_nam
 	// Construct the new shorthand definition and resolve its properties.
 	UniquePtr<ShorthandDefinition> property_shorthand(new ShorthandDefinition());
 
-	for (const String& name : property_list)
+	for (const String& raw_name : property_list)
 	{
 		ShorthandItem item;
+		bool optional = false;
+		String name = raw_name;
+
+		if (!raw_name.empty() && raw_name.back() == '?')
+		{
+			optional = true;
+			name.pop_back();
+		}
 
 		PropertyId property_id = property_map->GetId(name);
 		if (property_id != PropertyId::Invalid)
 		{
 			// We have a valid property
 			if (const PropertyDefinition* property = GetProperty(property_id))
-				item = ShorthandItem(property_id, property);
+				item = ShorthandItem(property_id, property, optional);
 		}
 		else
 		{
@@ -148,10 +156,10 @@ ShorthandId PropertySpecification::RegisterShorthand(const String& shorthand_nam
 			ShorthandId shorthand_id = shorthand_map->GetId(name);
 
 			// Test for valid shorthand id. The recursive types (and only those) can hold other shorthands.
-			if (shorthand_id != ShorthandId::Invalid && (type == ShorthandType::Recursive || type == ShorthandType::RecursiveCommaSeparated))
+			if (shorthand_id != ShorthandId::Invalid && (type == ShorthandType::RecursiveRepeat || type == ShorthandType::RecursiveCommaSeparated))
 			{
 				if (const ShorthandDefinition * shorthand = GetShorthand(shorthand_id))
-					item = ShorthandItem(shorthand_id, shorthand);
+					item = ShorthandItem(shorthand_id, shorthand, optional);
 			}
 		}
 
@@ -287,7 +295,7 @@ bool PropertySpecification::ParseShorthandDeclaration(PropertyDictionary& dictio
 			dictionary.SetProperty(shorthand_definition->items[i].property_definition->GetId(), new_property);
 		}
 	}
-	else if (shorthand_definition->type == ShorthandType::Recursive)
+	else if (shorthand_definition->type == ShorthandType::RecursiveRepeat)
 	{
 		bool result = true;
 
@@ -307,30 +315,36 @@ bool PropertySpecification::ParseShorthandDeclaration(PropertyDictionary& dictio
 	}
 	else if (shorthand_definition->type == ShorthandType::RecursiveCommaSeparated)
 	{
-		bool result = true;
-
 		StringList subvalues;
 		StringUtilities::ExpandString(subvalues, property_value);
 
-		if (shorthand_definition->items.size() != subvalues.size())
+		size_t num_optional = 0;
+		for (auto& item : shorthand_definition->items)
+			if (item.optional)
+				num_optional += 1;
+
+		if (subvalues.size() + num_optional < shorthand_definition->items.size())
 		{
-			// We must declare all subvalues
+			// Not enough subvalues declared.
 			return false;
 		}
 
-		for (size_t i = 0; i < shorthand_definition->items.size(); i++)
+		size_t subvalue_i = 0;
+		for (size_t i = 0; i < shorthand_definition->items.size() && subvalue_i < subvalues.size(); i++)
 		{
+			bool result = false;
+
 			const ShorthandItem& item = shorthand_definition->items[i];
 			if (item.type == ShorthandItemType::Property)
-				result &= ParsePropertyDeclaration(dictionary, item.property_id, subvalues[i]);
+				result = ParsePropertyDeclaration(dictionary, item.property_id, subvalues[subvalue_i]);
 			else if (item.type == ShorthandItemType::Shorthand)
-				result &= ParseShorthandDeclaration(dictionary, item.shorthand_id, subvalues[i]);
-			else
-				result = false;
-		}
+				result = ParseShorthandDeclaration(dictionary, item.shorthand_id, subvalues[subvalue_i]);
 
-		if (!result)
-			return false;
+			if (result)
+				subvalue_i += 1;
+			else if (!item.optional)
+				return false;
+		}
 	}
 	else
 	{

+ 1 - 1
Source/Core/StyleSheet.cpp

@@ -272,7 +272,7 @@ FontEffectsPtr StyleSheet::InstanceFontEffectsFromString(const String& font_effe
 			PropertyDictionary properties;
 			if (!specification.ParsePropertyDeclaration(properties, "font-effect", shorthand))
 			{
-				Log::Message(Log::LT_WARNING, "Could not parse decorator value '%s' at %s:%d", font_effect_string.c_str(), source_path, source_line_number);
+				Log::Message(Log::LT_WARNING, "Could not parse font-effect value '%s' at %s:%d", font_effect_string.c_str(), source_path, source_line_number);
 				continue;
 			}
 

+ 1 - 1
Source/Core/StyleSheetSpecification.cpp

@@ -276,7 +276,7 @@ void StyleSheetSpecification::RegisterDefaultProperties()
 	RegisterShorthand(ShorthandId::BorderRight, BORDER_RIGHT, "border-right-width, border-right-color", ShorthandType::FallThrough);
 	RegisterShorthand(ShorthandId::BorderBottom, BORDER_BOTTOM, "border-bottom-width, border-bottom-color", ShorthandType::FallThrough);
 	RegisterShorthand(ShorthandId::BorderLeft, BORDER_LEFT, "border-left-width, border-left-color", ShorthandType::FallThrough);
-	RegisterShorthand(ShorthandId::Border, BORDER, "border-top, border-right, border-bottom, border-left", ShorthandType::Recursive);
+	RegisterShorthand(ShorthandId::Border, BORDER, "border-top, border-right, border-bottom, border-left", ShorthandType::RecursiveRepeat);
 
 	RegisterProperty(PropertyId::Display, DISPLAY, "inline", false, true).AddParser("keyword", "none, block, inline, inline-block");
 	RegisterProperty(PropertyId::Position, POSITION, "static", false, true).AddParser("keyword", "static, relative, absolute, fixed");

+ 10 - 0
readme.md

@@ -147,6 +147,16 @@ decorator: ninepatch( button-outer, button-inner );
 ```
 The two sprites must be located in the same sprite sheet. Only sprites are supported by the ninepatch decorator, image urls cannot be used.
 
+Furthermore, the ninepatch decorator can have the rendered size of its edges specified manually.
+```CSS
+decorator: ninepatch( button-outer, button-inner, 19px 12px 25px 12px );
+```
+The edge sizes are specified in the common `top-right-bottom-left` box order. The box shorthands are also available, e.g. a single value will be replicated to all. Percent and numbers can also be used, they will scale relative to the native size of the given edge multiplied by the current dp ratio. Thus, setting
+```CSS
+decorator: ninepatch( button-outer, button-inner, 1.0 );
+```
+is a simple approach to scale the decorators with higher dp ratios. For crisper graphics, increase the sprite sheet's pixel size at the edges and lower the rendered edge size number correspondingly.
+
 
 ### Font-effects