C Manual Decorators - Gambini/libRocket GitHub Wiki

Decorators

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

Rocket 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 Rocket::Core::Decorator, and an instancer is registered for it with the Rocket 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
			{
				icon-decorator: image;
				icon-image-src: 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:

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 Rocket::Core::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 Rocket::Core::DecoratorDataHandle GenerateElementData(Rocket::Core::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(Rocket::Core::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(Rocket::Core::Element* element,
                           Rocket::Core::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 Rocket may render clipped content.

Generating geometry

Custom decorators do not need to render their geometry through Rocket's render interface; as the render interface is quite limiting, decorators way 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.

Rocket 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.

Rocket::Core::Geometry* geometry = new Rocket::Core::Geometry();

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

EMP::Core::STL::vector< Rocket::Core::Vertex >& vertices = geometry->GetVertices();
EMP::Core::STL::vector< int >& indices = geometry->GetIndices();

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

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

vertex.position = EMP::Core::Vector2f(0, 0);
vertices.insert(vertex);
vertex.position = EMP::Core::Vector2f(0, 68);
vertices.insert(vertex);
vertex.position = EMP::Core::Vector2f(42, 68);
vertices.insert(vertex);
vertex.position = EMP::Core::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(Rocket::Core::Box::PADDING));

To apply a texture to the geometry, call the SetTexture() function with a valid Rocket::Core::Texture object loaded through the render interface. You can load a texture either by fetching the current render interface with the Rocket::Core::GetRenderInterface() function and calling LoadTexture() or GenerateTexture(), or with the LoadTexture() helper method on the Rocket::Core::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 EMP::Core::String& texture_name, const EMP::Core::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 NULL if the index was invalid.
const Rocket::Core::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 Rocket::Core::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 Rocket::Core::DecoratorInstancer. The following three pure virtual functions need to be overridden:

// Instances a decorator given the property tag and attributes from the RCSS file.
// @param[in] name The type of decorator desired.
// @param[in] properties All RCSS properties associated with the decorator.
// @return The decorator if it was instanced successful, NULL if an error occured.
virtual Rocket::Core::Decorator* InstanceDecorator(const EMP::Core::String& name,
                                                   const PropertyDictionary& properties) = 0;

// Releases the given decorator.
// @param[in] decorator Decorator to release.
virtual void ReleaseDecorator(Decorator* decorator) = 0;

// Releases the instancer.
virtual void Release() = 0;

InstanceDecorator() will be called whenever a decorator needs to created using this instancer. It is passed in name, the name the decorator was created with, and properties, the dictionary of the properties the RCSS rules defined for the decorator (see below). 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 NULL (0) and any elements that would have used the decorator will ignore it.

ReleaseDecorator() will be called when a decorator created from this instancer is released from Rocket. The instancer should delete the decorator as well as any resources allocated for it.

Release() will be called when the instancer itself is no longer required, usually when Rocket is shut down. It should delete itself if it was dynamically allocated, as well as any other resources allocated for it.

Defining the decorator's properties

Each decorator instancer holds a complete property specification for the decorators it creates. The specification object (stored as a Rocket::Core::PropertySpecification type) is a protected member variable on the Rocket::Core::DecoratorInstancer class, so derived instancers can access it. A custom instancer has the opportunity in its constructor to add properties and shorthands to its specification. Use the RegisterProperty() and RegisterShorthand() functions on the property specification object itself to do this; for detailed documentation on defining properties, see the documentation on registering custom properties. Note that custom property parsers registered with Rocket::Core::StyleSheetSpecification can be used in decorator property specifications.

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

CustomDecoratorInstancer::CustomDecoratorInstancer() : Rocket:Core:DecoratorInstancer()
{
	properties.RegisterProperty("custom-property-1", "1").AddParser("number");
	properties.RegisterProperty("custom-property-2", "auto").AddParser("number")
								.AddParser("keyword", "auto, none");
	properties.RegisterShorthand("custom-shorthand", "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.

Registering an instancer

To register a custom decorator instancer with Rocket, call the RegisterDecoratorInstancer() function on the Rocket factory (Rocket::Core::Factory) after Rocket 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.
// @return The added instancer if the registration was successful, NULL otherwise.
static Rocket::Core::DecoratorInstancer* RegisterDecoratorInstancer(const EMP::Core::String& name,
                                                                    Rocket::Core::DecoratorInstancer* instancer);

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

Rocket::Core::DecoratorInstancer* instancer = new CustomDecoratorInstancer();
Rocket::Core::Factory::RegisterDecoratorInstancer("custom-decorator", instancer);
instancer->RemoveReference();

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

<rml>
	<head>
		<style>
			button
			{
				icon-decorator: custom-decorator;
				icon-parameter-1: 15;
			}
		</style>
	</head>
	<body>
...

Decorator instancers are reference counted. They start with a single reference, belonging to the constructing code; the factory will maintain a reference count for each time the instancer is registered with it (a decorator instancer can be registered multiple times with the factory under different names). Therefore, remember to remove the initial reference once you've registered the instancer with the factory.

Updating decorators

Decorators are not updated from inside Rocket. 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.

⚠️ **GitHub.com Fallback** ⚠️