LAttribute Class

#include <Layers/lattribute.h>
Inherits: LObject

Public Functions

LAttribute(const LString & name)
~LAttribute()
T as(const LStringList & conditions = LStringList())
const T * as_if(const LStringList & conditions = LStringList())
void break_link(bool update = true)
void clone_to(LObject * parent)
bool create_link(LAttribute * link_attr)
bool create_link(const LString & target_path)
LAttributes dependents(bool transitive = false)
void disconnect_change(const LConnectionID & connection)
void disconnect_link_change(const LConnectionID & connection)
bool has_states()
bool in_link_chain(const LAttribute * attr)
LConnectionID on_change(std::function<void ()> callback)
LConnectionID on_link_change(std::function<void ()> callback)
LString path()
void resolve_links()
void set_value(const char * new_value)
void set_value(const LVariant & new_value)
LAttribute * state(const LStringList & conditions)
LAttributeMap states(bool include_parent_states = true)
const LLink * link()
const LVariant & value()

Detailed Description

An attribute is a runtime object for storing data, created by the loader and maintained by the engine. Attributes store numbers, strings, or booleans through the LVariant type.

Attributes are declared in .lyr files. See the language reference for more details. Examples in this document are rooted in the language syntax.

LAttribute is the live runtime object whereas LAttributeNode is the parsed declaration it is built from. Since a single declaration can name several targets through expansion, one node can produce several live attributes.

Reading attribute values

Consider the following attribute declarations:

Name: "Elliot Alderson"
Age: 28
Is.Admin: true

Once loaded, attribute values can be read using as<T>() where T is the desired value type (LVariant types; double, bool, or LString).

LAttribute* name     = engine.root()->find_attribute("Name");
LAttribute* age      = engine.root()->find_attribute("Age");
LAttribute* is_admin = engine.root()->find_attribute("Is.Admin");

LString name_v    = name->as<LString>();   // `name_v` is "Elliot Alderson"
double age_v      = age->as<double>();     // `age_v` is 28.0
bool is_admin_v   = is_admin->as<bool>();  // `is_admin_v` is true

A default value of type T is returned if the attribute has no value (in the case of an unresolved link) or if the attribute's value isn't of type T.

LString wrong = age->as<LString>();  // `wrong` becomes empty LString

If the value's type might change, consider using as_if<T>() instead which returns nullptr if a proper value can't be deduced.

if (const LString* maybe = attr->as_if<LString>())
    // `attr` is LString; proceed to use `maybe`
else
    // `attr` is NOT LString

Hierarchy and paths

Layers form the hierarchy (see LLayer), and attributes are components of layers. An attribute's path is its name appended to its parent layer path, separated by a forward-slash (/). It can be acquired with path().

// Given `Father/Son/Retired: false` ...

LLayer* son = engine.find_layer("Father/Son");
LAttribute* son_retired = son->find_attribute("Retired");

LString son_retired_path = son_retired->path();  // "Father/Son/Retired"

Linking to other attributes

An attribute declaration can reference another attribute to establish a link. Consider the following:

Logo File: "logo.svg"
Icon/Source: Logo File  // Reference

Instead of expressing its own value, Icon/Source references Logo File.

Resolving links

Attribute links are resolved through resolve_links(). However, instead of manually calling it per attribute, use LEngine::resolve_links() which traverses the whole tree.

Once resolved, Icon/Source is linked to Logo File.

Reading linked attributes

A linked attribute can be read with the same as<T>() call which goes through the link chain to return the value.

LLayer* icon            = engine.find_layer("Icon");
LAttribute* icon_source = icon->find_attribute("Source");

LString icon_source_v   = icon_source->as<LString>();  // Returns "logo.svg"

Link chaining

An attribute that has been linked to can, itself, link to another attribute. This is called link chaining. When reading attribute values, as<T>() traverses the link chain to return the value at the end of the chain. Consider the following:

Theme/Primary: "#36393f"

Window/Fill: Theme/Primary
Window/Body/Fill: Window/Fill

In this example, Window/Body/Fill links to Window/Fill which links to Theme/Primary.

LLayer* body          = engine.find_layer("Window/Body");
LAttribute* body_fill = body->find_attribute("Fill");

LString color         = body_fill->as<LString>();  // Returns "#36393f"

Acquiring dependencies

When a link is established, the target receives a pointer to the newly linked attribute, also known as a dependent. The target can acquire its dependent attributes through dependents().

When calling dependents(), passing true also returns the transitive dependents indirectly linked to the caller; false (the default) returns only the dependents linked directly.

LLayer* theme           = engine.find_layer("Theme");
LAttribute* primary     = theme->find_attribute("Primary");

LAttributes direct      = primary->dependents();
                          // { Window/Fill }

LAttributes indirect    = primary->dependents(true);
                          // { Window/Fill, Window/Body/Fill }

States as conditional values

An attribute can carry multiple different state-controlled values. These are called conditional attributes. Consider the following:

Action: "Go"
    @Yellow: "Slowdown"
    @Red: "Stop"

When reading the value of a conditional attribute, pass the active states in a string list to the as<T>() call.

LAttribute* a = engine.root()->find_attribute("Action");

LString green  = a->as<LString>({"Green"});   // `green` is "Go"
LString yellow = a->as<LString>({"Yellow"});  // `yellow` is "Slowdown"
LString red    = a->as<LString>({"Red"});     // `red` is "Stop"

The main attribute value is returned if no states are provided or if the provided states don't match any known condition.

