User Manual

Class uiink::Data

Data objects are used to pass information to and from the GUI.

For an overview of the Data API, see: http://uiink.com/docs/data_api/

This class is a lightweight wrapper around the C 'inkd' type with automatic reference counting.

Methods

Data(inkd d)

Wrap an existing C data object pointer.

The wrapper will take care of reference counting. There is no need to call inkd_hold/inkd_release.

Examples

inkd root_c = ink_source(0, ink_ident_from_str("root"));

uiink::Data root_cpp(root_c); // no reference counting needed

Data(inkd d, bool hold)

Wrap an existing C data object pointer with optional initial reference count.

This constructior allows for avoiding an additional reference count. (The Data object will still be released in the destructor.)

Examples

inkd root_c = ink_source_hold(0, ink_ident_from_str("root"));

// Since the inkd was acquired from a c function ending in `_hold` an
// additional reference count isn't needed.
uiink::Data root_cpp(root_c, false);

Data(const Data& that)
Data(Data&& that)
~Data()
Data& operator=(const Data& that)

Copy a reference to a Data object with reference counting.

Both Data instances with refer to the same Data object.

Examples

using namespace uiink;
Data a = Ink().source("main");
Data b = a;
a.set_int("answer", 42);
TEST_ASSERT(b.get_int("answer") == 42); // modifying a also modifies b

Data& operator=(Data&& that)

Move a reference to a Data object.

This operator avoids reference counting overhead.

bool operator==(const Data& that)

Test if two Data instances refer to the same underlying Data object.

Examples

using namespace uiink;
Data a = Ink().source("main");
Data b = Ink().source("main");
Data c = b.append("entries");
TEST_ASSERT(a == b);
TEST_ASSERT_FALSE(a == c);

operator bool()

Evaluate true if this is a valid Data object.

Some methods that return a Data object may fail. Test for failure by evaluating the truthyness of the returned Data instance.

Examples

using namespace uiink;
Data root = Ink().source("main");
TEST_ASSERT(root); // evaluates to true

Data doesnt_exist = root.query("entries").match_string("label", "doesn't exist").maybe_get();
TEST_ASSERT_FALSE(doesnt_exist); // evaluates to false

Query query(Ident list_field)

Find an existing child that matches a query, or create a new one if it doesn't exist.

See the Query API overview for more information.

This method is a convenience function for writing queries on a single line. You may call Query::Query() directly for the same result.

Examples

using namespace uiink;
Data root = Ink().source("main");

Data first = root.query("entries").match_string("label", "Hello World!").get();
// For the first time we query, a new entry was created:
TEST_ASSERT(first.get_std_string("label") == "Hello World!");

Data second = root.query("entries").match_string("label", "Hello World!").get();
// The second time we query for the same match, the same entry is returned:
TEST_ASSERT(first == second);

void rebuild(Func f)

Remove any Data objects that aren't touched during the duration of a function call.

This is useful for structuring code in a manner that is similar to using an immediate mode API.

When the rebuild ends, all child Data objects will be removed unless:

For examples of how to use this API, see: http://uiink.com/articles/data-driven-immediate-mode-ui/

This method is intended to be convenient to use with a C++11 lambda. If you're not using C++11, or you just don't like lambdas, use DataRebuild instead.

Examples

using namespace uiink;
Data root = Ink().source("main");

root.rebuild([&] {
    Data entry = root.append("entries");
    entry.set_string("label", "Hello World!");
});
TEST_ASSERT(root.get_child("entries", 0).get_std_string("label") == "Hello World!");

root.rebuild([&] {
    Data entry = root.query("entries").match_string("label", "Hello World!").maybe_get();
});
TEST_ASSERT(root.get_child("entries", 0)); // entry was retained

root.rebuild([&] {
    // don't touch the entry this time
});
TEST_ASSERT_FALSE(root.get_child("entries", 0)); // entry was removed

void rebuild(Ident field, Func f)

Remove any Data objects that aren't touched during the duration of a function call, limited to a single list field.

This overload behaves the same as Data::rebuild(Func), except it only impacts the children in a single list field.

Examples

using namespace uiink;
Data root = Ink().source("main");

root.append("entries").set_string("label", "Hello World!");
root.append("untouched").set_string("label", "This won't be removed");

root.rebuild("entries", [&] {
    // don't touch anything
});
// entry in the field "entries" was removed
TEST_ASSERT_FALSE(root.get_child("entries", 0));
// entry in the field "untouched" was retained
TEST_ASSERT(root.get_child("untouched", 0).get_std_string("label") == "This won't be removed");

void set_float(Ident field, float v)

Set a field with a floating point value.

void set_int(Ident field, int v)

Set a field with an integer value.

void set_bool(Ident field, bool v)

Set a field with a boolean value.

void set_string(Ident field, ink_str value)

Set a field with a ink_str (c api) value.

void set_string(Ident field, const String& value)

Set a field with a uiink::String value.

Use this version of set_string to avoid copying the string contents. This may be useful to optimize code that sets a large number of strings, but in most cases it'll be more convienent to use the std::string or char* overloads.

