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