LString main = a->as<LString>();                 // `main` is "Go"
LString unknown = a->as<LString>({"Unknown"});   // `unknown` is "Go"

States with multiple conditions

as<T>(conditions) returns the most-specific qualifying value, the one whose conditions are a subset of the given ones and that matches the greatest number of them.

Underneath, state values are just child attributes of the attribute they condition, and as<T>() selects among them by calling state(). Calling state() directly is only needed when the underlying state attribute is desired instead of just its value.

Consider the following:

Thickness: 0
    @Desktop: 8
    @Desktop&Max: 0

The intent here is for Thickness to be 0 for any platform other than desktop, 8 when it is desktop but back to 0 if maximized.

LAttribute* t = engine.root()->find_attribute("Thickness");

double mobile  = t->as<double>({"Mobile"});           // `mobile` is 0.0
double desktop = t->as<double>({"Desktop"});          // `desktop` is 8.0
double max     = t->as<double>({"Desktop","Max"});    // `max` is 0.0

double focus   = t->as<double>({"Desktop","Focus"});  // `focus` is 8.0

The focus example shows how extra conditions are ignored; the @Desktop state qualifies because it is a subset of {"Desktop","Focus"}.

Check if an attribute is conditional

An attribute can be checked for conditions by calling has_states().

if (attr->has_states())
    // `attr` is conditional
else
    // `attr` is NOT conditional

Modifying attributes at runtime

An attribute's value can be set at runtime by passing a new value to set_value(). If the attribute is linked, the link is broken first. If the attribute's value is already the passed value, nothing happens.

// Given `Window/Fill: Theme/Primary` from earlier ...

LLayer* window          = engine.find_layer("Window");
LAttribute* window_fill = window->find_attribute("Fill");

LString before      = window_fill->as<LString>();  // Returns "#36393f"
window_fill->set_value("#333c4f");
LString after       = window_fill->as<LString>();  // Returns "#333c4f"

An attribute can be relinked at runtime.

// Consider `New Logo: "logo_new.svg"` and
// given `icon_source` from earlier ...

LAttribute* new_logo = engine.root()->find_attribute("New Logo");

LString before       = icon_source->as<LString>();  // Returns "logo.svg"
icon_source->create_link(new_logo);
LString after        = icon_source->as<LString>();  // Returns "logo_new.svg"

Change notifications

When an attribute's value has changed, it emits notifications in the form of executing registered callbacks. To register a callback for notification, pass it to on_change() which stores the callback and returns an associated connection ID (see LConnectionID). The ID is passed to disconnect_change() later to properly disconnect the notification.

// Given `primary` from earlier ...

LConnectionID connection = window_fill->on_change([]()
    { std::cout << "Window/Fill changed!" << std::endl; });

window_fill->set_value("#00000000");    // `Window/Fill changed!`
window_fill->create_link(primary);      // `Window/Fill changed!` again

Changes propagate to every transitive dependent. If you change the value of primary in the last example, the window_fill notification will continue to fire.

primary->set_value("#f0f0f0");          // `Window/Fill changed!` again

Remember to disconnect the notification when it's no longer needed.

window_fill->disconnect_change(connection);

Member Function Documentation

LAttribute(const LString & name)

Constructs an attribute with the given name.

~LAttribute()

No description available.

template<typename T>
T as(const LStringList & conditions = LStringList())

Returns the attribute's value as the specified type T.

If the attribute has states, conditions is used to select the appropriate state. If the attribute is linked, the value is retrieved from the link attribute.

Returns a default T value when the attribute's value is absent or the wrong type.

template<typename T>
const T * as_if(const LStringList & conditions = LStringList())

Returns a pointer to the attribute's value if it matches type T, otherwise returns nullptr.

This template implements the attribute evaluation priority chain. The value is determined from the following, with highest-priority first:

  • State value if it best qualifies the given conditions
  • Link attribute value if a resolved link is present
  • This attribute's own value

State selection is skipped entirely if the conditions list is empty.

void clone_to(LObject * parent)

Produces a clone of this attribute as a child of the given parent.

This is used by LLayer::finalize() for deep-copying the inherited base layer.

LAttributes dependents(bool transitive = false)

Returns a list of attributes that depend on this attribute.

If transitive is true, includes indirect dependencies.

void disconnect_change(const LConnectionID & connection)

Disconnects the change callback associated with connection.

bool has_states()

Returns true if this attribute has state variants.

LConnectionID on_change(std::function<void ()> callback)

Registers a callback to be invoked when the attribute value changes.

Returns a connection ID that can be used to disconnect the callback.

LString path()

Returns the full path of this attribute.

void set_value(const char * new_value)

Sets the attribute value from a C-string. Wraps new_value in LString and forwards to set_value(const LVariant&).

void set_value(const LVariant & new_value)

Sets the attribute value from a variant.

If linked, the link is broken first.

All transitive dependents are notified of the change.

LAttribute * state(const LStringList & conditions)

Returns the state attribute that best matches the given conditions; nullptr is returned when no match available.

A state attribute wins if all of its conditions are present. Conditions that are present but not specified by the state attribute are ignored, so a caller can pass its full active condition set without knowing the specific ones used by the conditional attribute.

When multiple states qualify, the one matching the most conditions wins. When several qualifying states match the same number of conditions, which one wins is unspecified.

LAttributeMap states(bool include_parent_states = true)

Returns a map of all state attributes.

If include_parent_states is true, includes inherited states. States are merged with the derived attribute's own states winning on collision.

const LVariant & value()

Returns a reference to the underlying variant value. If linked, the value at the end of the chain is returned.

Appearance
Theme
—