Attributes are dynamic named properties that can be added to objects in Cornerstone.
Attributes may be defined during compilation or they can be dynamically added and removed during runtime. Attributes can be accessed by their name from native code or from external systems, such as CSS.
In addition to their name, attributes have a type (such as boolean, integer, floating, etc). Cornerstone comes with most of the basic types defined and users can easily add new types if they need to extend the functionality.
Each attribute has multiple layers for possible values. These are ordered by priority. Values on more important layers will override value with lower priority. This system allows us a flexible way of defining attribute values. We can have default values that are overriden by values set from CSS and then finally overriden by values specified from C++/JavaScript code.
The attribute layers are defined in increasing priority as follows:
Valuable::Node is a key class when using attributes. The Valuable::Node itself is a special kind of attribute: it is an attribute itself, but instead of storing a value, it stores a list of other attributes. These attributes are the child attributes of the Valuable::Node.
MultiWidgets::Widget inherits from Valuable::Node and contain a list of widget-specific attributes. These attributes can be used to control the appearance and behaviour of a widget, e.g. text, color, font-size. As all widgets are nodes, custom attributes which contain some application-specific data can be added to them.
The attribute hierarchy is not to be confused with the widget hierarchy. It is completely separate from it. As widgets inherit from Valuable::Node, it is possible to define a widget to be an attribute of another widget. This will not automatically make the added widget a child of the containing widget in widget hierarchy however. If this is what you want, you must explicitly define the attribute widget as child in the widget hierarchy also.
Attributes can be defined in C++ code like follows:
When constructing the node root in the code above, we define its parent to be a nullptr. It could be another node object as well if we were creating a deeper hierarchy. After that we create node parent as a child of root. The two latter attributes are defined to be the children of parent. We also define the names and default values for the attributes when creating them.
Adding new attributes to existing nodes can be done like this:
The code above works for any Valuable::Node (including MultiWidgets::Widget). This allows the user to dynamically add new attributes to existing instances of objects. Adding attribute to node will also transfer responsibility of memory management to the parent node.
Attribute values can be set using C++ like follows:
Values can be set using Attribute::setValue function or using Node::setValue with a parameter path.
Same example using common widget-attribute could be:
Accessing attribute values is similar to setting them. We can query the values from C++ like follows:
Again, we can either get the value directly using the attribute or with a path through a Valuable::Node object. HelloImagesExample.cpp shows an example how to parse command line arguments easily using attributes.
It is often important to know when the value of an attribute changes. For example, you might want to perform some special logic if the width of a widget changes. Monitoring attributes is facilitated by attribute listeners. You can easily define callbacks for attributes that get called every time the value of the given attribute changes.
In the example above, we define a simple lambda function that gets called every time the attribute value changes. Changes in attributes can also be monitored using the event system.
AttributesExample shows you more about using attributes with widgets