Nnnnn relative data bind all paths (#11346) 41d316c675

* StateMachineListener relative view model property path
* StateMachineFireTrigger relative view model property path
* ScriptInputViewModelProperty relative view model property path

Co-authored-by: hernan <hernan@rive.app>
This commit is contained in:
bodymovin
2026-01-02 21:11:44 +00:00
parent c3ff521ac8
commit 15a922714f
20 changed files with 259 additions and 51 deletions

View File

@@ -1 +1 @@
56f08da6b1998d346fa791770d7a62a50466e0f7
41d316c675491aa5941a6342e859d6c37bf457c0

View File

@@ -16,6 +16,16 @@
"string": "viewmodelpathids"
},
"description": "Path to the selected view model trigger property."
},
"isDataBindPathRelative": {
"type": "bool",
"initialValue": "false",
"key": {
"int": 923,
"string": "isdatabindpathrelative"
},
"description": "Whether the data bind path is relative",
"runtime": false
}
}
}

View File

@@ -47,6 +47,16 @@
"string": "viewmodelpathids"
},
"description": "Path to the selected view model trigger property if listenerType is a viewModel trigger."
},
"isDataBindPathRelative": {
"type": "bool",
"initialValue": "false",
"key": {
"int": 924,
"string": "isdatabindpathrelative"
},
"description": "Whether the data bind path is relative",
"runtime": false
}
}
}

View File

@@ -26,6 +26,16 @@
"string": "databindpathids"
},
"description": "Path to the selected property."
},
"isDataBindPathRelative": {
"type": "bool",
"initialValue": "false",
"key": {
"int": 922,
"string": "isdatabindpathrelative"
},
"description": "Whether the data bind path is relative",
"runtime": false
}
}
}

View File

@@ -1,19 +1,19 @@
#ifndef _RIVE_STATE_MACHINE_FIRE_TRIGGER_HPP_
#define _RIVE_STATE_MACHINE_FIRE_TRIGGER_HPP_
#include "rive/generated/animation/state_machine_fire_trigger_base.hpp"
#include "rive/data_bind_path_referencer.hpp"
#include <stdio.h>
namespace rive
{
class StateMachineFireTrigger : public StateMachineFireTriggerBase
class StateMachineFireTrigger : public StateMachineFireTriggerBase,
public DataBindPathReferencer
{
public:
void perform(StateMachineInstance* stateMachineInstance) const override;
void decodeViewModelPathIds(Span<const uint8_t> value) override;
void copyViewModelPathIds(
const StateMachineFireTriggerBase& object) override;
protected:
std::vector<uint32_t> m_viewModelPathIdsBuffer;
StatusCode import(ImportStack& importStack) override;
};
} // namespace rive

View File

