add listener actions support for databind

adds support for manipulating view model values from state machines through listener actions

https://github.com/user-attachments/assets/7ddd4cd2-a04d-4d33-98de-b1f29aa24755

Diffs=
d9f5701ec add listener actions support for databind (#7710)

Co-authored-by: hernan <hernan@rive.app>
diff --git a/.rive_head b/.rive_head
index 791ad29..a891ec7 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-dcb1651300467e52124259dcdf0f61acf34148b4
+d9f5701ec10c0d0eecb0e3d2f5b0ee80499c746d
diff --git a/dev/defs/animation/listener_viewmodel_change.json b/dev/defs/animation/listener_viewmodel_change.json
new file mode 100644
index 0000000..96ffb5b
--- /dev/null
+++ b/dev/defs/animation/listener_viewmodel_change.json
@@ -0,0 +1,38 @@
+{
+  "name": "ListenerViewModelChange",
+  "key": {
+    "int": 487,
+    "string": "listenerviewmodelchange"
+  },
+  "extends": "animation/listener_action.json",
+  "properties": {
+    "bindablePropertyId": {
+      "type": "Id",
+      "key": {
+        "int": 657,
+        "string": "bindablepropertyid"
+      },
+      "description": "Id of the bindable property",
+      "runtime": false
+    },
+    "fromViewModelProperty": {
+      "type": "bool",
+      "key": {
+        "int": 658,
+        "string": "fromviewmodelproperty"
+      },
+      "description": "Whether it is using another view model property as the value",
+      "runtime": false
+    },
+    "fromDataBindId": {
+      "type": "Id",
+      "initialValue": "Core.missingId",
+      "key": {
+        "int": 659,
+        "string": "fromdatabindid"
+      },
+      "description": "Id of the data bind used if the value is taken from another view model property (only needed if fromViewModelProperty is true)",
+      "runtime": false
+    }
+  }
+}
\ No newline at end of file
diff --git a/include/rive/animation/listener_viewmodel_change.hpp b/include/rive/animation/listener_viewmodel_change.hpp
new file mode 100644
index 0000000..a9b82a1
--- /dev/null
+++ b/include/rive/animation/listener_viewmodel_change.hpp
@@ -0,0 +1,21 @@
+#ifndef _RIVE_LISTENER_VIEW_MODEL_CHANGE_HPP_
+#define _RIVE_LISTENER_VIEW_MODEL_CHANGE_HPP_
+#include "rive/generated/animation/listener_viewmodel_change_base.hpp"
+#include "rive/data_bind/bindable_property.hpp"
+#include <stdio.h>
+namespace rive
+{
+class ListenerViewModelChange : public ListenerViewModelChangeBase
+{
+public:
+    void perform(StateMachineInstance* stateMachineInstance,
+                 Vec2D position,
+                 Vec2D previousPosition) const override;
+    StatusCode import(ImportStack& importStack) override;
+
+private:
+    BindableProperty* m_bindableProperty;
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/animation/state_machine_instance.hpp b/include/rive/animation/state_machine_instance.hpp
index 093d05b..ce34a03 100644
--- a/include/rive/animation/state_machine_instance.hpp
+++ b/include/rive/animation/state_machine_instance.hpp
@@ -137,6 +137,7 @@
     const EventReport reportedEventAt(std::size_t index) const;
     bool playsAudio() override { return true; }
     BindableProperty* bindablePropertyInstance(BindableProperty* bindableProperty);
+    DataBind* bindableDataBind(BindableProperty* bindableProperty);
 #ifdef TESTING
     size_t hitComponentsCount() { return m_hitComponents.size(); };
     HitComponent* hitComponent(size_t index)
@@ -164,6 +165,7 @@
     NestedArtboard* m_parentNestedArtboard = nullptr;
     std::vector<DataBind*> m_dataBinds;
     std::unordered_map<BindableProperty*, BindableProperty*> m_bindablePropertyInstances;
+    std::unordered_map<BindableProperty*, DataBind*> m_bindableDataBinds;
 
 #ifdef WITH_RIVE_TOOLS
 public:
diff --git a/include/rive/data_bind/bindable_property.hpp b/include/rive/data_bind/bindable_property.hpp
index 3294401..cbfb148 100644
--- a/include/rive/data_bind/bindable_property.hpp
+++ b/include/rive/data_bind/bindable_property.hpp
@@ -6,14 +6,7 @@
 namespace rive
 {
 class BindableProperty : public BindablePropertyBase
-{
-public:
-    void dataBind(DataBind* value) { m_dataBind = value; };
-    DataBind* dataBind() { return m_dataBind; };
-
-private:
-    DataBind* m_dataBind;
-};
+{};
 } // namespace rive
 
 #endif
\ No newline at end of file
diff --git a/include/rive/generated/animation/listener_viewmodel_change_base.hpp b/include/rive/generated/animation/listener_viewmodel_change_base.hpp
new file mode 100644
index 0000000..4171141
--- /dev/null
+++ b/include/rive/generated/animation/listener_viewmodel_change_base.hpp
@@ -0,0 +1,36 @@
+#ifndef _RIVE_LISTENER_VIEW_MODEL_CHANGE_BASE_HPP_
+#define _RIVE_LISTENER_VIEW_MODEL_CHANGE_BASE_HPP_
+#include "rive/animation/listener_action.hpp"
+namespace rive
+{
+class ListenerViewModelChangeBase : public ListenerAction
+{
+protected:
+    typedef ListenerAction Super;
+
+public:
+    static const uint16_t typeKey = 487;
+
+    /// Helper to quickly determine if a core object extends another without RTTI
+    /// at runtime.
+    bool isTypeOf(uint16_t typeKey) const override
+    {
+        switch (typeKey)
+        {
+            case ListenerViewModelChangeBase::typeKey:
+            case ListenerActionBase::typeKey:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    uint16_t coreType() const override { return typeKey; }
+
+    Core* clone() const override;
+
+protected:
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/generated/core_registry.hpp b/include/rive/generated/core_registry.hpp
index 65a3ac9..592e41b 100644
--- a/include/rive/generated/core_registry.hpp
+++ b/include/rive/generated/core_registry.hpp
@@ -39,6 +39,7 @@
 #include "rive/animation/listener_input_change.hpp"
 #include "rive/animation/listener_number_change.hpp"
 #include "rive/animation/listener_trigger_change.hpp"
+#include "rive/animation/listener_viewmodel_change.hpp"
 #include "rive/animation/nested_bool.hpp"
 #include "rive/animation/nested_input.hpp"
 #include "rive/animation/nested_linear_animation.hpp"
@@ -353,6 +354,8 @@
                 return new ListenerTriggerChange();
             case BlendStateDirectBase::typeKey:
                 return new BlendStateDirect();
+            case ListenerViewModelChangeBase::typeKey:
+                return new ListenerViewModelChange();
             case TransitionValueNumberComparatorBase::typeKey:
                 return new TransitionValueNumberComparator();
             case NestedStateMachineBase::typeKey:
diff --git a/src/animation/listener_viewmodel_change.cpp b/src/animation/listener_viewmodel_change.cpp
new file mode 100644
index 0000000..5047ee5
--- /dev/null
+++ b/src/animation/listener_viewmodel_change.cpp
@@ -0,0 +1,37 @@
+#include "rive/animation/listener_viewmodel_change.hpp"
+#include "rive/animation/state_machine_instance.hpp"
+#include "rive/data_bind/bindable_property.hpp"
+#include "rive/importers/bindable_property_importer.hpp"
+
+using namespace rive;
+
+StatusCode ListenerViewModelChange::import(ImportStack& importStack)
+{
+
+    auto bindablePropertyImporter =
+        importStack.latest<BindablePropertyImporter>(BindablePropertyBase::typeKey);
+    if (bindablePropertyImporter == nullptr)
+    {
+        return StatusCode::MissingObject;
+    }
+    m_bindableProperty = bindablePropertyImporter->bindableProperty();
+
+    return Super::import(importStack);
+}
+
+// Note: perform works the same way whether the value comes from a direct value assignment or from
+// another view model. In the case of coming from another view model, the state machine instance
+// method "updataDataBinds" will handle updating the value of the bound object. That's the benefit
+// of binding the same bindable property with two data binding objects.
+void ListenerViewModelChange::perform(StateMachineInstance* stateMachineInstance,
+                                      Vec2D position,
+                                      Vec2D previousPosition) const
+{
+    // Get the bindable property instance from the state machine instance context
+    auto bindableInstance = stateMachineInstance->bindablePropertyInstance(m_bindableProperty);
+    // Get the data bound object (that goes from target to source) from this bindable instance
+    auto dataBind = stateMachineInstance->bindableDataBind(bindableInstance);
+    // Apply the change that will assign the value of the bindable property to the view model
+    // property instance
+    dataBind->updateSourceBinding();
+}
\ No newline at end of file
diff --git a/src/animation/state_machine_instance.cpp b/src/animation/state_machine_instance.cpp
index 219200f..c49745f 100644
--- a/src/animation/state_machine_instance.cpp
+++ b/src/animation/state_machine_instance.cpp
@@ -896,9 +896,11 @@
                 bindablePropertyClone = bindablePropertyInstance->second;
             }
             dataBindClone->target(bindablePropertyClone);
+            // 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)
             {
-                bindablePropertyClone->dataBind(dataBindClone);
+                m_bindableDataBinds[bindablePropertyClone] = dataBindClone;
             }
         }
     }
@@ -1265,3 +1267,13 @@
     }
     return bindablePropertyInstance->second;
 }
+
+DataBind* StateMachineInstance::bindableDataBind(BindableProperty* bindableProperty)
+{
+    auto dataBind = m_bindableDataBinds.find(bindableProperty);
+    if (dataBind == m_bindableDataBinds.end())
+    {
+        return nullptr;
+    }
+    return dataBind->second;
+}
diff --git a/src/generated/animation/listener_viewmodel_change_base.cpp b/src/generated/animation/listener_viewmodel_change_base.cpp
new file mode 100644
index 0000000..96d9e9a
--- /dev/null
+++ b/src/generated/animation/listener_viewmodel_change_base.cpp
@@ -0,0 +1,11 @@
+#include "rive/generated/animation/listener_viewmodel_change_base.hpp"
+#include "rive/animation/listener_viewmodel_change.hpp"
+
+using namespace rive;
+
+Core* ListenerViewModelChangeBase::clone() const
+{
+    auto cloned = new ListenerViewModelChange();
+    cloned->copy(*this);
+    return cloned;
+}