Decorators are generic, configurable, reusable objects designed to be attached to elements to add custom rendering effects. Decorators render on top of an element’s border and background. For a full description on how decorators are attached to elements and configured through RML, see the relevant section in the RCSS documentation.

Decorator overview

RmlUi ships with four decorators; one for displaying a single image on an element, and three for tiling images across an element’s surface. Depending on how fancy you want your elements to look, you may need to develop custom decorators. Custom decorators are defined and instanced in a similar way to custom elements. A custom decorator class is created, which needs to derive from Rml::Decorator, and an instancer is registered for it with the RmlUi factory.

In order to optimise memory usage, decorators are shared between elements where appropriate. For each decorator it has, an element maintains a handle to arbitrary per-element data that the decorator can generate if required. So, for example, in the document generated by the following RML:

<rml>
<head>
	<style>
		button
		{
			decorator: image( button.png );
		}
	</style>
</head>
<body>
	<button id="restart">Restart</button>
	<button id="restore">Restore</button>
	<button id="quit">Quit</button>
</body>
</rml>

only one actual decorator will be instanced; that decorator will be shared across all three button elements. The entire decorator system looks like the following:

decorators_1.gif

Custom decorators

If you need custom decoration above what the built-in decorators can provide, you can easily create a custom decorator to suit your needs.

Creating a custom decorator

All custom decorators are classes derived (not necessarily directly) from Rml::Decorator. There are three virtual functions that need to be overridden in a custom decorator:

// Called on a decorator to generate any required per-element data for a newly decorated element.
// @param[in] element The newly decorated element.
// @return A handle to a decorator-defined data handle, or NULL if none is needed for the element.
virtual Rml::DecoratorDataHandle GenerateElementData(Rml::Element* element) = 0;

// Called to release element data generated by this decorator.
// @param[in] element_data The element data handle to release.
virtual void ReleaseElementData(Rml::DecoratorDataHandle element_data) = 0;

// Called to render the decorator on an element.
// @param[in] element The element to render the decorator on.
// @param[in] element_data The handle to the data generated by the decorator for the element.
virtual void RenderElement(Rml::Element* element,
                           Rml::DecoratorDataHandle element_data) = 0;

GenerateElementData() will be called by an element that uses the decorator before the first time it is rendered and whenever it is resized. If the decorator needs to store data for each element that uses it, it can generate the data in this function and return a handle to it. The DecoratorDataHandle, like all handle types, is a void pointer type. If no data is required, return NULL (0). Therefore make sure that for your decorators, 0 is not a valid handle!

ReleaseElementData() will be called by an element when it needs to release a non-NULL data handle that was generated for it by the decorator. The decorator should free any resources allocated for the data handle.

RenderElement() will be called every frame when the element wants the decorator to render its decoration on the element. It passes in itself as well as the data handle that the decorator generated for it previously; this will be NULL if no data was generated. The decorator is free here to execute any rendering code necessary. Remember that the clipping region will be set up at this point as appropriate for the element; you’ll need to disable it if you want to render outside of the element’s bordered area. If you do so, remember to enable it again as appropriate, otherwise RmlUi may render clipped content.

Generating geometry

Custom decorators do not need to render their geometry through RmlUi’s render interface; as the render interface is quite limiting, decorators may want to talk directly to their application’s renderer to access effects such as multiple textures, pixel shaders, etc, for fancy effects. However, you may want to render through the render interface for simplicity if it’s all you need.

RmlUi uses the Geometry class internally for storing and rendering generated geometry. Each geometry object has a single vertex buffer, index buffer and an optional texture. A custom decorator can store one or more geometry objects in each decorator handle.

To create a geometry object, simply call the constructor.

Rml::Geometry* geometry = new Rml::Geometry();

To populate the object’s vertices and indices, use the GetVertices() and GetIndices() functions to retrieve references to the internal arrays.

std::vector< Rml::Vertex >& vertices = geometry->GetVertices();
std::vector< int >& indices = geometry->GetIndices();

Remember to store the arrays as references! Then simply add elements to the arrays as appropriate for your geometry.

Rml::Vertex vertex;
vertex.colour = Rml::Colourb(255, 128, 192, 200);
vertex.tex_coord = Rml::Vector2f(0, 0);

vertex.position = Rml::Vector2f(0, 0);
vertices.insert(vertex);
vertex.position = Rml::Vector2f(0, 68);
vertices.insert(vertex);
vertex.position = Rml::Vector2f(42, 68);
vertices.insert(vertex);
vertex.position = Rml::Vector2f(42, 0);
vertices.insert(vertex);

indices.insert(0);
indices.insert(1);
indices.insert(2);
indices.insert(0);
indices.insert(2);
indices.insert(3);

To render the geometry through the current render interface, call the Render() function. This takes a single vector, the translation to apply to the geometry before rendering it.

geometry->Render(element->GetAbsoluteOffset(Rml::Box::PADDING));

To apply a texture to the geometry, call the SetTexture() function with a valid Rml::Texture object loaded through the render interface. You can load a texture either by fetching the current render interface with the Rml::GetRenderInterface() function and calling LoadTexture() or GenerateTexture(), or with the LoadTexture() helper method on the Rml::Decorator class.

// Attempts to load a texture into the list of textures in use by the decorator.
// @param[in] texture_name The name of the texture to load.
// @param[in] rcss_path The RCSS file the decorator definition was loaded from; this is used to resolve relative paths.
// @return The index of the texture if the load was successful, or -1 if the load failed.
int LoadTexture(const Rml::String& texture_name, const Rml::String& rcss_path);

