blob: 857acb3dc92ba408c8a30490b05fb9deb05959d5 [file] [log] [blame]
#include <rive/file.hpp>
#include <rive/node.hpp>
#include <rive/shapes/rectangle.hpp>
#include <rive/shapes/shape.hpp>
#include <utils/no_op_renderer.hpp>
#include <rive/shapes/paint/solid_color.hpp>
#include <rive/shapes/paint/color.hpp>
#include <rive/shapes/paint/fill.hpp>
#include <rive/shapes/paint/solid_color.hpp>
#include <rive/text/text_value_run.hpp>
#include <rive/custom_property_boolean.hpp>
#include <rive/custom_property_number.hpp>
#include <rive/custom_property_string.hpp>
#include <rive/custom_property_trigger.hpp>
#include <rive/constraints/follow_path_constraint.hpp>
#include <rive/viewmodel/runtime/viewmodel_runtime.hpp>
#include <rive/viewmodel/runtime/viewmodel_instance_runtime.hpp>
#include <rive/viewmodel/runtime/viewmodel_instance_value_runtime.hpp>
#include <rive/data_bind/data_values/data_type.hpp>
#include <rive/viewmodel/viewmodel_instance_number.hpp>
#include <rive/viewmodel/viewmodel_instance_color.hpp>
#include <rive/viewmodel/viewmodel_instance_string.hpp>
#include <rive/viewmodel/viewmodel_instance_boolean.hpp>
#include <rive/viewmodel/viewmodel_instance_enum.hpp>
#include <rive/viewmodel/viewmodel_instance_trigger.hpp>
#include <rive/viewmodel/viewmodel_instance_list.hpp>
#include <rive/viewmodel/viewmodel_instance_list_item.hpp>
#include "rive/animation/state_machine_instance.hpp"
#include "rive/nested_artboard.hpp"
#include "utils/serializing_factory.hpp"
#include "rive_file_reader.hpp"
#include <catch.hpp>
#include <cstdio>
using namespace rive;
TEST_CASE("artboard with bound properties", "[data binding]")
{
auto file = ReadRiveFile("assets/data_binding_test.riv");
auto artboard = file->artboard("artboard-1")->instance();
REQUIRE(artboard != nullptr);
auto viewModelInstance =
file->createDefaultViewModelInstance(artboard.get());
REQUIRE(viewModelInstance != nullptr);
artboard->bindViewModelInstance(viewModelInstance);
artboard->advance(0.0f);
REQUIRE(artboard->find<rive::Rectangle>("bound_rect") != nullptr);
auto rectMapped = artboard->find<rive::Rectangle>("bound_rect");
REQUIRE(rectMapped->width() == 100.0f);
REQUIRE(artboard->find<rive::Shape>("bound_rect_shape") != nullptr);
auto shapeMapped = artboard->find<rive::Shape>("bound_rect_shape");
// Rotation has a system converter applied to it
REQUIRE(shapeMapped->rotation() == Approx(1.5708f));
REQUIRE(shapeMapped->children()[1]->is<rive::Fill>());
rive::Fill* rectMappadFill = shapeMapped->children()[1]->as<rive::Fill>();
REQUIRE(rectMappadFill->paint()->is<rive::SolidColor>());
REQUIRE(rectMappadFill->paint()->as<rive::SolidColor>()->colorValue() ==
rive::colorARGB(255, 255, 0, 0));
REQUIRE(artboard->find<rive::TextValueRun>("bound_text_run") != nullptr);
auto textRunMapped = artboard->find<rive::TextValueRun>("bound_text_run");
REQUIRE(textRunMapped->text() == "bound text");
REQUIRE(artboard->find<rive::FollowPathConstraint>("") != nullptr);
auto followPathConstraint = artboard->find<rive::FollowPathConstraint>("");
REQUIRE(followPathConstraint->orient() == false);
// View model properties
// Number
auto widthProperty = viewModelInstance->propertyValue("width");
REQUIRE(widthProperty != nullptr);
REQUIRE(widthProperty->is<rive::ViewModelInstanceNumber>());
// Number with comverter
auto rotationProperty = viewModelInstance->propertyValue("rotation");
REQUIRE(rotationProperty != nullptr);
REQUIRE(rotationProperty->is<rive::ViewModelInstanceNumber>());
// Color
auto colorProperty = viewModelInstance->propertyValue("color");
REQUIRE(colorProperty != nullptr);
REQUIRE(colorProperty->is<rive::ViewModelInstanceColor>());
// String
auto textProperty = viewModelInstance->propertyValue("text");
REQUIRE(textProperty != nullptr);
REQUIRE(textProperty->is<rive::ViewModelInstanceString>());
// Boolean
auto orientProperty = viewModelInstance->propertyValue("orient");
REQUIRE(orientProperty != nullptr);
REQUIRE(orientProperty->is<rive::ViewModelInstanceBoolean>());
// Update view model values
widthProperty->as<rive::ViewModelInstanceNumber>()->propertyValue(200.0f);
rotationProperty->as<rive::ViewModelInstanceNumber>()->propertyValue(
180.0f);
colorProperty->as<rive::ViewModelInstanceColor>()->propertyValue(
rive::colorARGB(255, 0, 255, 0));
textProperty->as<rive::ViewModelInstanceString>()->propertyValue(
"New text");
orientProperty->as<rive::ViewModelInstanceBoolean>()->propertyValue(true);
// Advance artboard so data binds apply
artboard->advance(0.0f);
// Validate new properties
REQUIRE(rectMapped->width() == 200.0f);
REQUIRE(shapeMapped->rotation() == Approx(3.14159f));
REQUIRE(rectMappadFill->paint()->as<rive::SolidColor>()->colorValue() ==
rive::colorARGB(255, 0, 255, 0));
REQUIRE(textRunMapped->text() == "New text");
REQUIRE(followPathConstraint->orient() == true);
}
TEST_CASE("state machine led by enums and triggers", "[data binding]")
{
auto file = ReadRiveFile("assets/data_binding_test.riv");
auto artboard = file->artboard("artboard-2")->instance();
REQUIRE(artboard != nullptr);
auto viewModelInstance =
file->createDefaultViewModelInstance(artboard.get());
REQUIRE(viewModelInstance != nullptr);
auto machine = artboard->defaultStateMachine();
machine->bindViewModelInstance(viewModelInstance);
REQUIRE(machine != nullptr);
//
REQUIRE(artboard->find<rive::Shape>("color_rectangle") != nullptr);
auto shapeMapped = artboard->find<rive::Shape>("color_rectangle");
REQUIRE(shapeMapped->children()[1]->is<rive::Fill>());
REQUIRE(shapeMapped->x() == 250);
REQUIRE(shapeMapped->y() == 250);
rive::Fill* rectMappadFill = shapeMapped->children()[1]->as<rive::Fill>();
REQUIRE(rectMappadFill->paint()->is<rive::SolidColor>());
REQUIRE(rectMappadFill->paint()->as<rive::SolidColor>()->colorValue() ==
rive::colorARGB(255, 116, 116, 116));
// View model properties
// Enum
auto stateProperty = viewModelInstance->propertyValue("state");
REQUIRE(stateProperty != nullptr);
REQUIRE(stateProperty->is<rive::ViewModelInstanceEnum>());
// Trigger
auto triggerProperty = viewModelInstance->propertyValue("trigger-prop");
REQUIRE(triggerProperty != nullptr);
REQUIRE(triggerProperty->is<rive::ViewModelInstanceTrigger>());
// Advance state machine
machine->advanceAndApply(0.0f);
REQUIRE(rectMappadFill->paint()->as<rive::SolidColor>()->colorValue() ==
rive::colorARGB(255, 255, 0, 0));
// Update view model properties
// Update enum by index
stateProperty->as<rive::ViewModelInstanceEnum>()->value(1);
// Advance state machine
machine->advanceAndApply(0.0f);
// Validate values have updated
REQUIRE(rectMappadFill->paint()->as<rive::SolidColor>()->colorValue() ==
rive::colorARGB(255, 0, 255, 0));
REQUIRE(shapeMapped->x() == 150);
REQUIRE(shapeMapped->y() == 250);
// Update view model properties
// Update enum by name
stateProperty->as<rive::ViewModelInstanceEnum>()->value("state-blue");
// Update trigger
triggerProperty->as<rive::ViewModelInstanceTrigger>()->propertyValue(1);
// Advance state machine
machine->advanceAndApply(0.0f);
// Validate values have updated
REQUIRE(rectMappadFill->paint()->as<rive::SolidColor>()->colorValue() ==
rive::colorARGB(255, 0, 0, 255));
REQUIRE(shapeMapped->x() == 350);
REQUIRE(shapeMapped->y() == 250);
// Update trigger
triggerProperty->as<rive::ViewModelInstanceTrigger>()->propertyValue(1);
// Advance state machine
machine->advanceAndApply(0.0f);
REQUIRE(shapeMapped->x() == 350);
REQUIRE(shapeMapped->y() == 350);
}
TEST_CASE("calculate and to string converters with numbers", "[data binding]")
{
auto file = ReadRiveFile("assets/data_binding_test.riv");
auto artboard = file->artboard("artboard-3")->instance();
REQUIRE(artboard != nullptr);
auto viewModelInstance =
file->createDefaultViewModelInstance(artboard.get());
REQUIRE(viewModelInstance != nullptr);
auto machine = artboard->defaultStateMachine();
machine->bindViewModelInstance(viewModelInstance);
REQUIRE(machine != nullptr);
// Bound property with Calculate Converter (multiply by 2)
REQUIRE(artboard->find<rive::CustomPropertyNumber>("num_prop") != nullptr);
auto customPropertyNumber =
artboard->find<rive::CustomPropertyNumber>("num_prop");
REQUIRE(customPropertyNumber->propertyValue() == 0.0f);
// Bound property with Group Converter with:
// - Calculate (divide by 3)
// - Convert to string (round decimals and remove trailing zeros)
REQUIRE(artboard->find<rive::TextValueRun>("text_run_bound") != nullptr);
auto textRunBound = artboard->find<rive::TextValueRun>("text_run_bound");
// View model properties
// Number with initial value set to 17
auto numProperty = viewModelInstance->propertyValue("num1");
REQUIRE(numProperty != nullptr);
REQUIRE(numProperty->is<rive::ViewModelInstanceNumber>());
REQUIRE(textRunBound->text() == "text");
// Advance state machine
machine->advanceAndApply(0.0f);
// Test Calculate Converter (multiply by 2)
REQUIRE(customPropertyNumber->propertyValue() == 34.0f);
REQUIRE(textRunBound->text() == "6");
// Update value to -10.0f
numProperty->as<rive::ViewModelInstanceNumber>()->propertyValue(-10.0f);
// Advance state machine
machine->advanceAndApply(0.0f);
// Test Calculate Converter (multiply by 2)
REQUIRE(customPropertyNumber->propertyValue() == -20.0f);
REQUIRE(textRunBound->text() == "-3");
}
TEST_CASE("trim string converter", "[data binding]")
{
auto file = ReadRiveFile("assets/data_binding_test.riv");
auto artboard = file->artboard("artboard-3")->instance();
REQUIRE(artboard != nullptr);
auto viewModelInstance =
file->createDefaultViewModelInstance(artboard.get());
REQUIRE(viewModelInstance != nullptr);
auto machine = artboard->defaultStateMachine();
machine->bindViewModelInstance(viewModelInstance);
REQUIRE(machine != nullptr);
REQUIRE(artboard->find<rive::TextValueRun>("second_text_run_trim_both") !=
nullptr);
auto trimmedBothTextRunBound =
artboard->find<rive::TextValueRun>("second_text_run_trim_both");
REQUIRE(artboard->find<rive::TextValueRun>("second_text_run_trim_start") !=
nullptr);
auto trimmedStartTextRunBound =
artboard->find<rive::TextValueRun>("second_text_run_trim_start");
REQUIRE(artboard->find<rive::TextValueRun>("second_text_run_trim_end") !=
nullptr);
auto trimmedEndTextRunBound =
artboard->find<rive::TextValueRun>("second_text_run_trim_end");
REQUIRE(artboard->find<rive::TextValueRun>("second_text_run_no_trim") !=
nullptr);
auto notTrimmedTextRunBound =
artboard->find<rive::TextValueRun>("second_text_run_no_trim");
// View model properties
// String with initial value " abc "
auto stringProperty = viewModelInstance->propertyValue("text");
REQUIRE(stringProperty != nullptr);
REQUIRE(stringProperty->is<rive::ViewModelInstanceString>());
REQUIRE(notTrimmedTextRunBound->text() == "text");
REQUIRE(trimmedBothTextRunBound->text() == "text");
REQUIRE(trimmedStartTextRunBound->text() == "text");
REQUIRE(trimmedEndTextRunBound->text() == "text");
// Advance state machine
machine->advanceAndApply(0.0f);
REQUIRE(trimmedBothTextRunBound->text() == "abc");
REQUIRE(notTrimmedTextRunBound->text() == " abc ");
REQUIRE(trimmedStartTextRunBound->text() == "abc ");
REQUIRE(trimmedEndTextRunBound->text() == " abc");
// Update value to "a b c "
stringProperty->as<rive::ViewModelInstanceString>()->propertyValue(
"a b c ");
// Advance state machine
machine->advanceAndApply(0.0f);
REQUIRE(notTrimmedTextRunBound->text() == "a b c ");
REQUIRE(trimmedBothTextRunBound->text() == "a b c");
REQUIRE(trimmedStartTextRunBound->text() == "a b c ");
REQUIRE(trimmedEndTextRunBound->text() == "a b c");
}
TEST_CASE("To string converter with color formatters", "[data binding]")
{
auto file = ReadRiveFile("assets/data_binding_test.riv");
auto artboard = file->artboard("artboard-4")->instance();
REQUIRE(artboard != nullptr);
auto viewModelInstance =
file->createDefaultViewModelInstance(artboard.get());
REQUIRE(viewModelInstance != nullptr);
auto machine = artboard->defaultStateMachine();
machine->bindViewModelInstance(viewModelInstance);
REQUIRE(machine != nullptr);
REQUIRE(artboard->find<rive::TextValueRun>("RGBA_formatted_color_run") !=
nullptr);
auto RGBATextRunBound =
artboard->find<rive::TextValueRun>("RGBA_formatted_color_run");
REQUIRE(artboard->find<rive::TextValueRun>("rgba_formatted_color_run") !=
nullptr);
auto rgbaTextRunBound =
artboard->find<rive::TextValueRun>("rgba_formatted_color_run");
REQUIRE(artboard->find<rive::TextValueRun>("hls_formatted_color_run") !=
nullptr);
auto hlsTextRunBound =
artboard->find<rive::TextValueRun>("hls_formatted_color_run");
REQUIRE(artboard->find<rive::TextValueRun>("escaped_characters_run") !=
nullptr);
auto escapedTextRunBound =
artboard->find<rive::TextValueRun>("escaped_characters_run");
// View model properties
// color with initial value "red 30, green 90, blue 200, alpha 255"
auto colorProperty = viewModelInstance->propertyValue("col");
REQUIRE(colorProperty != nullptr);
REQUIRE(colorProperty->is<rive::ViewModelInstanceColor>());
REQUIRE(RGBATextRunBound->text() == "text");
REQUIRE(rgbaTextRunBound->text() == "text");
REQUIRE(hlsTextRunBound->text() == "text");
REQUIRE(escapedTextRunBound->text() == "text");
// // Advance state machine
machine->advanceAndApply(0.0f);
REQUIRE(RGBATextRunBound->text() ==
"color: {red: 1E, green: 5A, blue: C8, alpha: FF}");
REQUIRE(rgbaTextRunBound->text() ==
"color: {red: 30, green: 90, blue: 200, alpha: 255}");
REQUIRE(hlsTextRunBound->text() ==
"color: {hue: 219, luminance: 45, saturation: 74}");
REQUIRE(escapedTextRunBound->text() == "%r %g %b %a \\a");
// // Update value to "red 200, green 100, blue 50, alpha 100"
colorProperty->as<rive::ViewModelInstanceColor>()->propertyValue(
rive::colorARGB(100, 200, 100, 50));
// // Advance state machine
machine->advanceAndApply(0.0f);
REQUIRE(RGBATextRunBound->text() ==
"color: {red: C8, green: 64, blue: 32, alpha: 64}");
REQUIRE(rgbaTextRunBound->text() ==
"color: {red: 200, green: 100, blue: 50, alpha: 100}");
REQUIRE(hlsTextRunBound->text() ==
"color: {hue: 20, luminance: 49, saturation: 60}");
REQUIRE(escapedTextRunBound->text() == "%r %g %b %a \\a");
// // Update value to "red 0, green 10, blue 16, alpha 100"
colorProperty->as<rive::ViewModelInstanceColor>()->propertyValue(
rive::colorARGB(100, 0, 10, 15));
// // Advance state machine
machine->advanceAndApply(0.0f);
REQUIRE(RGBATextRunBound->text() ==
"color: {red: 00, green: 0A, blue: 0F, alpha: 64}");
REQUIRE(rgbaTextRunBound->text() ==
"color: {red: 0, green: 10, blue: 15, alpha: 100}");
REQUIRE(hlsTextRunBound->text() ==
"color: {hue: 200, luminance: 3, saturation: 100}");
REQUIRE(escapedTextRunBound->text() == "%r %g %b %a \\a");
}
struct FormulaResult
{
std::string runName;
std::string result;
// Constructor
FormulaResult(const std::string& name, const std::string& res) :
runName(name), result(res)
{}
};
// TEST_CASE("Formula converter", "[data binding]")
// {
// auto file = ReadRiveFile("assets/data_binding_test.riv");
// auto artboard = file->artboard("artboard-5")->instance();
// REQUIRE(artboard != nullptr);
// auto viewModelInstance =
// file->createDefaultViewModelInstance(artboard.get());
// REQUIRE(viewModelInstance != nullptr);
// auto machine = artboard->defaultStateMachine();
// machine->bindViewModelInstance(viewModelInstance);
// REQUIRE(machine != nullptr);
// // Advance state machine
// machine->advanceAndApply(0.0f);
// // Initial input is 1.1
// std::vector<FormulaResult> results = {
// {"result-run-1", "11.1"}, // {Input} + 10.0
// {"result-run-2", "0.11"}, // {Input} / 10.0
// {"result-run-3", "-8.9"}, // {Input} - 10.0
// {"result-run-4", "11"}, // {Input} * 10.0
// {"result-run-5", "0.2"}, // {Input} % 0.9
// {"result-run-6", "0.891207"}, // sin({Input})
// {"result-run-7", "0.453596"}, // cos({Input})
// {"result-run-8", "1.96476"}, // tan({Input})
// {"result-run-9", "1.460573"}, // acos({Input}/10.0)
// {"result-run-10", "0.110223"}, // asin({Input}/10.0)
// {"result-run-11", "0.10956"}, // atan({Input}/10.0)
// {"result-run-12", "0.03665"}, // atan2({Input}/10.0,3.0)
// {"result-run-13", "1"}, // max(-1.0,{Input}/10.0,1.0)
// {"result-run-14", "-1.1"}, // min(-1.0,-1.0*{Input},1.0)
// {"result-run-15", "1"}, // round({Input})
// {"result-run-16", "2"}, // ceil({Input})
// {"result-run-17", "1"}, // floor({Input})
// {"result-run-18", "1.048809"}, // sqrt({Input})
// {"result-run-19", "1.21"}, // pow({Input},2.0)
// {"result-run-20", "3.004166"}, // exp({Input})
// {"result-run-21", "0.09531"}, // log({Input})
// {"result-run-22",
// "1.546404"}, // max(-1.0*cos({Input})+min(2.0,5.0),-3.0)
// {"result-run-23",
// "1"}, // max(min(max(floor({Input}),-10.0),10.0),-10.0)
// {"result-run-24",
// "-9565.368164"}, //
// cos(sin(tan({Input})))+exp(10.0)/log(0.1)
// {"result-run-25", "1.11"}, // {Input}*{Input}+{Input}/{Input}-{Input}
// {"result-run-26",
// "1.557408"}, // (sin(1.0)/cos(1.0))*(((exp(log(1.0)))))
// {"result-run-27", "0.910958"}, // pow(sin(1.0),cos(1.0))
// {"result-run-28", "0.1"}, // {Input}%round({Input})
// {"result-run-29", "14.993111"},
// // 4.0/{Input}*sqrt(17.0+max(-2.0,0.0))
// {"result-run-30", "1"}, //
// ceil(max({Input}*2.0/10.0,{Input}/10.0*2.0))
// };
// for (auto& formulaResult : results)
// {
// REQUIRE(artboard->find<rive::TextValueRun>(formulaResult.runName) !=
// nullptr);
// auto formulaTextRun =
// artboard->find<rive::TextValueRun>(formulaResult.runName);
// REQUIRE(formulaTextRun->text() == formulaResult.result);
// }
// }
// TODO: revisit this test when data binding in design mode is merged
// TEST_CASE("Time based interpolator", "[data binding]")
// {
// auto file = ReadRiveFile("assets/data_binding_test_2.riv");
// auto artboard = file->artboard("artboard-1")->instance();
// REQUIRE(artboard != nullptr);
// auto viewModelInstance =
// file->createDefaultViewModelInstance(artboard.get());
// REQUIRE(viewModelInstance != nullptr);
// auto machine = artboard->defaultStateMachine();
// machine->bindViewModelInstance(viewModelInstance);
// REQUIRE(machine != nullptr);
// // Advance state machine
// // artboard->advance(0);
// machine->advanceAndApply(0.0f);
// REQUIRE(artboard->find<rive::Shape>("rect") != nullptr);
// auto shapeRect = artboard->find<rive::Shape>("rect");
// REQUIRE(shapeRect->x() == 0);
// // View model properties
// // Number
// auto numProperty = viewModelInstance->propertyValue("num");
// REQUIRE(numProperty != nullptr);
// REQUIRE(numProperty->is<rive::ViewModelInstanceNumber>());
// numProperty->as<rive::ViewModelInstanceNumber>()->propertyValue(100.0f);
// fprintf(stderr, "==> machine advance\n");
// machine->advanceAndApply(0.0f);
// // Advance state machine
// fprintf(stderr, "==> machine advance\n");
// machine->advanceAndApply(0.25f);
// fprintf(stderr, "==> machine advance\n");
// machine->advanceAndApply(0.25f);
// fprintf(stderr, "shapeRect->x(): %f\n", shapeRect->x());
// REQUIRE(shapeRect->x() == 25.0f);
// machine->advanceAndApply(0.25f);
// fprintf(stderr, "shapeRect->x(): %f\n", shapeRect->x());
// REQUIRE(shapeRect->x() == 50.0f);
// }
TEST_CASE("Range Mapper", "[data binding]")
{
auto file = ReadRiveFile("assets/data_binding_test_2.riv");
auto artboard = file->artboard("artboard-2")->instance();
REQUIRE(artboard != nullptr);
auto viewModelInstance =
file->createDefaultViewModelInstance(artboard.get());
REQUIRE(viewModelInstance != nullptr);
auto machine = artboard->defaultStateMachine();
machine->bindViewModelInstance(viewModelInstance);
REQUIRE(machine != nullptr);
// Advance state machine
machine->advanceAndApply(0.0f);
// Range mapper [2 - 3]
REQUIRE(artboard->find<rive::CustomPropertyNumber>("mapped-range-1") !=
nullptr);
auto customPropertyNumber1 =
artboard->find<rive::CustomPropertyNumber>("mapped-range-1");
REQUIRE(customPropertyNumber1->propertyValue() == 6.0f);
// Range mapper [2 - 3] - lower clamp - upper clamp
REQUIRE(artboard->find<rive::CustomPropertyNumber>("mapped-range-2") !=
nullptr);
auto customPropertyNumber2 =
artboard->find<rive::CustomPropertyNumber>("mapped-range-2");
REQUIRE(customPropertyNumber2->propertyValue() == 3.0f);
// Range mapper [2 - 3] - modulo
REQUIRE(artboard->find<rive::CustomPropertyNumber>("mapped-range-3") !=
nullptr);
auto customPropertyNumber3 =
artboard->find<rive::CustomPropertyNumber>("mapped-range-3");
REQUIRE(customPropertyNumber3->propertyValue() == 2.0f);
// Range mapper [2 - 3] - lower clamp - upper clamp - reversed
REQUIRE(artboard->find<rive::CustomPropertyNumber>("mapped-range-4") !=
nullptr);
auto customPropertyNumber4 =
artboard->find<rive::CustomPropertyNumber>("mapped-range-4");
REQUIRE(customPropertyNumber4->propertyValue() == 2.0f);
// Range mapper [2 - 2]
REQUIRE(artboard->find<rive::CustomPropertyNumber>("mapped-range-5") !=
nullptr);
auto customPropertyNumber5 =
artboard->find<rive::CustomPropertyNumber>("mapped-range-5");
REQUIRE(customPropertyNumber5->propertyValue() == 2.0f);
// View model properties
// Number starts at 4.0f
auto numProperty = viewModelInstance->propertyValue("map-range-num");
REQUIRE(numProperty != nullptr);
REQUIRE(numProperty->is<rive::ViewModelInstanceNumber>());
REQUIRE(numProperty->as<rive::ViewModelInstanceNumber>()->propertyValue() ==
4.0f);
// Change value to -1.0f
numProperty->as<rive::ViewModelInstanceNumber>()->propertyValue(-1.0f);
// Advance state machine
machine->advanceAndApply(0.0f);
REQUIRE(customPropertyNumber1->propertyValue() == 1.0f);
REQUIRE(customPropertyNumber2->propertyValue() == 2.0f);
REQUIRE(customPropertyNumber3->propertyValue() == 2.0f);
REQUIRE(customPropertyNumber4->propertyValue() == 3.0f);
REQUIRE(customPropertyNumber5->propertyValue() == 2.0f);
// Change value to 0.0f
numProperty->as<rive::ViewModelInstanceNumber>()->propertyValue(0.0f);
// Advance state machine
machine->advanceAndApply(0.0f);
REQUIRE(customPropertyNumber1->propertyValue() == 2.0f);
REQUIRE(customPropertyNumber2->propertyValue() == 2.0f);
REQUIRE(customPropertyNumber3->propertyValue() == 2.0f);
REQUIRE(customPropertyNumber4->propertyValue() == 3.0f);
REQUIRE(customPropertyNumber5->propertyValue() == 2.0f);
// Change value to 0.25f
numProperty->as<rive::ViewModelInstanceNumber>()->propertyValue(0.25f);
// Advance state machine
machine->advanceAndApply(0.0f);
REQUIRE(customPropertyNumber1->propertyValue() == Approx(2.12916f));
REQUIRE(customPropertyNumber2->propertyValue() == Approx(2.12916f));
REQUIRE(customPropertyNumber3->propertyValue() == Approx(2.12916f));
REQUIRE(customPropertyNumber4->propertyValue() == Approx(2.87084f));
REQUIRE(customPropertyNumber5->propertyValue() == 2.0f);
// Change value to 2.0f
numProperty->as<rive::ViewModelInstanceNumber>()->propertyValue(2.0f);
// Advance state machine
machine->advanceAndApply(0.0f);
REQUIRE(customPropertyNumber1->propertyValue() == 4.0f);
REQUIRE(customPropertyNumber2->propertyValue() == 3.0f);
REQUIRE(customPropertyNumber3->propertyValue() == 2.0f);
REQUIRE(customPropertyNumber4->propertyValue() == 2.0f);
REQUIRE(customPropertyNumber5->propertyValue() == 2.0f);
// Change value to 2.25f
numProperty->as<rive::ViewModelInstanceNumber>()->propertyValue(2.25f);
// Advance state machine
machine->advanceAndApply(0.0f);
REQUIRE(customPropertyNumber1->propertyValue() == 4.25f);
REQUIRE(customPropertyNumber2->propertyValue() == 3.0f);
REQUIRE(customPropertyNumber3->propertyValue() == Approx(2.12916f));
REQUIRE(customPropertyNumber4->propertyValue() == 2.0f);
REQUIRE(customPropertyNumber5->propertyValue() == 2.0f);
}
TEST_CASE("Pad String", "[data binding]")
{
auto file = ReadRiveFile("assets/data_binding_test_2.riv");
auto artboard = file->artboard("artboard-3")->instance();
REQUIRE(artboard != nullptr);
auto viewModelInstance =
file->createDefaultViewModelInstance(artboard.get());
REQUIRE(viewModelInstance != nullptr);
auto machine = artboard->defaultStateMachine();
machine->bindViewModelInstance(viewModelInstance);
REQUIRE(machine != nullptr);
// Advance state machine
machine->advanceAndApply(0.0f);
// Pad string "abc" - length: 11 - Start
REQUIRE(artboard->find<rive::CustomPropertyString>("pad-string-1") !=
nullptr);
auto customPropertyString1 =
artboard->find<rive::CustomPropertyString>("pad-string-1");
REQUIRE(customPropertyString1->propertyValue() == "abcabcatext");
// Pad string "abc" - length: 12 - End
REQUIRE(artboard->find<rive::CustomPropertyString>("pad-string-2") !=
nullptr);
auto customPropertyString2 =
artboard->find<rive::CustomPropertyString>("pad-string-2");
REQUIRE(customPropertyString2->propertyValue() == "textabcabcab");
// Pad string "abc" - length: 12 - End - Worng type of input
REQUIRE(artboard->find<rive::CustomPropertyString>("pad-string-3") !=
nullptr);
auto customPropertyString3 =
artboard->find<rive::CustomPropertyString>("pad-string-3");
REQUIRE(customPropertyString3->propertyValue() == "");
// View model properties
// String starts with "text"
auto stringProperty = viewModelInstance->propertyValue("pad-string");
REQUIRE(stringProperty != nullptr);
REQUIRE(stringProperty->is<rive::ViewModelInstanceString>());
REQUIRE(
stringProperty->as<rive::ViewModelInstanceString>()->propertyValue() ==
"text");
// Change value to "text-text-text", longer than length
stringProperty->as<rive::ViewModelInstanceString>()->propertyValue(
"text-text-text");
// Advance state machine
machine->advanceAndApply(0.0f);
REQUIRE(customPropertyString1->propertyValue() == "text-text-text");
REQUIRE(customPropertyString2->propertyValue() == "text-text-text");
REQUIRE(customPropertyString3->propertyValue() == "");
// Change value to "", empty string
stringProperty->as<rive::ViewModelInstanceString>()->propertyValue("");
// Advance state machine
machine->advanceAndApply(0.0f);
REQUIRE(customPropertyString1->propertyValue() == "abcabcabcab");
REQUIRE(customPropertyString2->propertyValue() == "abcabcabcabc");
REQUIRE(customPropertyString3->propertyValue() == "");
}
TEST_CASE("Boolean Toggle", "[data binding]")
{
auto file = ReadRiveFile("assets/data_binding_test_2.riv");
auto artboard = file->artboard("artboard-3")->instance();
REQUIRE(artboard != nullptr);
auto viewModelInstance =
file->createDefaultViewModelInstance(artboard.get());
REQUIRE(viewModelInstance != nullptr);
auto machine = artboard->defaultStateMachine();
machine->bindViewModelInstance(viewModelInstance);
REQUIRE(machine != nullptr);
// Advance state machine
machine->advanceAndApply(0.0f);
// Boolean property
REQUIRE(artboard->find<rive::CustomPropertyBoolean>("negate-bool-1") !=
nullptr);
auto customPropertyBoolean1 =
artboard->find<rive::CustomPropertyBoolean>("negate-bool-1");
REQUIRE(customPropertyBoolean1->propertyValue() == true);
// View model properties
// bool property starts as false
auto boolProperty = viewModelInstance->propertyValue("bool-prop");
REQUIRE(boolProperty != nullptr);
REQUIRE(boolProperty->is<rive::ViewModelInstanceBoolean>());
REQUIRE(
boolProperty->as<rive::ViewModelInstanceBoolean>()->propertyValue() ==
false);
// Change value to true
boolProperty->as<rive::ViewModelInstanceBoolean>()->propertyValue(true);
// Advance state machine
machine->advanceAndApply(0.0f);
REQUIRE(customPropertyBoolean1->propertyValue() == false);
}
TEST_CASE("Instance is shared when the same one is attached to two properties",
"[data binding]")
{
auto file = ReadRiveFile("assets/shared_viewmodel_instance.riv");
auto artboard = file->artboard("main")->instance();
REQUIRE(artboard != nullptr);
auto viewModelInstance =
file->createDefaultViewModelInstance(artboard.get());
REQUIRE(viewModelInstance != nullptr);
auto machine = artboard->defaultStateMachine();
machine->bindViewModelInstance(viewModelInstance);
REQUIRE(machine != nullptr);
// Advance state machine
machine->advanceAndApply(0.0f);
// View model properties
auto childInstanceViewModel1 = viewModelInstance->propertyValue("child1");
REQUIRE(childInstanceViewModel1 != nullptr);
REQUIRE(childInstanceViewModel1->is<rive::ViewModelInstanceViewModel>());
auto referencedViewModel =
childInstanceViewModel1->as<rive::ViewModelInstanceViewModel>()
->referenceViewModelInstance();
REQUIRE(referencedViewModel != nullptr);
auto labelProperty = referencedViewModel->propertyValue("label");
REQUIRE(labelProperty != nullptr);
REQUIRE(labelProperty->is<rive::ViewModelInstanceString>());
// Elements bound to different view model properties that share same view
// model instance
REQUIRE(artboard->find<rive::NestedArtboard>("child1") != nullptr);
auto nestedArtboardChild1 = artboard->find<rive::NestedArtboard>("child1");
auto nestedArtboardArtboardChild1 =
nestedArtboardChild1->artboardInstance();
REQUIRE(nestedArtboardArtboardChild1 != nullptr);
auto textRunChild1 =
nestedArtboardArtboardChild1->find<rive::TextValueRun>("text_run");
REQUIRE(textRunChild1 != nullptr);
REQUIRE(textRunChild1->text() == "label-vmi-1");
REQUIRE(artboard->find<rive::NestedArtboard>("child2") != nullptr);
auto nestedArtboardChild2 = artboard->find<rive::NestedArtboard>("child2");
auto nestedArtboardArtboardChild2 =
nestedArtboardChild2->artboardInstance();
REQUIRE(nestedArtboardArtboardChild2 != nullptr);
auto textRunChild2 =
nestedArtboardArtboardChild2->find<rive::TextValueRun>("text_run");
REQUIRE(textRunChild2 != nullptr);
REQUIRE(textRunChild2->text() == "label-vmi-1");
// Changing the value on a single instance should affect both text although
// they are linked to different view model properties
labelProperty->as<rive::ViewModelInstanceString>()->propertyValue(
"label-update");
// Advance state machine
machine->advanceAndApply(0.0f);
REQUIRE(textRunChild1->text() == "label-update");
REQUIRE(textRunChild2->text() == "label-update");
}
TEST_CASE("Instance is not shared when the different ones are attached to two "
"properties",
"[data binding]")
{
auto file = ReadRiveFile("assets/shared_viewmodel_instance.riv");
auto artboard = file->artboard("main_2")->instance();
REQUIRE(artboard != nullptr);
auto viewModelInstance =
file->createDefaultViewModelInstance(artboard.get());
REQUIRE(viewModelInstance != nullptr);
auto machine = artboard->defaultStateMachine();
machine->bindViewModelInstance(viewModelInstance);
REQUIRE(machine != nullptr);
// Advance state machine
machine->advanceAndApply(0.0f);
// View model properties
auto childInstanceViewModel1 =
viewModelInstance->propertyValue("vm_2_child1");
REQUIRE(childInstanceViewModel1 != nullptr);
REQUIRE(childInstanceViewModel1->is<rive::ViewModelInstanceViewModel>());
auto referencedViewModel =
childInstanceViewModel1->as<rive::ViewModelInstanceViewModel>()
->referenceViewModelInstance();
REQUIRE(referencedViewModel != nullptr);
auto labelProperty = referencedViewModel->propertyValue("label");
REQUIRE(labelProperty != nullptr);
REQUIRE(labelProperty->is<rive::ViewModelInstanceString>());
// Elements bound to different view model properties that do not share same
// view model instance
REQUIRE(artboard->find<rive::NestedArtboard>("child1") != nullptr);
auto nestedArtboardChild1 = artboard->find<rive::NestedArtboard>("child1");
auto nestedArtboardArtboardChild1 =
nestedArtboardChild1->artboardInstance();
REQUIRE(nestedArtboardArtboardChild1 != nullptr);
auto textRunChild1 =
nestedArtboardArtboardChild1->find<rive::TextValueRun>("text_run");
REQUIRE(textRunChild1 != nullptr);
REQUIRE(textRunChild1->text() == "label-vmi-1");
REQUIRE(artboard->find<rive::NestedArtboard>("child2") != nullptr);
auto nestedArtboardChild2 = artboard->find<rive::NestedArtboard>("child2");
auto nestedArtboardArtboardChild2 =
nestedArtboardChild2->artboardInstance();
REQUIRE(nestedArtboardArtboardChild2 != nullptr);
auto textRunChild2 =
nestedArtboardArtboardChild2->find<rive::TextValueRun>("text_run");
REQUIRE(textRunChild2 != nullptr);
REQUIRE(textRunChild2->text() == "label-vmi-2");
// Changing the value on a single instance should not affect the other
// instance because they are not pointing to the same instance
labelProperty->as<rive::ViewModelInstanceString>()->propertyValue(
"label-update");
// Advance state machine
machine->advanceAndApply(0.0f);
REQUIRE(textRunChild1->text() == "label-update");
REQUIRE(textRunChild2->text() == "label-vmi-2");
}
TEST_CASE("Instances are not shared when a new view model instance is created",
"[data binding]")
{
auto file = ReadRiveFile("assets/shared_viewmodel_instance.riv");
auto artboard = file->artboard("main_2")->instance();
REQUIRE(artboard != nullptr);
auto viewModelInstance = file->createViewModelInstance(artboard.get());
REQUIRE(viewModelInstance != nullptr);
auto machine = artboard->defaultStateMachine();
machine->bindViewModelInstance(viewModelInstance);
REQUIRE(machine != nullptr);
// Advance state machine
machine->advanceAndApply(0.0f);
// View model properties
auto childInstanceViewModel1 =
viewModelInstance->propertyValue("vm_2_child1");
REQUIRE(childInstanceViewModel1 != nullptr);
REQUIRE(childInstanceViewModel1->is<rive::ViewModelInstanceViewModel>());
auto referencedViewModel =
childInstanceViewModel1->as<rive::ViewModelInstanceViewModel>()
->referenceViewModelInstance();
REQUIRE(referencedViewModel != nullptr);
auto labelProperty = referencedViewModel->propertyValue("label");
REQUIRE(labelProperty != nullptr);
REQUIRE(labelProperty->is<rive::ViewModelInstanceString>());
// Elements bound to different view model properties that do not share same
// view model instance
REQUIRE(artboard->find<rive::NestedArtboard>("child1") != nullptr);
auto nestedArtboardChild1 = artboard->find<rive::NestedArtboard>("child1");
auto nestedArtboardArtboardChild1 =
nestedArtboardChild1->artboardInstance();
REQUIRE(nestedArtboardArtboardChild1 != nullptr);
auto textRunChild1 =
nestedArtboardArtboardChild1->find<rive::TextValueRun>("text_run");
REQUIRE(textRunChild1 != nullptr);
REQUIRE(textRunChild1->text() == "");
REQUIRE(artboard->find<rive::NestedArtboard>("child2") != nullptr);
auto nestedArtboardChild2 = artboard->find<rive::NestedArtboard>("child2");
auto nestedArtboardArtboardChild2 =
nestedArtboardChild2->artboardInstance();
REQUIRE(nestedArtboardArtboardChild2 != nullptr);
auto textRunChild2 =
nestedArtboardArtboardChild2->find<rive::TextValueRun>("text_run");
REQUIRE(textRunChild2 != nullptr);
REQUIRE(textRunChild2->text() == "");
// Changing the value on a single instance should not affect the other
// instance because they are not pointing to the same instance
labelProperty->as<rive::ViewModelInstanceString>()->propertyValue(
"label-update");
// Advance state machine
machine->advanceAndApply(0.0f);
REQUIRE(textRunChild1->text() == "label-update");
REQUIRE(textRunChild2->text() == "");
}
TEST_CASE("Triggers updated by events correctly update state", "[data binding]")
{
auto file = ReadRiveFile("assets/data_binding_test_triggers.riv");
auto artboard = file->artboard("root")->instance();
REQUIRE(artboard != nullptr);
auto viewModelInstance = file->createViewModelInstance(artboard.get());
REQUIRE(viewModelInstance != nullptr);
auto machine = artboard->defaultStateMachine();
machine->bindViewModelInstance(viewModelInstance);
REQUIRE(machine != nullptr);
// Advance state machine
machine->advanceAndApply(0.0f);
REQUIRE(artboard->find<rive::Shape>("main_rect") != nullptr);
auto rect = artboard->find<rive::Shape>("main_rect");
REQUIRE(rect->children()[1]->is<rive::Fill>());
rive::Fill* rectMappadFill = rect->children()[1]->as<rive::Fill>();
REQUIRE(rectMappadFill->paint()->is<rive::SolidColor>());
REQUIRE(rectMappadFill->paint()->as<rive::SolidColor>()->colorValue() ==
rive::colorARGB(255, 255, 0, 0));
// Advance state machine so the child reports the event
machine->advanceAndApply(0.7f);
// Advance state machine so the parent consumes the event
machine->advanceAndApply(0.1f);
REQUIRE(rectMappadFill->paint()->as<rive::SolidColor>()->colorValue() ==
rive::colorARGB(255, 0, 255, 0));
}
TEST_CASE("Transition self conditions", "[data binding]")
{
rive::SerializingFactory silver;
auto file =
ReadRiveFile("assets/transition_self_comparator_test.riv", &silver);
auto artboard = file->artboardDefault();
silver.frameSize(artboard->width(), artboard->height());
REQUIRE(artboard != nullptr);
auto stateMachine = artboard->stateMachineAt(0);
int viewModelId = artboard.get()->viewModelId();
auto vmi = viewModelId == -1
? file->createViewModelInstance(artboard.get())
: file->createViewModelInstance(viewModelId, 0);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.1f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
auto numProp =
vmi->propertyValue("num")->as<rive::ViewModelInstanceNumber>();
auto triggerProp =
vmi->propertyValue("tri")->as<rive::ViewModelInstanceTrigger>();
auto colorProp =
vmi->propertyValue("col")->as<rive::ViewModelInstanceColor>();
auto bolProp =
vmi->propertyValue("bol")->as<rive::ViewModelInstanceBoolean>();
auto stringProp =
vmi->propertyValue("str")->as<rive::ViewModelInstanceString>();
auto lisProp = vmi->propertyValue("lis")->as<rive::ViewModelInstanceList>();
// Number value changes once triggering a state transition
silver.addFrame();
numProp->propertyValue(20);
stateMachine->advanceAndApply(0.0f);
artboard->draw(renderer.get());
// Setting number property to same value doesn't trigger state transition
silver.addFrame();
numProp->propertyValue(20);
stateMachine->advanceAndApply(0.0f);
artboard->draw(renderer.get());
// Updating number property twice triggers single state transition
silver.addFrame();
numProp->propertyValue(10);
numProp->propertyValue(20);
stateMachine->advanceAndApply(0.0f);
artboard->draw(renderer.get());
// Updating two properties triggers transition in different layers
silver.addFrame();
numProp->propertyValue(10);
triggerProp->trigger();
stateMachine->advanceAndApply(0.0f);
artboard->draw(renderer.get());
// Updating color property twice triggers state transition
silver.addFrame();
colorProp->propertyValue(rive::colorARGB(100, 0, 10, 15));
colorProp->propertyValue(rive::colorARGB(101, 0, 10, 15));
stateMachine->advanceAndApply(0.0f);
artboard->draw(renderer.get());
// Updating color property once more triggers state transition
silver.addFrame();
colorProp->propertyValue(rive::colorARGB(102, 0, 10, 15));
stateMachine->advanceAndApply(0.0f);
artboard->draw(renderer.get());
// Updating boolean property twice triggers state transition
silver.addFrame();
bolProp->propertyValue(true);
bolProp->propertyValue(false);
stateMachine->advanceAndApply(0.0f);
artboard->draw(renderer.get());
// Updating boolean property once more triggers state transition
silver.addFrame();
bolProp->propertyValue(true);
stateMachine->advanceAndApply(0.0f);
artboard->draw(renderer.get());
// Updating string property twice triggers state transition
silver.addFrame();
stringProp->propertyValue("a");
stringProp->propertyValue("b");
stateMachine->advanceAndApply(0.0f);
artboard->draw(renderer.get());
// Updating string property once more triggers state transition
silver.addFrame();
stringProp->propertyValue("c");
stateMachine->advanceAndApply(0.0f);
artboard->draw(renderer.get());
// Updating list property by adding two items to the list triggers a single
// state transition
{
silver.addFrame();
auto itemList = rive::make_rcp<rive::ViewModelInstanceListItem>();
lisProp->addItem(itemList);
auto itemList2 = rive::make_rcp<rive::ViewModelInstanceListItem>();
lisProp->addItem(itemList2);
stateMachine->advanceAndApply(0.0f);
artboard->draw(renderer.get());
}
// Updating list property by adding one item to the list triggers a single
// state transition
{
silver.addFrame();
auto itemList = rive::make_rcp<rive::ViewModelInstanceListItem>();
lisProp->addItem(itemList);
stateMachine->advanceAndApply(0.0f);
artboard->draw(renderer.get());
}
// Updating list property by adding one item at a specific position triggers
// a state transition
{
silver.addFrame();
auto itemList = rive::make_rcp<rive::ViewModelInstanceListItem>();
lisProp->addItemAt(itemList, 0);
stateMachine->advanceAndApply(0.0f);
artboard->draw(renderer.get());
}
// Updating list property by adding one item at a at an invalid position
// does not trigger a state transition
{
silver.addFrame();
auto itemList = rive::make_rcp<rive::ViewModelInstanceListItem>();
lisProp->addItemAt(itemList, 10);
stateMachine->advanceAndApply(0.0f);
artboard->draw(renderer.get());
}
// Updating list property swapping items triggers a state transition
{
silver.addFrame();
lisProp->swap(0, 1);
stateMachine->advanceAndApply(0.0f);
artboard->draw(renderer.get());
}
// Removing item from list by index triggers a state transition
{
silver.addFrame();
lisProp->removeItem(0);
stateMachine->advanceAndApply(0.0f);
artboard->draw(renderer.get());
}
// Removing item from list by index outside of range doesn't trigger a state
// transition
{
silver.addFrame();
lisProp->removeItem(10);
stateMachine->advanceAndApply(0.0f);
artboard->draw(renderer.get());
}
CHECK(silver.matches("transition_self_comparator_test"));
}
TEST_CASE("Computed root transform nested artboard", "[data binding]")
{
rive::SerializingFactory silver;
auto file = ReadRiveFile("assets/computed_root_transform.riv", &silver);
auto artboard = file->artboardNamed("nested-artboard-main");
silver.frameSize(artboard->width(), artboard->height());
REQUIRE(artboard != nullptr);
auto stateMachine = artboard->stateMachineAt(0);
int viewModelId = artboard.get()->viewModelId();
auto vmi = viewModelId == -1
? file->createViewModelInstance(artboard.get())
: file->createViewModelInstance(viewModelId, 0);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.0f);
stateMachine->advanceAndApply(0.016f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
int frames = (int)(1.0f / 0.016f);
for (int i = 0; i < frames; i++)
{
silver.addFrame();
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
}
CHECK(silver.matches("computed_root_transform-nested_artboard"));
}
TEST_CASE("Computed root transform list", "[data binding]")
{
rive::SerializingFactory silver;
auto file = ReadRiveFile("assets/computed_root_transform.riv", &silver);
auto artboard = file->artboardNamed("list-main");
silver.frameSize(artboard->width(), artboard->height());
REQUIRE(artboard != nullptr);
auto stateMachine = artboard->stateMachineAt(0);
int viewModelId = artboard.get()->viewModelId();
auto vmi = viewModelId == -1
? file->createViewModelInstance(artboard.get())
: file->createViewModelInstance(viewModelId, 0);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.0f);
stateMachine->advanceAndApply(0.016f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
int frames = (int)(1.0f / 0.016f);
for (int i = 0; i < frames; i++)
{
silver.addFrame();
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
}
CHECK(silver.matches("computed_root_transform-list"));
}
TEST_CASE("Trigger based listeners", "[data binding]")
{
rive::SerializingFactory silver;
auto file = ReadRiveFile("assets/trigger_based_listeners.riv", &silver);
auto artboard = file->artboardNamed("main");
silver.frameSize(artboard->width(), artboard->height());
REQUIRE(artboard != nullptr);
auto stateMachine = artboard->stateMachineAt(0);
int viewModelId = artboard.get()->viewModelId();
auto vmi = viewModelId == -1
? file->createViewModelInstance(artboard.get())
: file->createViewModelInstance(viewModelId, 0);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.0f);
stateMachine->advanceAndApply(0.016f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
stateMachine->pointerDown(rive::Vec2D(25.0f, 25.0f));
// Advance and apply twice to take the transition and apply the next state.
stateMachine->advanceAndApply(0.1f);
stateMachine->advanceAndApply(1.0f);
stateMachine->pointerUp(rive::Vec2D(25.0f, 25.0f));
// Advance and apply twice to take the transition and apply the next state.
stateMachine->advanceAndApply(0.1f);
stateMachine->advanceAndApply(1.0f);
silver.addFrame();
artboard->draw(renderer.get());
CHECK(silver.matches("trigger_based_listeners"));
}
TEST_CASE("Custom Property Trigger Binding", "[data binding]")
{
rive::SerializingFactory silver;
auto file = ReadRiveFile("assets/custom_property_trigger.riv", &silver);
auto artboard = file->artboard("Main")->instance();
REQUIRE(artboard != nullptr);
silver.frameSize(artboard->width(), artboard->height());
auto viewModelInstance =
file->createDefaultViewModelInstance(artboard.get());
REQUIRE(viewModelInstance != nullptr);
auto machine = artboard->defaultStateMachine();
machine->bindViewModelInstance(viewModelInstance);
REQUIRE(machine != nullptr);
// Advance state machine
machine->advanceAndApply(0.0f);
auto circle = artboard->find<rive::Shape>("MainCircle");
REQUIRE(circle != nullptr);
REQUIRE(circle->scaleX() == 1.0f);
REQUIRE(circle->scaleY() == 1.0f);
auto trig = artboard->find<rive::CustomPropertyTrigger>("Trig");
REQUIRE(trig != nullptr);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
int frames = (int)(1.0f / 0.16f);
for (int i = 0; i < frames; i++)
{
silver.addFrame();
machine->advanceAndApply(0.16f);
artboard->draw(renderer.get());
}
CHECK(silver.matches("custom_property_trigger_bind"));
}
TEST_CASE("Data binding solos", "[data binding]")
{
rive::SerializingFactory silver;
auto file = ReadRiveFile("assets/data_bind_solo.riv", &silver);
auto artboard = file->artboardNamed("values-to-solos");
silver.frameSize(artboard->width(), artboard->height());
REQUIRE(artboard != nullptr);
auto stateMachine = artboard->stateMachineAt(0);
int viewModelId = artboard.get()->viewModelId();
auto vmi = viewModelId == -1
? file->createViewModelInstance(artboard.get())
: file->createViewModelInstance(viewModelId, 0);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.0f);
stateMachine->advanceAndApply(0.016f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
int frames = (int)(1.0f / 0.016f);
for (int i = 0; i < frames; i++)
{
silver.addFrame();
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
}
CHECK(silver.matches("data_bind_solo-values-to-solos"));
}
TEST_CASE("Data binding solos - target to source", "[data binding]")
{
rive::SerializingFactory silver;
auto file = ReadRiveFile("assets/data_bind_solo.riv", &silver);
auto artboard = file->artboardNamed("solos-to-values");
silver.frameSize(artboard->width(), artboard->height());
REQUIRE(artboard != nullptr);
auto stateMachine = artboard->stateMachineAt(0);
int viewModelId = artboard.get()->viewModelId();
auto vmi = viewModelId == -1
? file->createViewModelInstance(artboard.get())
: file->createViewModelInstance(viewModelId, 0);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.0f);
stateMachine->advanceAndApply(0.016f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
int frames = (int)(1.0f / 0.016f);
for (int i = 0; i < frames; i++)
{
silver.addFrame();
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
}
CHECK(silver.matches("data_bind_solo-solos-to-values"));
}
TEST_CASE("State machine fire triggers", "[data binding]")
{
rive::SerializingFactory silver;
auto file =
ReadRiveFile("assets/state_transition_fire_trigger.riv", &silver);
auto artboard = file->artboardNamed("main");
silver.frameSize(artboard->width(), artboard->height());
REQUIRE(artboard != nullptr);
auto stateMachine = artboard->stateMachineAt(0);
int viewModelId = artboard.get()->viewModelId();
auto vmi = viewModelId == -1
? file->createViewModelInstance(artboard.get())
: file->createViewModelInstance(viewModelId, 0);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.0f);
stateMachine->advanceAndApply(0.016f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
// Advance and apply twice to take the transition and apply the next state.
stateMachine->advanceAndApply(0.1f);
stateMachine->advanceAndApply(1.0f);
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
stateMachine->advanceAndApply(1.0f);
artboard->draw(renderer.get());
silver.addFrame();
// Advance and apply twice to take the transition and apply the next state.
stateMachine->advanceAndApply(0.1f);
stateMachine->advanceAndApply(1.0f);
artboard->draw(renderer.get());
CHECK(silver.matches("state_transition_fire_trigger"));
}
TEST_CASE("Custom enum properties", "[data binding]")
{
rive::SerializingFactory silver;
auto file = ReadRiveFile("assets/custom_property_enum.riv", &silver);
auto artboard = file->artboardNamed("main");
silver.frameSize(artboard->width(), artboard->height());
REQUIRE(artboard != nullptr);
auto stateMachine = artboard->stateMachineAt(0);
int viewModelId = artboard.get()->viewModelId();
auto vmi = viewModelId == -1
? file->createViewModelInstance(artboard.get())
: file->createViewModelInstance(viewModelId, 0);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.0f);
stateMachine->advanceAndApply(0.016f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
int frames = (int)(3.0f / 0.048f);
for (int i = 0; i < frames; i++)
{
silver.addFrame();
stateMachine->advanceAndApply(0.048f);
artboard->draw(renderer.get());
}
CHECK(silver.matches("custom_property_enum"));
}
TEST_CASE("View model runtime properties", "[data binding]")
{
auto file = ReadRiveFile("assets/viewmodel_runtime_file.riv");
auto vm = file->viewModelByName("vm");
REQUIRE(vm != nullptr);
auto instance = vm->createDefaultInstance();
REQUIRE(instance != nullptr);
// Number
auto num = instance->propertyNumber("num");
REQUIRE(num != nullptr);
REQUIRE(num->dataType() == rive::DataType::number);
// string
auto str = instance->propertyString("str");
REQUIRE(str != nullptr);
REQUIRE(str->dataType() == rive::DataType::string);
// Requesting a property with the same name from cache doesn't return the
// wrong object
auto strWrong = instance->propertyNumber("str");
REQUIRE(strWrong == nullptr);
// Boolean
auto boo = instance->propertyBoolean("boo");
REQUIRE(boo != nullptr);
REQUIRE(boo->dataType() == rive::DataType::boolean);
// Color
auto col = instance->propertyColor("col");
REQUIRE(col != nullptr);
REQUIRE(col->dataType() == rive::DataType::color);
// Trigger
auto tri = instance->propertyTrigger("tri");
REQUIRE(tri != nullptr);
REQUIRE(tri->dataType() == rive::DataType::trigger);
// Enum
auto enu = instance->propertyEnum("enu");
REQUIRE(enu != nullptr);
REQUIRE(enu->dataType() == rive::DataType::enumType);
// Image
auto ima = instance->propertyImage("ima");
REQUIRE(ima != nullptr);
REQUIRE(ima->dataType() == rive::DataType::assetImage);
// Artboard
auto art = instance->propertyArtboard("art");
REQUIRE(art != nullptr);
REQUIRE(art->dataType() == rive::DataType::artboard);
// List
auto lis = instance->propertyList("lis");
REQUIRE(lis != nullptr);
REQUIRE(lis->dataType() == rive::DataType::list);
// number in nested view model: chi > num
auto numChi = instance->propertyNumber("chi/chi-num");
REQUIRE(numChi != nullptr);
REQUIRE(numChi->dataType() == rive::DataType::number);
}
TEST_CASE("Trigger fires single change on listener", "[data binding]")
{
rive::SerializingFactory silver;
auto file = ReadRiveFile("assets/trigger_fires_single_change.riv", &silver);
auto artboard = file->artboardNamed("main");
silver.frameSize(artboard->width(), artboard->height());
REQUIRE(artboard != nullptr);
auto stateMachine = artboard->stateMachineAt(0);
int viewModelId = artboard.get()->viewModelId();
auto vmi = viewModelId == -1
? file->createViewModelInstance(artboard.get())
: file->createViewModelInstance(viewModelId, 0);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.0f);
stateMachine->advanceAndApply(0.016f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
// Advance and apply twice to take the transition and apply the next state.
stateMachine->pointerDown(rive::Vec2D(225.0f, 275.0f));
stateMachine->advanceAndApply(0.1f);
stateMachine->advanceAndApply(1.0f);
stateMachine->pointerUp(rive::Vec2D(225.0f, 275.0f));
stateMachine->advanceAndApply(0.1f);
stateMachine->advanceAndApply(1.0f);
silver.addFrame();
artboard->draw(renderer.get());
// Advance and apply twice to take the transition and apply the next state.
stateMachine->pointerDown(rive::Vec2D(225.0f, 275.0f));
stateMachine->advanceAndApply(0.1f);
stateMachine->advanceAndApply(1.0f);
stateMachine->pointerUp(rive::Vec2D(225.0f, 275.0f));
stateMachine->advanceAndApply(0.1f);
stateMachine->advanceAndApply(1.0f);
silver.addFrame();
artboard->draw(renderer.get());
CHECK(silver.matches("trigger_fires_single_change"));
}
TEST_CASE("Convert to number", "[data binding]")
{
rive::SerializingFactory silver;
auto file = ReadRiveFile("assets/data_converter_to_number.riv", &silver);
auto artboard = file->artboardNamed("main");
silver.frameSize(artboard->width(), artboard->height());
REQUIRE(artboard != nullptr);
auto stateMachine = artboard->stateMachineAt(0);
int viewModelId = artboard.get()->viewModelId();
auto vmi = viewModelId == -1
? file->createViewModelInstance(artboard.get())
: file->createViewModelInstance(viewModelId, 0);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.0f);
stateMachine->advanceAndApply(0.016f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
int frames = (int)(1.2f / 0.016f);
for (int i = 0; i < frames; i++)
{
silver.addFrame();
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
}
CHECK(silver.matches("data_converter_to_number"));
}
TEST_CASE("List to path", "[data binding]")
{
rive::SerializingFactory silver;
auto file = ReadRiveFile("assets/list_to_path.riv", &silver);
auto artboard = file->artboardNamed("main");
silver.frameSize(artboard->width(), artboard->height());
REQUIRE(artboard != nullptr);
auto stateMachine = artboard->stateMachineAt(0);
int viewModelId = artboard.get()->viewModelId();
auto vmi = viewModelId == -1
? file->createViewModelInstance(artboard.get())
: file->createViewModelInstance(viewModelId, 0);
auto listProp =
vmi->propertyValue("lis")->as<rive::ViewModelInstanceList>();
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.0f);
stateMachine->advanceAndApply(0.016f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
silver.addFrame();
// Create a square with 4 XY vertices
auto vertex1 = file->createViewModelInstance("vertex-x-y");
auto vertexInstanceListItem1 =
rive::make_rcp<rive::ViewModelInstanceListItem>();
vertexInstanceListItem1->viewModelInstance(vertex1);
listProp->addItem(vertexInstanceListItem1);
auto vertex2 = file->createViewModelInstance("vertex-x-y");
vertex2->propertyValue("x")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(100);
auto vertexInstanceListItem2 =
rive::make_rcp<rive::ViewModelInstanceListItem>();
vertexInstanceListItem2->viewModelInstance(vertex2);
listProp->addItem(vertexInstanceListItem2);
auto vertex3 = file->createViewModelInstance("vertex-x-y");
vertex3->propertyValue("x")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(100);
vertex3->propertyValue("y")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(100);
auto vertexInstanceListItem3 =
rive::make_rcp<rive::ViewModelInstanceListItem>();
vertexInstanceListItem3->viewModelInstance(vertex3);
listProp->addItem(vertexInstanceListItem3);
auto vertex4 = file->createViewModelInstance("vertex-x-y");
vertex4->propertyValue("y")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(100);
auto vertexInstanceListItem4 =
rive::make_rcp<rive::ViewModelInstanceListItem>();
vertexInstanceListItem4->viewModelInstance(vertex4);
listProp->addItem(vertexInstanceListItem4);
stateMachine->advanceAndApply(0.0f);
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
silver.addFrame();
// Insert a mirrored vertex at index 2
auto vertexRD1 = file->createViewModelInstance("vertex-rotation-distance");
vertexRD1->propertyValue("x")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(200);
vertexRD1->propertyValue("rotation")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(1.5f);
vertexRD1->propertyValue("distance")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(20);
auto vertexInstanceListItemRD1 =
rive::make_rcp<rive::ViewModelInstanceListItem>();
vertexInstanceListItemRD1->viewModelInstance(vertexRD1);
listProp->addItemAt(vertexInstanceListItemRD1, 2);
stateMachine->advanceAndApply(0.0f);
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
silver.addFrame();
// Insert a detached vertex at index 3
auto vertexD1 = file->createViewModelInstance("vertex-detached");
vertexD1->propertyValue("x")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(200);
vertexD1->propertyValue("y")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(100);
vertexD1->propertyValue("inRotation")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(1);
vertexD1->propertyValue("outRotation")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(2);
vertexD1->propertyValue("inDistance")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(10);
vertexD1->propertyValue("outDistance")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(30);
auto vertexInstanceListItemD1 =
rive::make_rcp<rive::ViewModelInstanceListItem>();
vertexInstanceListItemD1->viewModelInstance(vertexD1);
listProp->addItemAt(vertexInstanceListItemD1, 3);
stateMachine->advanceAndApply(0.0f);
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
silver.addFrame();
// Insert a cubic in out vertex at index 4
auto vertexIO1 = file->createViewModelInstance("vertex-in-out");
vertexIO1->propertyValue("x")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(100);
vertexIO1->propertyValue("y")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(200);
vertexIO1->propertyValue("inX")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(40);
vertexIO1->propertyValue("inY")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(20);
vertexIO1->propertyValue("outX")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(10);
vertexIO1->propertyValue("outY")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(30);
auto vertexInstanceListItemIO1 =
rive::make_rcp<rive::ViewModelInstanceListItem>();
vertexInstanceListItemIO1->viewModelInstance(vertexIO1);
listProp->addItemAt(vertexInstanceListItemIO1, 4);
stateMachine->advanceAndApply(0.0f);
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
silver.addFrame();
// Insert a non valid vertex at index 4
auto vertexN1 = file->createViewModelInstance("non-vertex");
auto vertexInstanceListItemN1 =
rive::make_rcp<rive::ViewModelInstanceListItem>();
vertexInstanceListItemN1->viewModelInstance(vertexN1);
listProp->addItemAt(vertexInstanceListItemN1, 5);
stateMachine->advanceAndApply(0.0f);
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
silver.addFrame();
// Insert a vertex with some paired values undefined
auto vertexI1 = file->createViewModelInstance("vertex-incomplete");
vertexI1->propertyValue("x")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(100);
vertexI1->propertyValue("y")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(300);
vertexI1->propertyValue("inDistance")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(60);
vertexI1->propertyValue("inRotation")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(-1);
vertexI1->propertyValue("outX")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(30);
vertexI1->propertyValue("inX")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(-30);
auto vertexInstanceListItemI1 =
rive::make_rcp<rive::ViewModelInstanceListItem>();
vertexInstanceListItemI1->viewModelInstance(vertexI1);
listProp->addItemAt(vertexInstanceListItemI1, 4);
stateMachine->advanceAndApply(0.0f);
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
silver.addFrame();
// Update some values to trigger dirt updates
vertexI1->propertyValue("inX")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(-30);
vertex1->propertyValue("x")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(50);
vertexRD1->propertyValue("rotation")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(1.0f);
vertexD1->propertyValue("inDistance")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(30);
vertexIO1->propertyValue("outY")
->as<rive::ViewModelInstanceNumber>()
->propertyValue(40);
stateMachine->advanceAndApply(0.0f);
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
for (int i = 0; i < 60; i++)
{
silver.addFrame();
vertexI1->propertyValue("inRotation")
->as<rive::ViewModelInstanceNumber>()
->propertyValue((float)i * 6);
vertexRD1->propertyValue("rotation")
->as<rive::ViewModelInstanceNumber>()
->propertyValue((float)i * 6);
stateMachine->advanceAndApply(0.01f);
stateMachine->advanceAndApply(0.0f);
artboard->draw(renderer.get());
}
CHECK(silver.matches("list_to_path"));
}
TEST_CASE("Format text with commas", "[data binding]")
{
rive::SerializingFactory silver;
auto file = ReadRiveFile("assets/format_number_with_commas.riv", &silver);
auto artboard = file->artboardNamed("main");
silver.frameSize(artboard->width(), artboard->height());
REQUIRE(artboard != nullptr);
auto stateMachine = artboard->stateMachineAt(0);
int viewModelId = artboard.get()->viewModelId();
auto vmi = viewModelId == -1
? file->createViewModelInstance(artboard.get())
: file->createViewModelInstance(viewModelId, 0);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.0f);
stateMachine->advanceAndApply(0.016f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
CHECK(silver.matches("format_number_with_commas"));
}
TEST_CASE("Interpolate color and number", "[data binding]")
{
rive::SerializingFactory silver;
auto file = ReadRiveFile("assets/time_based_interpolation.riv", &silver);
auto artboard = file->artboardNamed("main");
silver.frameSize(artboard->width(), artboard->height());
REQUIRE(artboard != nullptr);
auto stateMachine = artboard->stateMachineAt(0);
int viewModelId = artboard.get()->viewModelId();
auto vmi = viewModelId == -1
? file->createViewModelInstance(artboard.get())
: file->createViewModelInstance(viewModelId, 0);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.0f);
stateMachine->advanceAndApply(0.016f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
stateMachine->pointerDown(rive::Vec2D(25.0f, 25.0f));
// Advance and apply twice to take the transition and apply the next state.
stateMachine->advanceAndApply(0.016f);
stateMachine->advanceAndApply(0.016f);
stateMachine->pointerUp(rive::Vec2D(25.0f, 25.0f));
// Advance and apply twice to take the transition and apply the next state.
stateMachine->advanceAndApply(0.016f);
stateMachine->advanceAndApply(0.016f);
silver.addFrame();
artboard->draw(renderer.get());
int frames = (int)(1.0f / 0.032f);
for (int i = 0; i < frames; i++)
{
silver.addFrame();
stateMachine->advanceAndApply(0.032f);
artboard->draw(renderer.get());
}
stateMachine->pointerDown(rive::Vec2D(425.0f, 25.0f));
// Advance and apply twice to take the transition and apply the next state.
stateMachine->advanceAndApply(0.016f);
stateMachine->advanceAndApply(0.016f);
stateMachine->pointerUp(rive::Vec2D(425.0f, 25.0f));
// Advance and apply twice to take the transition and apply the next state.
stateMachine->advanceAndApply(0.016f);
stateMachine->advanceAndApply(0.016f);
silver.addFrame();
artboard->draw(renderer.get());
for (int i = 0; i < 10; i++)
{
silver.addFrame();
stateMachine->advanceAndApply(0.032f);
artboard->draw(renderer.get());
}
// Interrupt interpolation mid way
stateMachine->pointerDown(rive::Vec2D(25.0f, 25.0f));
// Advance and apply twice to take the transition and apply the next state.
stateMachine->advanceAndApply(0.016f);
stateMachine->advanceAndApply(0.016f);
stateMachine->pointerUp(rive::Vec2D(25.0f, 25.0f));
// Advance and apply twice to take the transition and apply the next state.
stateMachine->advanceAndApply(0.016f);
stateMachine->advanceAndApply(0.016f);
for (int i = 0; i < frames; i++)
{
silver.addFrame();
stateMachine->advanceAndApply(0.032f);
artboard->draw(renderer.get());
}
CHECK(silver.matches("time_based_interpolation"));
}
TEST_CASE("Bidirectional data binding with source to target precedence",
"[data binding]")
{
rive::SerializingFactory silver;
auto file = ReadRiveFile("assets/bidirectional_precedence.riv", &silver);
auto artboard = file->artboardNamed("source_first");
silver.frameSize(artboard->width(), artboard->height());
REQUIRE(artboard != nullptr);
auto stateMachine = artboard->stateMachineAt(0);
int viewModelId = artboard.get()->viewModelId();
auto vmi = viewModelId == -1
? file->createViewModelInstance(artboard.get())
: file->createViewModelInstance(viewModelId, 0);
auto xProp = vmi->propertyValue("x")->as<rive::ViewModelInstanceNumber>();
auto yProp = vmi->propertyValue("y")->as<rive::ViewModelInstanceNumber>();
// On source first these values will overwrite the target values
// that are [250,250]
xProp->propertyValue(100);
yProp->propertyValue(100);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.0f);
stateMachine->advanceAndApply(0.016f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
CHECK(silver.matches("bidirectional_precedence-source_first"));
}
TEST_CASE("Bidirectional data binding with target to source precedence",
"[data binding]")
{
rive::SerializingFactory silver;
auto file = ReadRiveFile("assets/bidirectional_precedence.riv", &silver);
auto artboard = file->artboardNamed("target_first");
silver.frameSize(artboard->width(), artboard->height());
REQUIRE(artboard != nullptr);
auto stateMachine = artboard->stateMachineAt(0);
int viewModelId = artboard.get()->viewModelId();
auto vmi = viewModelId == -1
? file->createViewModelInstance(artboard.get())
: file->createViewModelInstance(viewModelId, 0);
auto xProp = vmi->propertyValue("x")->as<rive::ViewModelInstanceNumber>();
auto yProp = vmi->propertyValue("y")->as<rive::ViewModelInstanceNumber>();
// On target first these values will be overwritten by the target values
// that are [250,250]
xProp->propertyValue(100);
yProp->propertyValue(100);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.0f);
stateMachine->advanceAndApply(0.016f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
CHECK(silver.matches("bidirectional_precedence-target_first"));
}
TEST_CASE("Artboards as conditions", "[silver]")
{
SerializingFactory silver;
auto file = ReadRiveFile("assets/databind_artboard.riv", &silver);
auto artboard = file->artboardDefault();
REQUIRE(artboard != nullptr);
silver.frameSize(artboard->width(), artboard->height());
auto stateMachine = artboard->stateMachineAt(0);
auto vmi =
file->createViewModelInstance((int)artboard.get()->viewModelId(), 0);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.1f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
silver.addFrame();
stateMachine->pointerDown(rive::Vec2D(247, 332));
stateMachine->pointerUp(rive::Vec2D(247, 332));
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
CHECK(silver.matches("databind_artboard"));
}
TEST_CASE("Relative data binding", "[silver]")
{
SerializingFactory silver;
auto file = ReadRiveFile("assets/relative_data_binding.riv", &silver);
auto artboard = file->artboardDefault();
REQUIRE(artboard != nullptr);
silver.frameSize(artboard->width(), artboard->height());
auto stateMachine = artboard->stateMachineAt(0);
auto vmi =
file->createViewModelInstance((int)artboard.get()->viewModelId(), 0);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.1f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
CHECK(silver.matches("relative_data_binding"));
}
TEST_CASE("Relative data binding view model path", "[silver]")
{
SerializingFactory silver;
auto file = ReadRiveFile("assets/relative_data_bind_path.riv", &silver);
auto artboard = file->artboardDefault();
REQUIRE(artboard != nullptr);
silver.frameSize(artboard->width(), artboard->height());
auto stateMachine = artboard->stateMachineAt(0);
auto renderer = silver.makeRenderer();
// First use the default view model instance that is attached to the
// artboard
{
auto vmi =
file->createViewModelInstance((int)artboard.get()->viewModelId(),
0);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
silver.addFrame();
}
// Next bind it to a different view model type that matches the expected
// view model shape
{
auto vm = file->viewModel("ViewModel1");
auto vmi = file->createDefaultViewModelInstance(vm);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
silver.addFrame();
}
// Next bind it to a different view model with the wrong shape
{
auto vm = file->viewModel("ViewModel2");
auto vmi = file->createDefaultViewModelInstance(vm);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
}
CHECK(silver.matches("relative_data_bind_path"));
}
TEST_CASE("Listen to view model value changes in state machines", "[silver]")
{
SerializingFactory silver;
auto file = ReadRiveFile("assets/listener_view_model.riv", &silver);
auto artboard = file->artboardDefault();
silver.frameSize(artboard->width(), artboard->height());
auto stateMachine = artboard->stateMachineAt(0);
int viewModelId = artboard.get()->viewModelId();
auto vmi = viewModelId == -1
? file->createViewModelInstance(artboard.get())
: file->createViewModelInstance(viewModelId, 0);
auto colorProp = vmi->propertyValue("col")->as<ViewModelInstanceColor>();
auto triggerProp =
vmi->propertyValue("tri")->as<ViewModelInstanceTrigger>();
auto numProp = vmi->propertyValue("num1")->as<ViewModelInstanceNumber>();
stateMachine->bindViewModelInstance(vmi);
auto renderer = silver.makeRenderer();
stateMachine->advanceAndApply(0.0f);
artboard->draw(renderer.get());
silver.addFrame();
colorProp->propertyValue(rive::colorARGB(100, 0, 10, 15));
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
silver.addFrame();
triggerProp->trigger();
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
silver.addFrame();
numProp->propertyValue(55);
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
CHECK(silver.matches("listener_view_model"));
}