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