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
.lyrfiles. See the language reference for more details. Examples in this document are rooted in the language syntax.
LAttributeis the live runtime object whereasLAttributeNodeis the parsed declaration it is built from. Since a single declaration can name several targets through expansion, one node can produce several live attributes.
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
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"
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.
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.
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"
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"
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 }
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"
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"}.
An attribute can be checked for conditions by calling has_states().
if (attr->has_states())
// `attr` is conditional
else
// `attr` is NOT conditional
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"
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 selection is skipped entirely if the conditions list is empty.
void break_link(bool update = true)
Breaks the link to another attribute. The link attribute's current value is copied before the link is dropped.
If update is true, notifies dependent attributes of the change.
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.
bool create_link(LAttribute * link_attr)
Creates a link to another attribute.
Since this method takes the link attribute directly, the resulting link is already resolved.
Returns false and leaves this attribute unchanged if link_attr is
null or if the link would form a cycle (see in_link_chain()).
bool create_link(const LString & target_path)
Creates an unresolved link using a target's path.
Declaring an attribute reference in .lyr is the normal way to link;
LLoader calls this while building the tree. Direct calls are for
tooling that re-links at runtime.
Attributes need to be able to establish link relationships without those links being resolved immediately because during the loading process, the target attributes may not have loaded yet.
Returns false and leaves this attribute unchanged if target_path is
empty. A target that would form a cycle is rejected at resolution
instead (see LLink::is_cycle).
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.
void disconnect_link_change(const LConnectionID & connection)
Disconnects the link change callback associated with connection.
bool has_states()
Returns true if this attribute has state variants.
bool in_link_chain(const LAttribute * attr)
Returns true if attr is this attribute or any attribute along its
link chain.
Linking an attribute to a target whose chain already contains it would form a cycle, so link creation and resolution reject those targets.
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.
LConnectionID on_link_change(std::function<void ()> callback)
Registers a callback to be invoked when the attribute's link changes (when it's broken or relinked).
Returns a connection ID that can be used to disconnect the callback.
LString path()
Returns the full path of this attribute.
void resolve_links()
Resolves all link references within 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 LLink * link()
Returns the link if this attribute links to another attribute.
Returns nullptr if this attribute is not a link.
const LVariant & value()
Returns a reference to the underlying variant value. If linked, the value at the end of the chain is returned.