blob: 6139cafded518e1e62423106e608e1500a9d07c8 [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_number.hpp>
#include <rive/custom_property_string.hpp>
#include <rive/custom_property_boolean.hpp>
#include <rive/constraints/follow_path_constraint.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/animation/state_machine_instance.hpp"
#include "rive/nested_artboard.hpp"
#include "rive_file_reader.hpp"
#include <catch.hpp>
#include <cstdio>
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() == "");
}