// Returns one of the decorator's previously loaded textures.
// @param[in] index The index of the desired texture.
// @return The texture at the appropriate index, or nullptr if the index was invalid.
const Rml::Texture* GetTexture(int index = 0) const;

LoadTexture() takes the file name of the texture to load and the path of the resource that specified the file name; this is used so file names can be specified relative to the declaring RCSS file. If your texture name is not relative to any particular resource, leave this blank; otherwise, if you are loading from an RCSS property, you can access the source of the property with the source variable on the property structure. If the texture load is successful, the index of the new texture will be returned; otherwise, -1 will be returned. You can then retrieve the Rml::Texture object with the GetTexture() function.

If you ever change the geometry or its texture, be sure to call the Release() function to force the geometry to be recompiled.

Creating a custom decorator instancer

The instancer for a decorator is a bit more sophisticated than the element; it is responsible for defining and processing the properties that can be used to configure the decorator. While you can create a custom decorator with no properties, we recommend you expose all variables to RCSS; it’s quick, easy and you’ll have much more flexible decorators.

A decorator instancer needs to derive from Rml::DecoratorInstancer. The following pure virtual function needs to be overridden:

// Instances a decorator given the property tag and attributes from the RCSS file.
// @param[in] name The type of decorator desired. For example, "decorator: simple(...);" is declared as type "simple".
// @param[in] properties All RCSS properties associated with the decorator.
// @param[in] instancer_interface An interface for querying the active style sheet.
// @return A shared_ptr to the decorator if it was instanced successfully.
virtual Rml::SharedPtr<Rml::Decorator> InstanceDecorator(const Rml::String& name,
                                                   const Rml::PropertyDictionary& properties,
                                                   const Rml::DecoratorInstancerInterface& interface) = 0;

InstanceDecorator() will be called whenever a decorator needs to be created using this instancer. It is passed with name, the name the decorator was created with, properties, the dictionary of the properties the RCSS rules defined for the decorator (see below), and interface, an interface for querying the current document state, such as for retrieving sprites.

The property dictionary will contain an entry for every property in the decorator’s property specification; if a value was not specified in the RCSS, then the default value will be put into the dictionary for you. If the decorator was created successfully, return it from the function. If not, return nullptr and any elements that would have used the decorator will ignore it.

Defining the decorator’s properties

Each decorator instancer holds a complete property specification for the decorators it creates. In its constructor, the custom instancer has the opportunity to add properties and shorthands to its specification by using the protected functions RegisterProperty() and RegisterShorthand(). For detailed documentation on defining properties, see the documentation on registering custom properties. Note that custom property parsers registered with Rml::StyleSheetSpecification can be used in decorator property specifications.

The following is an example decorator defining a simple property specification:

CustomDecoratorInstancer::CustomDecoratorInstancer() : Rml::DecoratorInstancer()
{
	property_id1 = RegisterProperty("custom-property-1", "1").AddParser("number").GetId();
	property_id2 = RegisterProperty("custom-property-2", "auto").AddParser("number")
	                                                            .AddParser("keyword", "auto, none")
	                                                            .GetId();
	RegisterShorthand("decorator", "custom-property-1, custom-property-2");
}

The custom decorator now has two properties. The property dictionary passed into the instancer’s InstanceDecorator() function will contain values for the two properties, defaulting to their specified default values if they weren’t set in the RCSS.

Note that the shorthand decorator is special. This shorthand will be used to parse the text inside the parenthesis of the property declaration. This allows specifying the decorator with inline properties as in the following example.

decorator: custom-decorator( 15 auto );

Now this will be parsed by the above rules such that ‘custom-property-1’ contains 15, and ‘custom-property-2’ contains the keyword ‘auto’.

The property IDs are stored on the instancer object, so they can easily retrieve the parsed value during the call to InstanceDecorator(),

int value1 = properties.GetProperty(property_id1)->Get<int>();

The value1 variable will now contain the value specified in the style sheet, e.g. ‘15’ in the above example.

Registering an instancer

To register a custom decorator instancer with RmlUi, call the RegisterDecoratorInstancer() function on the RmlUi factory (Rml::Factory) after RmlUi has been initialised.

// Registers an instancer that will be used to instance decorators.
// @param[in] name The name of the decorator the instancer will be called for.
// @param[in] instancer The instancer to call when the decorator name is encountered.
// @lifetime The instancer must be kept alive until after the call to Rml::Shutdown.
// @return The added instancer if the registration was successful, nullptr otherwise.
static Rml::DecoratorInstancer* RegisterDecoratorInstancer(const Rml::String& name,
                                                                    Rml::DecoratorInstancer* instancer);

The name parameter is the string value that you will use to bind to the decorator through RML. For example, calling:

// Keep instancer alive until after the call to Rml::Shutdown().
auto instancer = std::make_unique<CustomDecoratorInstancer>();
Rml::Factory::RegisterDecoratorInstancer("custom-decorator", instancer.get());

will cause the following RML fragment to create decorators using the CustomDecoratorInstancer type:

<rml>
<head>
	<style>
		button
		{
			decorator: custom-decorator( 15 );
		}
	</style>
</head>
<body>
...

Like for other instancers, it is the user’s responsibility to manage the lifetime of the instancer. Thus, it must be kept alive until after the call to Rml::Shutdown(), and then cleaned up by the user.

Updating decorators

Decorators are not updated from inside RmlUi. If you have a decorator that requires updating (for example, for updating animation) then you could maintain a list of the decorator data handles that require updating and process them when appropriate during your update loop, or simply update them during the render loop.