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;
+}