void set_string(Ident field, const std::string& v)

Set a field with a std::string value.

A copy of the string will be made; you do not need to manage its lifetime.

void set_string(Ident field, const char* v)

Set a field with a null terminated c string ( char*) value.

A copy of the string will be made; you do not need to manage its lifetime.

void set_ident(Ident field, Ident v)

Set a field with an Ident value.

void set_image(Ident field, ink_image* v)

Set a field with an image value.

void set_color(Ident field, uint32_t v)

Set a field with a color value.

The color value should be encoded in RGBA format with A being the least significant byte. For example, partially transparent green could be written as 0x00FF00AA.

float get_float(Ident field)

Get a float value.

If the given field does not contain a floating point value, 0.0f will be returned.

int get_int(Ident field)

Get an int value.

If the given field does not contain an integer value, 0 will be returned.

bool get_bool(Ident field)

Get an bool value.

If the given field does not contain a boolean value, false will be returned.

String get_string(Ident field)

Get an String value.

If the given field does not contain a string value, an empty (zero-length) String will be returned.

This method does not copy the contents of the string.

std::string get_std_string(Ident field)

Get an std::string value.

If the given field does not contain a string value, an empty (zero-length) std::string will be returned.

This method makes a copy of the string. To read the string contents without making a copy, use get_string() or get_c_string().

const char* get_c_string(Ident field)

Get a c string (null terminated char*) value.

If the given field does not contain a string value, an empty (zero-length) string will be returned.

This method does not copy the contents of the string. The pointer is valid as long ink has a reference to the string internally. In practice, this means that you can use the pointer until one of the following happens:

A) A different value is set to the same field

B) Ink.step() occures. (A different value may be assigned from the user at this point.)

Be aware that in a multi-threaded application either of the above may happen in a separate thread. If you are modifying Data objects from multiple threads consider using get_string() instead. (The String class will hold a reference to the string data, ensuring that it is valid even if another thread modifies the Data object.)

Ident get_ident(Ident field)

Get an Ident value.

If the given field does not contain an identifier value, an null identifier will be returned.

struct ink_image* get_image(Ident field)

Get an image value.

If the given field does not contain an image value, a null pointer will be returned.

uint32_t get_color(Ident field)

Get a color value.

If the given field does not contain a color value, transparent black ( 0x00000000) will be returned.

See set_color() for information on how the color is encoded.

Data append(Ident list_field)

Create a new Data object in a list.

If this is called inside of a rebuild operation, this newly created Data object will not be removed when the rebuild ends.

Examples

using namespace uiink;
Data root = Ink().source("root");

Data first = root.append("entries");
first.set_string("label", "Hello World");

Data second = root.append("entries");
second.set_string("label", "Second item in entries list.");

void remove()

Remove this Data object from its containing list.

If notify_on_remove() has been previously called on this Data object, an INK_DATA_EVENT_REMOVE event will be created.

Once the Data object has been removed, methods getting and setting value fields may still be called. However, all methods relating to parent/child relationships become no-ops.

The Data object will be deleted once there are no more references to it.

If you find yourself writing a lot of code to determine which Data objects are no longer needed, the rebuild() API may be more convenient.

Data get_parent()

Return the parent of this Data object.

This will return a null Data object if this Data object has been removed, or if it is the root of a Source Node.

Examples

using namespace uiink;
Data root = Ink().source("root");
Data entry = root.append("entries");
TEST_ASSERT(entry.get_parent() == root);

Ident get_parent_field()

Return the name of the field in the parent in which this Data object is stored.

This matches the name of the field passed to Data::append() when the Data object was created. For Data objects that represent a Source node this will return a null Ident.

Examples

using namespace uiink;
Data root = Ink().source("root");
Data entry = root.append("entries");
TEST_ASSERT(entry.get_parent_field() == Ident("entries"));
TEST_ASSERT(root.get_parent_field() == Ident());

int get_index()

Return the position of this Data object in its containing list.

Indexes start at zero

Examples

using namespace uiink;
Data root = Ink().source("root");
Data first = root.append("entries");
Data second = root.append("entries");
TEST_ASSERT(first.get_index() == 0);
TEST_ASSERT(second.get_index() == 1);

Data get_child(Ident list_field, int index)

Return a child Data object this is at a give position in a list.

Indexes start at zero.

If 'index' is out of bounds a null Data will be returned.

Examples

using namespace uiink;
Data root = Ink().source("root");
Data first = root.append("entries");
Data second = root.append("entries");
TEST_ASSERT(root.get_child("entries", 0) == first);
TEST_ASSERT(root.get_child("entries", 1) == second);
TEST_ASSERT_FALSE(root.get_child("entries", 2)); // out of bounds

void move(int new_index)

Move this Data object to a new position in its list.

Note that if you find yourself writing a significant amount of code to manage the order of Data objects, consider moving that responsibility to a sort node.

Examples