@@ -3,6 +3,7 @@
#include "rive/generated/animation/state_machine_listener_base.hpp"
#include "rive/listener_type.hpp"
#include "rive/math/vec2d.hpp"
#include "rive/data_bind_path_referencer.hpp"
namespace rive
{
@@ -10,7 +11,8 @@ class Shape;
class StateMachineListenerImporter;
class ListenerAction;
class StateMachineInstance;
class StateMachineListener : public StateMachineListenerBase
class StateMachineListener : public StateMachineListenerBase,
public DataBindPathReferencer
{
friend class StateMachineListenerImporter;
@@ -32,13 +34,7 @@ public:
Vec2D previousPosition) const;
void decodeViewModelPathIds(Span<const uint8_t> value) override;
void copyViewModelPathIds(const StateMachineListenerBase& object) override;
std::vector<uint32_t> viewModelPathIdsBuffer() const
{
return m_viewModelPathIdsBuffer;
}
protected:
std::vector<uint32_t> m_viewModelPathIdsBuffer;
std::vector<uint32_t> viewModelPathIdsBuffer() const;
private:
void addAction(std::unique_ptr<ListenerAction>);

View File

@@ -24,6 +24,7 @@ public:
ViewModelInstanceValue* getRelativeViewModelProperty(
const std::vector<uint32_t> path,
DataResolver* resolver) const;
ViewModelInstanceValue* getViewModelProperty(DataBindPath* dataBindPath);
rcp<ViewModelInstance> getViewModelInstance(
const std::vector<uint32_t> path) const;
rcp<ViewModelInstance> getViewModelInstance(DataBindPath*) const;

View File

@@ -3,26 +3,24 @@
#include "rive/generated/script_input_viewmodel_property_base.hpp"
#include "rive/assets/script_asset.hpp"
#include "rive/scripted/scripted_object.hpp"
#include "rive/data_bind_path_referencer.hpp"
#include <stdio.h>
namespace rive
{
class ViewModelInstanceValue;
class ScriptInputViewModelProperty : public ScriptInputViewModelPropertyBase,
public ScriptInput
public ScriptInput,
public DataBindPathReferencer
{
private:
ViewModelInstanceValue* m_viewModelInstanceValue;
protected:
std::vector<uint32_t> m_DataBindPathIdsBuffer;
public:
~ScriptInputViewModelProperty();
void decodeDataBindPathIds(Span<const uint8_t> value) override;
void copyDataBindPathIds(
const ScriptInputViewModelPropertyBase& object) override;
std::vector<uint32_t> dataBindPathIds() { return m_DataBindPathIdsBuffer; };
void initScriptedValue() override;
bool validateForScriptInit() override;
StatusCode import(ImportStack& importStack) override;

View File

@@ -8,10 +8,9 @@ void StateMachineFireTrigger::perform(
StateMachineInstance* stateMachineInstance) const
{
auto dataContext = stateMachineInstance->dataContext();
if (dataContext != nullptr)
if (dataContext != nullptr && m_dataBindPath)
{
auto vmProp =
dataContext->getViewModelProperty(m_viewModelPathIdsBuffer);
auto vmProp = dataContext->getViewModelProperty(m_dataBindPath);
if (vmProp && vmProp->is<ViewModelInstanceTrigger>())
{
vmProp->as<ViewModelInstanceTrigger>()->trigger();
@@ -19,19 +18,19 @@ void StateMachineFireTrigger::perform(
}
}
StatusCode StateMachineFireTrigger::import(ImportStack& importStack)
{
importDataBindPath(importStack);
return Super::import(importStack);
}
void StateMachineFireTrigger::decodeViewModelPathIds(Span<const uint8_t> value)
{
BinaryReader reader(value);
while (!reader.reachedEnd())
{
auto val = reader.readVarUintAs<uint32_t>();
m_viewModelPathIdsBuffer.push_back(val);
}
decodeDataBindPath(value);
}
void StateMachineFireTrigger::copyViewModelPathIds(
const StateMachineFireTriggerBase& object)
{
m_viewModelPathIdsBuffer =
object.as<StateMachineFireTrigger>()->m_viewModelPathIdsBuffer;
copyDataBindPath(object.as<StateMachineFireTrigger>()->dataBindPath());
}

View File

@@ -1014,8 +1014,8 @@ public:
void bindFromContext(DataContext* dataContext)
{
clearDataContext();
auto path = m_listener->viewModelPathIdsBuffer();
auto vmProp = dataContext->getViewModelProperty(path);
auto vmProp =
dataContext->getViewModelProperty(m_listener->dataBindPath());
if (vmProp != nullptr)
{
m_viewModelInstanceValue = rive::ref_rcp(vmProp);
@@ -1341,8 +1341,8 @@ StateMachineInstance::StateMachineInstance(const StateMachine* machine,
// We are only storing in this unordered map data binds that are
// targetting the source. For now, this is only the case for
// listener actions.
if (static_cast<DataBindFlags>(dataBindClone->flags()) ==
DataBindFlags::ToSource)
if ((static_cast<DataBindFlags>(dataBindClone->flags()) &
DataBindFlags::ToSource) == DataBindFlags::ToSource)
{
m_bindableDataBindsToSource[bindablePropertyClone] =
dataBindClone;

View File

@@ -19,6 +19,7 @@ void StateMachineListener::addAction(std::unique_ptr<ListenerAction> action)
StatusCode StateMachineListener::import(ImportStack& importStack)
{
importDataBindPath(importStack);
auto stateMachineImporter =
importStack.latest<StateMachineImporter>(StateMachineBase::typeKey);
if (stateMachineImporter == nullptr)
@@ -53,17 +54,16 @@ void StateMachineListener::performChanges(
void StateMachineListener::decodeViewModelPathIds(Span<const uint8_t> value)
{
BinaryReader reader(value);
while (!reader.reachedEnd())
{
auto val = reader.readVarUintAs<uint32_t>();
m_viewModelPathIdsBuffer.push_back(val);
}
decodeDataBindPath(value);
}
void StateMachineListener::copyViewModelPathIds(
const StateMachineListenerBase& object)
{
m_viewModelPathIdsBuffer =
object.as<StateMachineListener>()->m_viewModelPathIdsBuffer;
copyDataBindPath(object.as<StateMachineListener>()->dataBindPath());
}
std::vector<uint32_t> StateMachineListener::viewModelPathIdsBuffer() const
{
return dataBindPath()->path();
}

View File

@@ -42,7 +42,7 @@ const std::vector<uint32_t>& DataBindPath::resolvedPath()
return m_pathBuffer;
}
auto dataResolver = m_file->dataResolver();
if (dataResolver)
if (dataResolver && m_pathBuffer.size() == 1)
{
auto pathId = m_pathBuffer[0];
m_pathBuffer = dataResolver->resolvePath(pathId);

View File

@@ -98,6 +98,26 @@ skip_relative_path:
return nullptr;
}
ViewModelInstanceValue* DataContext::getViewModelProperty(
DataBindPath* dataBindPath)
{
if (dataBindPath->isRelative())
{
auto file = dataBindPath->file();
if (file)
{
auto resolver = file->dataResolver();
if (resolver)
{
return getRelativeViewModelProperty(
dataBindPath->resolvedPath(),
resolver);
}
}
}
return getViewModelProperty(dataBindPath->path());
}
rcp<ViewModelInstance> DataContext::getViewModelInstance(
const std::vector<uint32_t> path) const
{

View File

@@ -20,19 +20,13 @@ ScriptInputViewModelProperty::~ScriptInputViewModelProperty()
void ScriptInputViewModelProperty::decodeDataBindPathIds(
Span<const uint8_t> value)
{
BinaryReader reader(value);
while (!reader.reachedEnd())
{
auto val = reader.readVarUintAs<uint32_t>();
m_DataBindPathIdsBuffer.push_back(val);
}
decodeDataBindPath(value);
}
void ScriptInputViewModelProperty::copyDataBindPathIds(
const ScriptInputViewModelPropertyBase& object)
{
m_DataBindPathIdsBuffer =
object.as<ScriptInputViewModelProperty>()->m_DataBindPathIdsBuffer;
copyDataBindPath(object.as<ScriptInputViewModelProperty>()->dataBindPath());
}
void ScriptInputViewModelProperty::initScriptedValue()
@@ -62,7 +56,11 @@ bool ScriptInputViewModelProperty::validateForScriptInit()
{
return false;
}
auto instanceValue = dataContext->getViewModelProperty(dataBindPathIds());
if (m_dataBindPath == nullptr)
{
return false;
}
auto instanceValue = dataContext->getViewModelProperty(m_dataBindPath);
if (instanceValue == nullptr)
{
return false;
@@ -78,6 +76,7 @@ bool ScriptInputViewModelProperty::validateForScriptInit()
StatusCode ScriptInputViewModelProperty::import(ImportStack& importStack)
{
importDataBindPath(importStack);
auto importer =
importStack.latest<ScriptedObjectImporter>(ScriptedDrawable::typeKey);
if (importer == nullptr)

View File

@@ -2046,6 +2046,171 @@ TEST_CASE("Relative data binding view model path", "[silver]")
CHECK(silver.matches("relative_data_bind_path"));
}
TEST_CASE("Relative data binding view model state machine listener", "[silver]")
{
SerializingFactory silver;
auto file = ReadRiveFile("assets/relative_data_bind_path.riv", &silver);
auto artboard = file->artboardNamed("listener");
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);
auto numProp = vmi->propertyValue("num")->as<ViewModelInstanceNumber>();
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
silver.addFrame();
numProp->propertyValue(100);
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
silver.addFrame();
}
// Next bind it to a different view model with the same shape
{
auto vm = file->viewModel("SML_VM2");
auto vmi = file->createDefaultViewModelInstance(vm);
auto numProp = vmi->propertyValue("num")->as<ViewModelInstanceNumber>();
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
silver.addFrame();
numProp->propertyValue(100);
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
}
CHECK(silver.matches("relative_data_bind_path-listener"));
}
TEST_CASE("Relative data binding view model state machine fire trigger",
"[silver]")
{
SerializingFactory silver;
auto file = ReadRiveFile("assets/relative_data_bind_path.riv", &silver);
auto artboard = file->artboardNamed("fire-trigger");
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);
auto resetProp =
vmi->propertyValue("reset")->as<ViewModelInstanceTrigger>();
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
silver.addFrame();
resetProp->trigger();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
silver.addFrame();
}
// Next bind it to a different view model with the same shape
{
auto vm = file->viewModel("SMFT-VM2");
auto vmi = file->createDefaultViewModelInstance(vm);
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
silver.addFrame();
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
}
CHECK(silver.matches("relative_data_bind_path-fire-trigger"));
}
TEST_CASE("Relative data binding view model scripted input", "[silver]")
{
SerializingFactory silver;
auto file = ReadRiveFile("assets/relative_data_bind_path.riv", &silver);
auto artboard = file->artboardNamed("scripted-input");
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);
auto child =
vmi->propertyValue("child")->as<ViewModelInstanceViewModel>();
auto boo = child->referenceViewModelInstance()
->propertyValue("boo")
->as<ViewModelInstanceBoolean>();
auto paused = child->referenceViewModelInstance()
->propertyValue("paused")
->as<ViewModelInstanceBoolean>();
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
silver.addFrame();
paused->propertyValue(false);
stateMachine->advanceAndApply(1.0f);
artboard->draw(renderer.get());
silver.addFrame();
paused->propertyValue(true);
boo->propertyValue(false);
stateMachine->advanceAndApply(1.0f);
artboard->draw(renderer.get());
silver.addFrame();
}
// Next bind it to a different view model with the same shape
{
auto vm = file->viewModel("SI-VM2");
auto vmi = file->createDefaultViewModelInstance(vm);
auto child =
vmi->propertyValue("child")->as<ViewModelInstanceViewModel>();
auto boo = child->referenceViewModelInstance()
->propertyValue("boo")
->as<ViewModelInstanceBoolean>();
auto paused = child->referenceViewModelInstance()
->propertyValue("paused")
->as<ViewModelInstanceBoolean>();
stateMachine->bindViewModelInstance(vmi);
stateMachine->advanceAndApply(0.1f);
artboard->draw(renderer.get());
silver.addFrame();
paused->propertyValue(false);
stateMachine->advanceAndApply(1.0f);
artboard->draw(renderer.get());
silver.addFrame();
paused->propertyValue(true);
boo->propertyValue(false);
stateMachine->advanceAndApply(1.0f);
artboard->draw(renderer.get());
}
CHECK(silver.matches("relative_data_bind_path-scripted-input"));
}
TEST_CASE("Listen to view model value changes in state machines", "[silver]")
{