blob: 395ffbfe8c11096db4c47e6093f7f01f80d4d186 [file]
#include <rive/file.hpp>
#include <rive/node.hpp>
#include <rive/assets/image_asset.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/image.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_artboard.hpp>
#include <rive/viewmodel/viewmodel_instance_viewmodel.hpp>
#include <rive/viewmodel/viewmodel_instance_trigger.hpp>
#include <rive/viewmodel/runtime/viewmodel_instance_runtime.hpp>
#include "rive/animation/state_machine_instance.hpp"
#include "rive/viewmodel/runtime/viewmodel_runtime.hpp"
#include "rive/nested_artboard.hpp"
#include "rive_file_reader.hpp"
#include "utils/serializing_factory.hpp"
#include <catch.hpp>
#include <cstdio>
#include <cstring>
using namespace rive;
TEST_CASE("Test data binding artboards from same and different sources",
"[data binding]")
{
// Note: the data_binding_artboards_source_test has a view model created
// that matches the view model the original artboards have. This is a
// temporary "hack" to validate that the artboard gets correctly bound
SerializingFactory silver;
auto file = ReadRiveFile("assets/data_binding_artboards_test.riv", &silver);
auto sourceFile =
ReadRiveFile("assets/data_binding_artboards_source_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 vmiArtboard =
vmi->propertyValue("ab")->as<ViewModelInstanceArtboard>();
auto vmiChild = vmi->propertyValue("ch")
->as<ViewModelInstanceViewModel>()
->referenceViewModelInstance()
.get();
auto vmTrigger =
vmiChild->propertyValue("tr")->as<ViewModelInstanceTrigger>();
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
// Change source to local artboard at index 1
auto ch1Source = file->bindableArtboardNamed("ch1");
vmiArtboard->asset(ch1Source);
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
// Change source to local artboard at index 2
auto ch2Source = file->bindableArtboardNamed("ch2");
vmiArtboard->asset(ch2Source);
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
// Change source to external artboard "source_1"
auto source1 = sourceFile->bindableArtboardNamed("source_1");
vmiArtboard->asset(source1);
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
// Fire trigger once to change text color
vmTrigger->trigger();
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
// Fire trigger once to change text color again
vmTrigger->trigger();
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
// Change source to external artboard "source_2"
auto source2 = sourceFile->bindableArtboardNamed("source_2");
vmiArtboard->asset(source2);
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
// Change source to external artboard "source_1"
vmiArtboard->asset(source1);
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
// Change back to local artboard at index 1
vmiArtboard->asset(ch2Source);
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
// reset the artboard to null
vmiArtboard->asset(nullptr);
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
// Change back to local artboard at index 1
vmiArtboard->asset(ch2Source);
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
CHECK(silver.matches("data_binding_artboards_test"));
}
TEST_CASE("Test recursive data binding artboards is skipped", "[data binding]")
{
// Note: the data_binding_artboards_source_test has a view model created
// that matches the view model the original artboards have. This is a
// temporary "hack" to validate that the artboard gets correctly bound
SerializingFactory silver;
auto file = ReadRiveFile("assets/data_binding_artboards_test.riv", &silver);
auto artboard = file->artboardNamed("recursive-grand-parent");
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 vmiArtboard =
vmi->propertyValue("ab")->as<ViewModelInstanceArtboard>();
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
// Change source to artboard "recursive-grand-child-1"
auto child1Source = file->bindableArtboardNamed("recursive-grand-child-1");
vmiArtboard->asset(child1Source);
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
// Changing source to artboard "recursive-parent" does not change the
// content because it's an ancestor of itself
auto parentSource = file->bindableArtboardNamed("recursive-parent");
vmiArtboard->asset(parentSource);
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
// Changing source to artboard "recursive-grand-parent" does not change the
// content because it's an ancestor of itself
auto grandParentSource =
file->bindableArtboardNamed("recursive-grand-parent");
vmiArtboard->asset(grandParentSource);
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
// Change source to artboard "recursive-grand-child-2"
auto child2Source = file->bindableArtboardNamed("recursive-grand-child-2");
vmiArtboard->asset(child2Source);
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
CHECK(silver.matches("data_binding_artboards_test_recursive"));
}
TEST_CASE("Setting a bindable artboard clears stale bound instance",
"[data binding]")
{
auto file = ReadRiveFile("assets/data_binding_artboards_test.riv");
auto artboard = file->artboardDefault();
REQUIRE(artboard != nullptr);
int viewModelId = artboard.get()->viewModelId();
auto vmi = viewModelId == -1
? file->createViewModelInstance(artboard.get())
: file->createViewModelInstance(viewModelId, 0);
REQUIRE(vmi != nullptr);
auto vmiArtboard =
vmi->propertyValue("ab")->as<ViewModelInstanceArtboard>();
REQUIRE(vmiArtboard != nullptr);
auto runtimeVmi = make_rcp<ViewModelInstanceRuntime>(vmi);
auto runtimeArtboard = runtimeVmi->propertyArtboard("ab");
REQUIRE(runtimeArtboard != nullptr);
auto sourceA = file->bindableArtboardNamed("ch1");
auto sourceB = file->bindableArtboardNamed("ch2");
REQUIRE(sourceA != nullptr);
REQUIRE(sourceB != nullptr);
runtimeArtboard->value(sourceA);
runtimeArtboard->viewModelInstance(vmi);
REQUIRE(vmiArtboard->boundViewModelInstance() != nullptr);
// Runtime assignment should clear any previously bound VMI.
runtimeArtboard->value(sourceB);
CHECK(vmiArtboard->boundViewModelInstance() == nullptr);
}
TEST_CASE("Runtime artboard property exposes the bound artboard name",
"[data binding]")
{
auto file = ReadRiveFile("assets/data_binding_artboards_test.riv");
auto artboard = file->artboardDefault();
REQUIRE(artboard != nullptr);
int viewModelId = artboard.get()->viewModelId();
auto vmi = viewModelId == -1
? file->createViewModelInstance(artboard.get())
: file->createViewModelInstance(viewModelId, 0);
REQUIRE(vmi != nullptr);
auto runtimeVmi = make_rcp<ViewModelInstanceRuntime>(vmi);
auto runtimeArtboard = runtimeVmi->propertyArtboard("ab");
REQUIRE(runtimeArtboard != nullptr);
auto sourceA = file->bindableArtboardNamed("ch1");
auto sourceB = file->bindableArtboardNamed("ch2");
REQUIRE(sourceA != nullptr);
REQUIRE(sourceB != nullptr);
// The name reflects the currently bound bindable artboard.
runtimeArtboard->value(sourceA);
CHECK(runtimeArtboard->artboardName() == "ch1");
// Rebinding updates the reported name.
runtimeArtboard->value(sourceB);
CHECK(runtimeArtboard->artboardName() == "ch2");
// Clearing the source reports an empty name rather than dangling.
runtimeArtboard->value(nullptr);
CHECK(runtimeArtboard->artboardName().empty());
}
TEST_CASE("Test default data binding artboard from different source",
"[data binding]")
{
// Note: the data_binding_artboards_source_test has a view model created
// that matches the view model the original artboards have. This is a
// temporary "hack" to validate that the artboard gets correctly bound
SerializingFactory silver;
auto file = ReadRiveFile("assets/data_binding_artboards_test.riv", &silver);
auto sourceFile =
ReadRiveFile("assets/data_binding_artboards_source_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 vmiArtboard =
vmi->propertyValue("ab")->as<ViewModelInstanceArtboard>();
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
// Change source to local artboard at index 1
auto ch1Source = file->bindableArtboardNamed("ch1");
vmiArtboard->asset(ch1Source);
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
// Change source to local artboard at index 2
auto ch2Source = file->bindableArtboardNamed("ch2");
vmiArtboard->asset(ch2Source);
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
// Change source to default bindable artboard
auto source1 = sourceFile->bindableArtboardDefault();
vmiArtboard->asset(source1);
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
CHECK(silver.matches("data_binding_artboards_default_test"));
}
TEST_CASE(
"Test Scripted Artboard Input data bound to internal and external artboards",
"[data binding]")
{
// Note: the data_binding_artboards_source_test has a view model created
// that matches the view model the original artboards have. This is a
// temporary "hack" to validate that the artboard gets correctly bound
SerializingFactory silver;
auto file = ReadRiveFile("assets/data_bind_artboard_input.riv", &silver);
auto sourceFile =
ReadRiveFile("assets/data_binding_artboards_source_test.riv", &silver);
auto artboard = file->artboardDefault();
silver.frameSize(artboard->width(), artboard->height());
REQUIRE(artboard != nullptr);
auto stateMachine = artboard->stateMachineAt(0);
auto vmi = file->createViewModelInstance(artboard.get());
stateMachine->bindViewModelInstance(vmi);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
auto vmiArtboard =
vmi->propertyValue("artboardProperty")->as<ViewModelInstanceArtboard>();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
// Change source to local artboard named "child2"
auto ch1Source = file->bindableArtboardNamed("child2");
vmiArtboard->asset(ch1Source);
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
// Change source to local artboard named "child1"
auto ch2Source = file->bindableArtboardNamed("child1");
vmiArtboard->asset(ch2Source);
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
// Change source to default external bindable artboard
auto source1 = sourceFile->bindableArtboardDefault();
vmiArtboard->asset(source1);
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
// Change source to local artboard by index
vmiArtboard->propertyValue(1);
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
// Change source to invalid index
vmiArtboard->propertyValue(10);
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
CHECK(silver.matches("data_bind_artboard_input"));
}
TEST_CASE("Data bind external artboard with no initial source artboard",
"[silver]")
{
SerializingFactory silver;
auto file =
ReadRiveFile("assets/databind_external_artboard_main.riv", &silver);
auto file2 =
ReadRiveFile("assets/databind_external_artboard_child.riv", &silver);
auto artboard = file->artboardDefault();
REQUIRE(artboard != nullptr);
silver.frameSize(artboard->width(), artboard->height());
auto stateMachine = artboard->stateMachineAt(0);
auto renderer = silver.makeRenderer();
stateMachine->advanceAndApply(0.0f);
artboard->draw(renderer.get());
silver.addFrame();
auto vmi = file->createViewModelInstance(artboard.get());
stateMachine->bindViewModelInstance(vmi);
auto artboardProp =
vmi->propertyValue("ab")->as<ViewModelInstanceArtboard>();
auto insertedArtboard = file2->bindableArtboardNamed("ExternalChild");
artboardProp->asset(insertedArtboard);
auto mainVM = file2->viewModelByName("Child");
auto mainVMI = mainVM->createInstanceFromName("Instance");
auto newCircleProp = mainVMI->propertyString("label");
vmi->replaceViewModelByName("child", mainVMI->instance());
newCircleProp->value("updated label");
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
CHECK(silver.matches("databind_external_artboard_main"));
}
TEST_CASE("Data bound artboard with view model instance resets its properties",
"[silver]")
{
SerializingFactory silver;
auto file = ReadRiveFile("assets/bindable_artboard_nesty.riv", &silver);
auto file2 = ReadRiveFile("assets/bindable_artboard_child.riv", &silver);
auto artboard = file->artboardDefault();
REQUIRE(artboard != nullptr);
silver.frameSize(artboard->width(), artboard->height());
auto renderer = silver.makeRenderer();
auto stateMachine = artboard->stateMachineAt(0);
auto vmi = file->createViewModelInstance(artboard.get());
auto artboardProp =
vmi->propertyValue("someArtboard")->as<ViewModelInstanceArtboard>();
auto insertedArtboard = file2->bindableArtboardNamed("Artboard");
auto viewModelRenamed = file2->viewModel("ViewModelRenamed");
auto viewModelRenamedInstance = viewModelRenamed->createInstance();
artboardProp->asset(insertedArtboard);
artboardProp->boundViewModelInstance(viewModelRenamedInstance);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
silver.addFrame();
stateMachine->pointerDown(rive::Vec2D(250, 250));
stateMachine->pointerUp(rive::Vec2D(250, 250));
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
int frames = (int)(0.5f / 0.1f);
for (int i = 0; i < frames; i++)
{
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
}
silver.addFrame();
stateMachine->pointerDown(rive::Vec2D(250, 250));
stateMachine->pointerUp(rive::Vec2D(250, 250));
stateMachine->advanceAndApply(0.016f);
artboard->draw(renderer.get());
CHECK(silver.matches("bindable_artboard_nesty"));
}