using namespace uiink;
Data root = Ink().source("root");
Data first = root.append("entries");
Data second = root.append("entries");
second.move(0);
TEST_ASSERT(first.get_index() == 1); // 'first' was moved to index 1
TEST_ASSERT(second.get_index() == 0);

void set_variant(Ident variant)

Set the variant name for this Data object.

This is useful when a single list has multiple kinds of objects in it that need to be mapped to different templates.

For details on how to setup variant templates in Quill, see the documentation page for Template Variants.

Examples

using namespace uiink;
Data root = Ink().source("root");

// The first entry will use the default template variant
Data first = root.append("entries");

// The second entry will use the "my variant" template variant
Data second = root.append("entries");
second.set_variant("my variant");

void impulse(Ident field)

Initiate an impulse.

This is useful when an Instanced Source Node has an impulse as an input. (... which is to say, it isn't often useful, but when you need it you need it.)

void trigger(Ident field)

Initiate a trigger.

This is useful for notifying the GUI of an event that may not have a value associated with, but the GUI may want to acknowledge with an animation.

void clear(Ident field)

Return a field to its default state.

If this is a value field, it will be reset to the default for that value type. If this is a list field, all of the entries in the list will be removed.

Examples

using namespace uiink;
Data root = Ink().source("main");

root.set_string("label", "Hello!");
TEST_ASSERT(root.get_std_string("label") == "Hello!");
root.clear("label");
TEST_ASSERT(root.get_std_string("label") == "");

root.append("entries");
TEST_ASSERT(root.get_child("entries", 0)); // added a single entry
root.clear("entries");
TEST_ASSERT_FALSE(root.get_child("entries", 0)); // entry has been removed

void clear_all()

Clear all of the fields on this data object.

This is equivalent to calling clear() on each field.

Examples

using namespace uiink;
Data root = Ink().source("main");

root.set_string("label", "Hello!");
root.append("entries");

TEST_ASSERT(root.get_std_string("label") == "Hello!");
TEST_ASSERT(root.get_child("entries", 0)); // added a single entry

root.clear_all();

TEST_ASSERT(root.get_std_string("label") == "");
TEST_ASSERT_FALSE(root.get_child("entries", 0)); // entry has been removed

void notify_on_remove()

Cause a INK_DATA_EVENT_REMOVE event to be created when this Data object is removed.

This can be useful if there are application data structures that need to be cleaned up when the associated Data object is removed.

void on_field_change(Ident field, std::function< void( DataEvent)> callback)

Set a callback for when a field has an event.

When a value is changed, an impulse/trigger is fired from the GUI, or this data object is removed, the given callback will be called. The callback is not triggered by value changes using the data API.

Adding a change callback implicitly calls notify_on_remove().

This method requires C++11 and standard library containers. If you do not wish to use either, use the Listener class instead.

Examples

using namespace uiink;
Data root = Ink().source("app");
root.set_string("message", "a message");
root.on_field_change("message", [](DataEvent ev) {
    std::cout << "message changed by user to: " << ev.data.get_std_string("message") << std::endl;
});

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

Set a callback for when any field on this data object has an event.

Like on_field_change() but the callback is triggered when any field on this data object has an event.

Examples

using namespace uiink;
Data root = Ink().source("app");
root.on_change([](DataEvent ev) {
    if (ev.type == INK_DATA_EVENT_CHANGE) {
        if (ev.field == "message") {
            std::cout << "message changed by user to:" << ev.data.get_std_string("message") << std::endl;
        } else {
            std::cout << "some other field changed" << std::endl;
        }
    }
});

void set_tag(Ident tag_name, void* tag_value)

Store a void pointer in the Data object.

This value is only accessible from the programming API. It is not used by the ink runtime itself.

If this value points to an object that should be cleaned up when the Data object is removed, consider creating a Listener and calling notify_on_remove().

Note that each language binding has its own namespace for tags, (with the exception of C and C++.) A tag set in one language will not conflict with a tag set in another language binding.

Examples

using namespace uiink;
Data root = Ink().source("main");
static int some_memory = 42;
root.set_tag("tag", &some_memory);
TEST_ASSERT(static_cast<int*>(root.get_tag("tag")) == &some_memory);

void* get_tag(Ident tag_name)

Retrieve a void pointer previously set with set_tag().

If set_tag() has not been called with this tag name, a null pointer will be returned.

Examples

using namespace uiink;
Data root = Ink().source("main");
static int some_memory = 42;
root.set_tag("tag", &some_memory);
TEST_ASSERT(static_cast<int*>(root.get_tag("tag")) == &some_memory);
TEST_ASSERT(root.get_tag("unset tag") == nullptr);

Data find_by_tag(Ident field, Ident tag_name, void* tag_value)

Find a child of this Data object that has a given tag value.

inkd get_inkd()

Access the wrapped C API 'inkd' object.

This is useful for integrating with code that uses the C API.

Ink get_ink_instance()

Returns the Ink instance with which this data object is associated.

Static Methods

static Data make_null()

Create an 'null' data object.

This data object will evaluate to false. Any operation on it will have no effect.