Nnnnn add scripted inputs (#11494) a8418441ca
* chore: add support for inputs in scripted istener and conditions
Co-authored-by: hernan <hernan@rive.app>
diff --git a/.rive_head b/.rive_head
index 93a602c..6760971 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-853b2a08b5743038ac40d2d4b80697d8983a23df
+a8418441ca00176a69e5cdde02ff4204a090b238
diff --git a/include/rive/animation/scripted_listener_action.hpp b/include/rive/animation/scripted_listener_action.hpp
index 32e615f..8e940a4 100644
--- a/include/rive/animation/scripted_listener_action.hpp
+++ b/include/rive/animation/scripted_listener_action.hpp
@@ -9,6 +9,8 @@
public ScriptedObject
{
public:
+ ~ScriptedListenerAction();
+ virtual void disposeScriptInputs() override;
void perform(StateMachineInstance* stateMachineInstance,
Vec2D position,
Vec2D previousPosition,
@@ -30,7 +32,8 @@
Component* component() override { return nullptr; }
StatusCode import(ImportStack& importStack) override;
Core* clone() const override;
- ScriptedObject* cloneScriptedObject() const override;
+ ScriptedObject* cloneScriptedObject(DataBindContainer*) const override;
+ void addProperty(CustomProperty* prop) override;
};
} // namespace rive
diff --git a/include/rive/animation/scripted_transition_condition.hpp b/include/rive/animation/scripted_transition_condition.hpp
index e26683a..f955987 100644
--- a/include/rive/animation/scripted_transition_condition.hpp
+++ b/include/rive/animation/scripted_transition_condition.hpp
@@ -9,6 +9,8 @@
public ScriptedObject
{
public:
+ ~ScriptedTransitionCondition();
+ virtual void disposeScriptInputs() override;
bool evaluate(const StateMachineInstance* stateMachineInstance,
StateMachineLayerInstance* layerInstance) const override;
bool evaluateStateful(const StateMachineInstance* stateMachineInstance,
@@ -25,7 +27,8 @@
Component* component() override { return nullptr; }
StatusCode import(ImportStack& importStack) override;
Core* clone() const override;
- ScriptedObject* cloneScriptedObject() const override;
+ ScriptedObject* cloneScriptedObject(DataBindContainer*) const override;
+ void addProperty(CustomProperty* prop) override;
};
} // namespace rive
diff --git a/include/rive/animation/state_machine_instance.hpp b/include/rive/animation/state_machine_instance.hpp
index 18f3437..1fd516f 100644
--- a/include/rive/animation/state_machine_instance.hpp
+++ b/include/rive/animation/state_machine_instance.hpp
@@ -241,7 +241,7 @@
std::unordered_map<BindableProperty*, BindableProperty*>
m_bindablePropertyInstances;
std::unordered_map<const ScriptedObject*, ScriptedObject*>
- m_scriptedListenerActionsMap;
+ m_scriptedObjectsMap;
std::unordered_map<BindableProperty*, DataBind*>
m_bindableDataBindsToTarget;
std::unordered_map<BindableProperty*, DataBind*>
diff --git a/include/rive/assets/script_asset.hpp b/include/rive/assets/script_asset.hpp
index d62f942..465edd1 100644
--- a/include/rive/assets/script_asset.hpp
+++ b/include/rive/assets/script_asset.hpp
@@ -39,14 +39,19 @@
protected:
ScriptedObject* m_scriptedObject = nullptr;
DataBind* m_dataBind = nullptr;
+ bool m_ownsDataBind = false;
public:
- virtual ~ScriptInput() {};
+ virtual ~ScriptInput();
virtual void initScriptedValue();
virtual bool validateForScriptInit() = 0;
static ScriptInput* from(Core* component);
DataBind* dataBind() { return m_dataBind; }
- void dataBind(DataBind* dataBind) { m_dataBind = dataBind; }
+ void dataBind(DataBind* dataBind, bool ownsDataBind = false)
+ {
+ m_dataBind = dataBind;
+ m_ownsDataBind = ownsDataBind;
+ }
ScriptedObject* scriptedObject() { return m_scriptedObject; }
void scriptedObject(ScriptedObject* object) { m_scriptedObject = object; }
};
diff --git a/include/rive/scripted/scripted_object.hpp b/include/rive/scripted/scripted_object.hpp
index 18e9182..d7d529d 100644
--- a/include/rive/scripted/scripted_object.hpp
+++ b/include/rive/scripted/scripted_object.hpp
@@ -18,6 +18,7 @@
class Component;
class DataContext;
class ViewModelInstanceValue;
+class DataBindContainer;
class ScriptedObject : public FileAssetReferencer,
public CustomPropertyContainer,
@@ -63,7 +64,11 @@
virtual ScriptProtocol scriptProtocol() = 0;
int self() { return m_self; }
virtual Component* component() = 0;
- virtual ScriptedObject* cloneScriptedObject() const { return nullptr; }
+ virtual ScriptedObject* cloneScriptedObject(DataBindContainer*) const
+ {
+ return nullptr;
+ }
+ void cloneProperties(CustomPropertyContainer*, DataBindContainer*) const;
};
} // namespace rive
diff --git a/src/animation/scripted_listener_action.cpp b/src/animation/scripted_listener_action.cpp
index db217cc..0026f60 100644
--- a/src/animation/scripted_listener_action.cpp
+++ b/src/animation/scripted_listener_action.cpp
@@ -1,9 +1,28 @@
#include "rive/animation/scripted_listener_action.hpp"
#include "rive/animation/state_machine_instance.hpp"
#include "rive/importers/state_machine_importer.hpp"
+#include "rive/data_bind/data_bind.hpp"
using namespace rive;
+ScriptedListenerAction::~ScriptedListenerAction() { disposeScriptInputs(); }
+
+void ScriptedListenerAction::disposeScriptInputs()
+{
+ auto props = m_customProperties;
+ ScriptedObject::disposeScriptInputs();
+ for (auto prop : props)
+ {
+ auto scriptInput = ScriptInput::from(prop);
+ if (scriptInput != nullptr)
+ {
+ // ScriptedDataConverters need to delete their own inputs
+ // because they are not components
+ delete scriptInput;
+ }
+ }
+}
+
// Note: performStateful is the actual instance of the ScriptedListenerAction
// that will run the script. perform itself will look for the map between the
// stateless and the stateful instances of this class.
@@ -86,17 +105,23 @@
{
twin->setAsset(m_fileAsset);
}
- for (auto prop : m_customProperties)
- {
- auto clonedValue = prop->clone()->as<CustomProperty>();
- twin->addProperty(clonedValue);
- }
return twin;
}
-ScriptedObject* ScriptedListenerAction::cloneScriptedObject() const
+ScriptedObject* ScriptedListenerAction::cloneScriptedObject(
+ DataBindContainer* dataBindContainer) const
{
auto clonedScriptedObject = clone()->as<ScriptedListenerAction>();
+ cloneProperties(clonedScriptedObject, dataBindContainer);
clonedScriptedObject->reinit();
return clonedScriptedObject;
+}
+void ScriptedListenerAction::addProperty(CustomProperty* prop)
+{
+ auto scriptInput = ScriptInput::from(prop);
+ if (scriptInput != nullptr)
+ {
+ scriptInput->scriptedObject(this);
+ }
+ CustomPropertyContainer::addProperty(prop);
}
\ No newline at end of file
diff --git a/src/animation/scripted_transition_condition.cpp b/src/animation/scripted_transition_condition.cpp
index 8ea3eda..1ab848d 100644
--- a/src/animation/scripted_transition_condition.cpp
+++ b/src/animation/scripted_transition_condition.cpp
@@ -4,9 +4,31 @@
using namespace rive;
-// Note: performStateful is the actual instance of the ScriptedListenerAction
-// that will run the script. perform itself will look for the map between the
-// stateless and the stateful instances of this class.
+ScriptedTransitionCondition::~ScriptedTransitionCondition()
+{
+ disposeScriptInputs();
+}
+
+void ScriptedTransitionCondition::disposeScriptInputs()
+{
+ auto props = m_customProperties;
+ ScriptedObject::disposeScriptInputs();
+ for (auto prop : props)
+ {
+ auto scriptInput = ScriptInput::from(prop);
+ if (scriptInput != nullptr)
+ {
+ // ScriptedDataConverters need to delete their own inputs
+ // because they are not components
+ delete scriptInput;
+ }
+ }
+}
+
+// Note: evaluateStateful is the actual instance of the
+// ScriptedTransitionCondition that will run the script. evaluate itself will
+// look for the map between the stateless and the stateful instances of this
+// class.
bool ScriptedTransitionCondition::evaluateStateful(
const StateMachineInstance* stateMachineInstance,
StateMachineLayerInstance* layerInstance) const
@@ -85,17 +107,24 @@
{
twin->setAsset(m_fileAsset);
}
- for (auto prop : m_customProperties)
- {
- auto clonedValue = prop->clone()->as<CustomProperty>();
- twin->addProperty(clonedValue);
- }
return twin;
}
-ScriptedObject* ScriptedTransitionCondition::cloneScriptedObject() const
+ScriptedObject* ScriptedTransitionCondition::cloneScriptedObject(
+ DataBindContainer* dataBindContainer) const
{
auto clonedScriptedObject = clone()->as<ScriptedTransitionCondition>();
+ cloneProperties(clonedScriptedObject, dataBindContainer);
clonedScriptedObject->reinit();
return clonedScriptedObject;
+}
+
+void ScriptedTransitionCondition::addProperty(CustomProperty* prop)
+{
+ auto scriptInput = ScriptInput::from(prop);
+ if (scriptInput != nullptr)
+ {
+ scriptInput->scriptedObject(this);
+ }
+ CustomPropertyContainer::addProperty(prop);
}
\ No newline at end of file
diff --git a/src/animation/state_machine_instance.cpp b/src/animation/state_machine_instance.cpp
index 226bb45..b382be8 100644
--- a/src/animation/state_machine_instance.cpp
+++ b/src/animation/state_machine_instance.cpp
@@ -1487,8 +1487,8 @@
// Initialize local instances of ScriptedObjects
for (auto& scriptedOb : machine->scriptedObjects())
{
- m_scriptedListenerActionsMap[scriptedOb] =
- scriptedOb->cloneScriptedObject();
+ m_scriptedObjectsMap[scriptedOb] =
+ scriptedOb->cloneScriptedObject(this);
}
sortHitComponents();
}
@@ -1496,8 +1496,8 @@
ScriptedObject* StateMachineInstance::scriptedObject(
const ScriptedObject* source) const
{
- auto itr = m_scriptedListenerActionsMap.find(source);
- if (itr != m_scriptedListenerActionsMap.end())
+ auto itr = m_scriptedObjectsMap.find(source);
+ if (itr != m_scriptedObjectsMap.end())
{
return itr->second;
}
@@ -1527,12 +1527,12 @@
delete listenerViewModel;
}
m_bindablePropertyInstances.clear();
- for (auto& pair : m_scriptedListenerActionsMap)
+ for (auto& pair : m_scriptedObjectsMap)
{
delete pair.second;
pair.second = nullptr;
}
- m_scriptedListenerActionsMap.clear();
+ m_scriptedObjectsMap.clear();
}
void StateMachineInstance::removeEventListeners()
@@ -1843,7 +1843,7 @@
{
listenerViewModel->bindFromContext(dataContext);
}
- for (auto& scriptedObjectItr : m_scriptedListenerActionsMap)
+ for (auto& scriptedObjectItr : m_scriptedObjectsMap)
{
scriptedObjectItr.second->dataContext(dataContext);
}
diff --git a/src/assets/script_asset.cpp b/src/assets/script_asset.cpp
index bca0765..ed5199e 100644
--- a/src/assets/script_asset.cpp
+++ b/src/assets/script_asset.cpp
@@ -16,9 +16,18 @@
#include "rive/scripted/scripted_drawable.hpp"
#include "rive/scripted/scripted_layout.hpp"
#include "rive/scripted/scripted_object.hpp"
+#include "rive/data_bind/data_bind.hpp"
using namespace rive;
+ScriptInput::~ScriptInput()
+{
+ if (m_ownsDataBind && m_dataBind)
+ {
+ delete m_dataBind;
+ }
+}
+
ScriptInput* ScriptInput::from(Core* component)
{
switch (component->coreType())
diff --git a/src/data_bind/data_bind.cpp b/src/data_bind/data_bind.cpp
index 18c5761..a4f9bc0 100644
--- a/src/data_bind/data_bind.cpp
+++ b/src/data_bind/data_bind.cpp
@@ -64,7 +64,7 @@
auto input = ScriptInput::from(target());
if (input != nullptr)
{
- input->dataBind(this);
+ bool ownsDataBind = true;
if (input->scriptedObject() != nullptr)
{
if (input->scriptedObject()->component() != nullptr)
@@ -73,10 +73,12 @@
ArtboardBase::typeKey);
if (importer != nullptr)
{
+ ownsDataBind = false;
importer->addDataBind(this);
}
}
}
+ input->dataBind(this, ownsDataBind);
}
else if (target()->is<DataConverter>())
{
diff --git a/src/file.cpp b/src/file.cpp
index a7ef9a0..a72a351 100644
--- a/src/file.cpp
+++ b/src/file.cpp
@@ -548,6 +548,8 @@
case ScriptedDrawable::typeKey:
case ScriptedLayout::typeKey:
case ScriptedPathEffect::typeKey:
+ case ScriptedListenerAction::typeKey:
+ case ScriptedTransitionCondition::typeKey:
{
auto scriptedObject = ScriptedObject::from(object);
if (scriptedObject != nullptr)
diff --git a/src/scripted/scripted_object.cpp b/src/scripted/scripted_object.cpp
index 00066b3..4ad32d1 100644
--- a/src/scripted/scripted_object.cpp
+++ b/src/scripted/scripted_object.cpp
@@ -5,11 +5,14 @@
#include "rive/artboard.hpp"
#include "rive/file.hpp"
#include "rive/script_input_artboard.hpp"
+#include "rive/animation/scripted_listener_action.hpp"
+#include "rive/animation/scripted_transition_condition.hpp"
#include "rive/scripted/scripted_data_converter.hpp"
#include "rive/scripted/scripted_drawable.hpp"
#include "rive/scripted/scripted_layout.hpp"
#include "rive/scripted/scripted_path_effect.hpp"
#include "rive/scripted/scripted_object.hpp"
+#include "rive/data_bind/data_bind.hpp"
using namespace rive;
@@ -25,6 +28,10 @@
return object->as<ScriptedLayout>();
case ScriptedPathEffect::typeKey:
return object->as<ScriptedPathEffect>();
+ case ScriptedListenerAction::typeKey:
+ return object->as<ScriptedListenerAction>();
+ case ScriptedTransitionCondition::typeKey:
+ return object->as<ScriptedTransitionCondition>();
}
return nullptr;
}
@@ -414,4 +421,32 @@
}
}
-void ScriptedObject::markNeedsUpdate() {}
\ No newline at end of file
+void ScriptedObject::markNeedsUpdate() {}
+
+void ScriptedObject::cloneProperties(CustomPropertyContainer* twin,
+ DataBindContainer* dataBindContainer) const
+{
+
+ for (auto prop : m_customProperties)
+ {
+ auto clonedValue = prop->clone()->as<CustomProperty>();
+ twin->addProperty(clonedValue);
+ auto input = ScriptInput::from(prop);
+ if (input != nullptr)
+ {
+ auto dataBind = input->dataBind();
+ if (dataBind)
+ {
+ auto dataBindClone = static_cast<DataBind*>(dataBind->clone());
+ dataBindClone->file(dataBind->file());
+ if (dataBind->converter() != nullptr)
+ {
+ dataBindClone->converter(
+ dataBind->converter()->clone()->as<DataConverter>());
+ }
+ dataBindClone->target(clonedValue);
+ dataBindContainer->addDataBind(dataBindClone);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/unit_tests/assets/listener_action_inputs.riv b/tests/unit_tests/assets/listener_action_inputs.riv
new file mode 100644
index 0000000..aa2bc7e
--- /dev/null
+++ b/tests/unit_tests/assets/listener_action_inputs.riv
Binary files differ
diff --git a/tests/unit_tests/runtime/scripting/scripting_listener_action_test.cpp b/tests/unit_tests/runtime/scripting/scripting_listener_action_test.cpp
index 38889b8..17eb1cb 100644
--- a/tests/unit_tests/runtime/scripting/scripting_listener_action_test.cpp
+++ b/tests/unit_tests/runtime/scripting/scripting_listener_action_test.cpp
@@ -43,4 +43,36 @@
artboard->draw(renderer.get());
CHECK(silver.matches("scripted_listener_action"));
+}
+
+TEST_CASE("Listener action inputs", "[silver]")
+{
+ SerializingFactory silver;
+ auto file = ReadRiveFile("assets/listener_action_inputs.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);
+
+ stateMachine->bindViewModelInstance(vmi);
+ auto renderer = silver.makeRenderer();
+ stateMachine->advanceAndApply(0.016f);
+ artboard->draw(renderer.get());
+ stateMachine->pointerDown(
+ rive::Vec2D(artboard->width() / 2.0f, artboard->height() / 2.0f),
+ 3);
+ stateMachine->pointerUp(
+ rive::Vec2D(artboard->width() / 2.0f, artboard->height() / 2.0f),
+ 3);
+ silver.addFrame();
+ stateMachine->advanceAndApply(0.016f);
+ artboard->draw(renderer.get());
+
+ CHECK(silver.matches("listener_action_inputs"));
}
\ No newline at end of file
diff --git a/tests/unit_tests/silvers/listener_action_inputs.sriv b/tests/unit_tests/silvers/listener_action_inputs.sriv
new file mode 100644
index 0000000..d8531ff
--- /dev/null
+++ b/tests/unit_tests/silvers/listener_action_inputs.sriv
Binary files differ