feature: add support for multiple inputs on listeners (#11862) 501b7f488c * feature: add support for multiple inputs on listeners Co-authored-by: hernan <hernan@rive.app>
diff --git a/.rive_head b/.rive_head index cc9d1b5..fb8495d 100644 --- a/.rive_head +++ b/.rive_head
@@ -1 +1 @@ -89305a5fedb7a617c22dad6d7818c9dcc4f1cf84 +501b7f488c5af1947ae46f5998dba79d829ca7ef
diff --git a/dev/defs/animation/listener_types/listener_input_type.json b/dev/defs/animation/listener_types/listener_input_type.json new file mode 100644 index 0000000..c9d7e18 --- /dev/null +++ b/dev/defs/animation/listener_types/listener_input_type.json
@@ -0,0 +1,28 @@ +{ + "name": "ListenerInputType", + "key": { + "int": 658, + "string": "listenerinputtype" + }, + "properties": { + "listenerTypeValue": { + "type": "uint", + "initialValue": "0", + "key": { + "int": 965, + "string": "listenertypevalue" + }, + "description": "Listener type (hover, click, etc)." + }, + "listenerId": { + "type": "Id", + "initialValue": "Core.missingId", + "key": { + "int": 961, + "string": "listenerid" + }, + "description": "The state machine listener this listener belongs to", + "runtime": false + } + } +} \ No newline at end of file
diff --git a/dev/defs/animation/listener_types/listener_input_type_event.json b/dev/defs/animation/listener_types/listener_input_type_event.json new file mode 100644 index 0000000..3b8186d --- /dev/null +++ b/dev/defs/animation/listener_types/listener_input_type_event.json
@@ -0,0 +1,21 @@ +{ + "name": "ListenerInputTypeEvent", + "key": { + "int": 659, + "string": "listenerinputtypeevent" + }, + "extends": "animation/listener_types/listener_input_type.json", + "properties": { + "eventId": { + "type": "Id", + "typeRuntime": "uint", + "initialValue": "Core.missingId", + "initialValueRuntime": "-1", + "key": { + "int": 962, + "string": "eventid" + }, + "description": "Event id for the associated event" + } + } +} \ No newline at end of file
diff --git a/dev/defs/animation/listener_types/listener_input_type_viewmodel.json b/dev/defs/animation/listener_types/listener_input_type_viewmodel.json new file mode 100644 index 0000000..2031b4c --- /dev/null +++ b/dev/defs/animation/listener_types/listener_input_type_viewmodel.json
@@ -0,0 +1,31 @@ +{ + "name": "ListenerInputTypeViewModel", + "key": { + "int": 660, + "string": "listenerinputtypeviewmodel" + }, + "extends": "animation/listener_types/listener_input_type.json", + "properties": { + "viewModelPathIds": { + "type": "List<Id>", + "typeRuntime": "Bytes", + "encoded": true, + "initialValue": "[]", + "key": { + "int": 963, + "string": "viewmodelpathids" + }, + "description": "Path to the selected view model trigger property if listenerType is a viewModel trigger." + }, + "isDataBindPathRelative": { + "type": "bool", + "initialValue": "false", + "key": { + "int": 964, + "string": "isdatabindpathrelative" + }, + "description": "Whether the data bind path is relative", + "runtime": false + } + } +} \ No newline at end of file
diff --git a/dev/defs/animation/state_machine_listener.json b/dev/defs/animation/state_machine_listener.json index fc6e50a..52274aa 100644 --- a/dev/defs/animation/state_machine_listener.json +++ b/dev/defs/animation/state_machine_listener.json
@@ -1,8 +1,8 @@ { "name": "StateMachineListener", "key": { - "int": 114, - "string": "stateMachineListener" + "int": 654, + "string": "statemachinelistener" }, "extends": "animation/state_machine_component.json", "properties": { @@ -16,47 +16,6 @@ "string": "targetid" }, "description": "Identifier used to track the object use as a target for this listener." - }, - "listenerTypeValue": { - "type": "uint", - "initialValue": "0", - "key": { - "int": 225, - "string": "listenertypevalue" - }, - "description": "Listener type (hover, click, etc)." - }, - "eventId": { - "type": "Id", - "typeRuntime": "uint", - "initialValue": "Core.missingId", - "initialValueRuntime": "-1", - "key": { - "int": 399, - "string": "eventid" - }, - "description": "Event id for the associated event, if listenerType is event" - }, - "viewModelPathIds": { - "type": "List<Id>", - "typeRuntime": "Bytes", - "encoded": true, - "initialValue": "[]", - "key": { - "int": 868, - "string": "viewmodelpathids" - }, - "description": "Path to the selected view model trigger property if listenerType is a viewModel trigger." - }, - "isDataBindPathRelative": { - "type": "bool", - "initialValue": "false", - "key": { - "int": 924, - "string": "isdatabindpathrelative" - }, - "description": "Whether the data bind path is relative", - "runtime": false } } } \ No newline at end of file
diff --git a/dev/defs/animation/state_machine_listener_single.json b/dev/defs/animation/state_machine_listener_single.json new file mode 100644 index 0000000..4e106a9 --- /dev/null +++ b/dev/defs/animation/state_machine_listener_single.json
@@ -0,0 +1,51 @@ +{ + "name": "StateMachineListenerSingle", + "key": { + "int": 114, + "string": "stateMachineListener" + }, + "extends": "animation/state_machine_listener.json", + "properties": { + "listenerTypeValue": { + "type": "uint", + "initialValue": "0", + "key": { + "int": 225, + "string": "listenertypevalue" + }, + "description": "Listener type (hover, click, etc)." + }, + "eventId": { + "type": "Id", + "typeRuntime": "uint", + "initialValue": "Core.missingId", + "initialValueRuntime": "-1", + "key": { + "int": 399, + "string": "eventid" + }, + "description": "Event id for the associated event, if listenerType is event" + }, + "viewModelPathIds": { + "type": "List<Id>", + "typeRuntime": "Bytes", + "encoded": true, + "initialValue": "[]", + "key": { + "int": 868, + "string": "viewmodelpathids" + }, + "description": "Path to the selected view model trigger property if listenerType is a viewModel trigger." + }, + "isDataBindPathRelative": { + "type": "bool", + "initialValue": "false", + "key": { + "int": 924, + "string": "isdatabindpathrelative" + }, + "description": "Whether the data bind path is relative", + "runtime": false + } + } +} \ No newline at end of file
diff --git a/include/rive/animation/focus_listener_group.hpp b/include/rive/animation/focus_listener_group.hpp index fa1e57c..95fb775 100644 --- a/include/rive/animation/focus_listener_group.hpp +++ b/include/rive/animation/focus_listener_group.hpp
@@ -30,6 +30,9 @@ /// Check if this is a focus listener (vs blur listener). bool isFocusListener() const { return m_isFocusListener; } + /// Check if this is a blur listener (vs focus listener). + bool isBlurListener() const { return m_isBlurListener; } + // FocusListener interface void onFocused() override; void onBlurred() override; @@ -39,6 +42,7 @@ const StateMachineListener* m_listener; StateMachineInstance* m_stateMachineInstance; bool m_isFocusListener; + bool m_isBlurListener; }; } // namespace rive
diff --git a/include/rive/animation/listener_types/listener_input_type.hpp b/include/rive/animation/listener_types/listener_input_type.hpp new file mode 100644 index 0000000..1d616f4 --- /dev/null +++ b/include/rive/animation/listener_types/listener_input_type.hpp
@@ -0,0 +1,14 @@ +#ifndef _RIVE_LISTENER_INPUT_TYPE_HPP_ +#define _RIVE_LISTENER_INPUT_TYPE_HPP_ +#include "rive/generated/animation/listener_types/listener_input_type_base.hpp" +#include <stdio.h> +namespace rive +{ +class ListenerInputType : public ListenerInputTypeBase +{ +public: + StatusCode import(ImportStack& importStack) override; +}; +} // namespace rive + +#endif \ No newline at end of file
diff --git a/include/rive/animation/listener_types/listener_input_type_event.hpp b/include/rive/animation/listener_types/listener_input_type_event.hpp new file mode 100644 index 0000000..92c3361 --- /dev/null +++ b/include/rive/animation/listener_types/listener_input_type_event.hpp
@@ -0,0 +1,13 @@ +#ifndef _RIVE_LISTENER_INPUT_TYPE_EVENT_HPP_ +#define _RIVE_LISTENER_INPUT_TYPE_EVENT_HPP_ +#include "rive/generated/animation/listener_types/listener_input_type_event_base.hpp" +#include <stdio.h> +namespace rive +{ +class ListenerInputTypeEvent : public ListenerInputTypeEventBase +{ +public: +}; +} // namespace rive + +#endif \ No newline at end of file
diff --git a/include/rive/animation/listener_types/listener_input_type_viewmodel.hpp b/include/rive/animation/listener_types/listener_input_type_viewmodel.hpp new file mode 100644 index 0000000..0455851 --- /dev/null +++ b/include/rive/animation/listener_types/listener_input_type_viewmodel.hpp
@@ -0,0 +1,19 @@ +#ifndef _RIVE_LISTENER_INPUT_TYPE_VIEW_MODEL_HPP_ +#define _RIVE_LISTENER_INPUT_TYPE_VIEW_MODEL_HPP_ +#include "rive/generated/animation/listener_types/listener_input_type_viewmodel_base.hpp" +#include "rive/data_bind_path_referencer.hpp" +#include <stdio.h> +namespace rive +{ +class ListenerInputTypeViewModel : public ListenerInputTypeViewModelBase, + public DataBindPathReferencer +{ +public: + void decodeViewModelPathIds(Span<const uint8_t> value) override; + void copyViewModelPathIds( + const ListenerInputTypeViewModelBase& object) override; + std::vector<uint32_t> viewModelPathIdsBuffer() const; +}; +} // namespace rive + +#endif \ No newline at end of file
diff --git a/include/rive/animation/state_machine_listener.hpp b/include/rive/animation/state_machine_listener.hpp index ac1c9a6..976eb2d 100644 --- a/include/rive/animation/state_machine_listener.hpp +++ b/include/rive/animation/state_machine_listener.hpp
@@ -3,7 +3,7 @@ #include "rive/generated/animation/state_machine_listener_base.hpp" #include "rive/listener_type.hpp" #include "rive/math/vec2d.hpp" -#include "rive/data_bind_path_referencer.hpp" +#include "rive/animation/listener_types/listener_input_type.hpp" namespace rive { @@ -11,8 +11,7 @@ class StateMachineListenerImporter; class ListenerAction; class StateMachineInstance; -class StateMachineListener : public StateMachineListenerBase, - public DataBindPathReferencer +class StateMachineListener : public StateMachineListenerBase { friend class StateMachineListenerImporter; @@ -20,26 +19,31 @@ StateMachineListener(); ~StateMachineListener() override; - ListenerType listenerType() const - { - return (ListenerType)listenerTypeValue(); - } + // ListenerType listenerType() const + // { + // return (ListenerType)listenerTypeValue(); + // } + virtual bool hasListener(ListenerType) const; size_t actionCount() const { return m_actions.size(); } + size_t listenerInputTypeCount() const + { + return m_listenerInputTypes.size(); + } const ListenerAction* action(size_t index) const; + const ListenerInputType* listenerInputType(size_t index) const; StatusCode import(ImportStack& importStack) override; void performChanges(StateMachineInstance* stateMachineInstance, Vec2D position, Vec2D previousPosition, int pointerId) const; - void decodeViewModelPathIds(Span<const uint8_t> value) override; - void copyViewModelPathIds(const StateMachineListenerBase& object) override; - std::vector<uint32_t> viewModelPathIdsBuffer() const; private: void addAction(std::unique_ptr<ListenerAction>); + void addListenerInputType(std::unique_ptr<ListenerInputType>); std::vector<std::unique_ptr<ListenerAction>> m_actions; + std::vector<std::unique_ptr<ListenerInputType>> m_listenerInputTypes; }; } // namespace rive
diff --git a/include/rive/animation/state_machine_listener_single.hpp b/include/rive/animation/state_machine_listener_single.hpp new file mode 100644 index 0000000..f972e02 --- /dev/null +++ b/include/rive/animation/state_machine_listener_single.hpp
@@ -0,0 +1,23 @@ +#ifndef _RIVE_STATE_MACHINE_LISTENER_SINGLE_HPP_ +#define _RIVE_STATE_MACHINE_LISTENER_SINGLE_HPP_ +#include "rive/generated/animation/state_machine_listener_single_base.hpp" +#include "rive/data_bind_path_referencer.hpp" +namespace rive +{ +class StateMachineListenerSingle : public StateMachineListenerSingleBase, + public DataBindPathReferencer +{ +public: + StatusCode import(ImportStack& importStack) override; + bool hasListener(ListenerType listenerType) const override + { + return listenerTypeValue() == (int)listenerType; + } + void decodeViewModelPathIds(Span<const uint8_t> value) override; + void copyViewModelPathIds( + const StateMachineListenerSingleBase& object) override; + std::vector<uint32_t> viewModelPathIdsBuffer() const; +}; +} // namespace rive + +#endif \ No newline at end of file
diff --git a/include/rive/generated/animation/listener_types/listener_input_type_base.hpp b/include/rive/generated/animation/listener_types/listener_input_type_base.hpp new file mode 100644 index 0000000..c393a4c --- /dev/null +++ b/include/rive/generated/animation/listener_types/listener_input_type_base.hpp
@@ -0,0 +1,69 @@ +#ifndef _RIVE_LISTENER_INPUT_TYPE_BASE_HPP_ +#define _RIVE_LISTENER_INPUT_TYPE_BASE_HPP_ +#include "rive/core.hpp" +#include "rive/core/field_types/core_uint_type.hpp" +namespace rive +{ +class ListenerInputTypeBase : public Core +{ +protected: + typedef Core Super; + +public: + static const uint16_t typeKey = 658; + + /// Helper to quickly determine if a core object extends another without + /// RTTI at runtime. + bool isTypeOf(uint16_t typeKey) const override + { + switch (typeKey) + { + case ListenerInputTypeBase::typeKey: + return true; + default: + return false; + } + } + + uint16_t coreType() const override { return typeKey; } + + static const uint16_t listenerTypeValuePropertyKey = 965; + +protected: + uint32_t m_ListenerTypeValue = 0; + +public: + inline uint32_t listenerTypeValue() const { return m_ListenerTypeValue; } + void listenerTypeValue(uint32_t value) + { + if (m_ListenerTypeValue == value) + { + return; + } + m_ListenerTypeValue = value; + listenerTypeValueChanged(); + } + + Core* clone() const override; + void copy(const ListenerInputTypeBase& object) + { + m_ListenerTypeValue = object.m_ListenerTypeValue; + } + + bool deserialize(uint16_t propertyKey, BinaryReader& reader) override + { + switch (propertyKey) + { + case listenerTypeValuePropertyKey: + m_ListenerTypeValue = CoreUintType::deserialize(reader); + return true; + } + return false; + } + +protected: + virtual void listenerTypeValueChanged() {} +}; +} // namespace rive + +#endif \ No newline at end of file
diff --git a/include/rive/generated/animation/listener_types/listener_input_type_event_base.hpp b/include/rive/generated/animation/listener_types/listener_input_type_event_base.hpp new file mode 100644 index 0000000..f0de47d --- /dev/null +++ b/include/rive/generated/animation/listener_types/listener_input_type_event_base.hpp
@@ -0,0 +1,71 @@ +#ifndef _RIVE_LISTENER_INPUT_TYPE_EVENT_BASE_HPP_ +#define _RIVE_LISTENER_INPUT_TYPE_EVENT_BASE_HPP_ +#include "rive/animation/listener_types/listener_input_type.hpp" +#include "rive/core/field_types/core_uint_type.hpp" +namespace rive +{ +class ListenerInputTypeEventBase : public ListenerInputType +{ +protected: + typedef ListenerInputType Super; + +public: + static const uint16_t typeKey = 659; + + /// Helper to quickly determine if a core object extends another without + /// RTTI at runtime. + bool isTypeOf(uint16_t typeKey) const override + { + switch (typeKey) + { + case ListenerInputTypeEventBase::typeKey: + case ListenerInputTypeBase::typeKey: + return true; + default: + return false; + } + } + + uint16_t coreType() const override { return typeKey; } + + static const uint16_t eventIdPropertyKey = 962; + +protected: + uint32_t m_EventId = -1; + +public: + inline uint32_t eventId() const { return m_EventId; } + void eventId(uint32_t value) + { + if (m_EventId == value) + { + return; + } + m_EventId = value; + eventIdChanged(); + } + + Core* clone() const override; + void copy(const ListenerInputTypeEventBase& object) + { + m_EventId = object.m_EventId; + ListenerInputType::copy(object); + } + + bool deserialize(uint16_t propertyKey, BinaryReader& reader) override + { + switch (propertyKey) + { + case eventIdPropertyKey: + m_EventId = CoreUintType::deserialize(reader); + return true; + } + return ListenerInputType::deserialize(propertyKey, reader); + } + +protected: + virtual void eventIdChanged() {} +}; +} // namespace rive + +#endif \ No newline at end of file
diff --git a/include/rive/generated/animation/listener_types/listener_input_type_viewmodel_base.hpp b/include/rive/generated/animation/listener_types/listener_input_type_viewmodel_base.hpp new file mode 100644 index 0000000..e657916 --- /dev/null +++ b/include/rive/generated/animation/listener_types/listener_input_type_viewmodel_base.hpp
@@ -0,0 +1,62 @@ +#ifndef _RIVE_LISTENER_INPUT_TYPE_VIEW_MODEL_BASE_HPP_ +#define _RIVE_LISTENER_INPUT_TYPE_VIEW_MODEL_BASE_HPP_ +#include "rive/animation/listener_types/listener_input_type.hpp" +#include "rive/core/field_types/core_bytes_type.hpp" +#include "rive/span.hpp" +namespace rive +{ +class ListenerInputTypeViewModelBase : public ListenerInputType +{ +protected: + typedef ListenerInputType Super; + +public: + static const uint16_t typeKey = 660; + + /// Helper to quickly determine if a core object extends another without + /// RTTI at runtime. + bool isTypeOf(uint16_t typeKey) const override + { + switch (typeKey) + { + case ListenerInputTypeViewModelBase::typeKey: + case ListenerInputTypeBase::typeKey: + return true; + default: + return false; + } + } + + uint16_t coreType() const override { return typeKey; } + + static const uint16_t viewModelPathIdsPropertyKey = 963; + +public: + virtual void decodeViewModelPathIds(Span<const uint8_t> value) = 0; + virtual void copyViewModelPathIds( + const ListenerInputTypeViewModelBase& object) = 0; + + Core* clone() const override; + void copy(const ListenerInputTypeViewModelBase& object) + { + copyViewModelPathIds(object); + ListenerInputType::copy(object); + } + + bool deserialize(uint16_t propertyKey, BinaryReader& reader) override + { + switch (propertyKey) + { + case viewModelPathIdsPropertyKey: + decodeViewModelPathIds(CoreBytesType::deserialize(reader)); + return true; + } + return ListenerInputType::deserialize(propertyKey, reader); + } + +protected: + virtual void viewModelPathIdsChanged() {} +}; +} // namespace rive + +#endif \ No newline at end of file
diff --git a/include/rive/generated/animation/state_machine_listener_base.hpp b/include/rive/generated/animation/state_machine_listener_base.hpp index dc80d46..7684abb 100644 --- a/include/rive/generated/animation/state_machine_listener_base.hpp +++ b/include/rive/generated/animation/state_machine_listener_base.hpp
@@ -1,9 +1,7 @@ #ifndef _RIVE_STATE_MACHINE_LISTENER_BASE_HPP_ #define _RIVE_STATE_MACHINE_LISTENER_BASE_HPP_ #include "rive/animation/state_machine_component.hpp" -#include "rive/core/field_types/core_bytes_type.hpp" #include "rive/core/field_types/core_uint_type.hpp" -#include "rive/span.hpp" namespace rive { class StateMachineListenerBase : public StateMachineComponent @@ -12,7 +10,7 @@ typedef StateMachineComponent Super; public: - static const uint16_t typeKey = 114; + static const uint16_t typeKey = 654; /// Helper to quickly determine if a core object extends another without /// RTTI at runtime. @@ -31,14 +29,9 @@ uint16_t coreType() const override { return typeKey; } static const uint16_t targetIdPropertyKey = 224; - static const uint16_t listenerTypeValuePropertyKey = 225; - static const uint16_t eventIdPropertyKey = 399; - static const uint16_t viewModelPathIdsPropertyKey = 868; protected: uint32_t m_TargetId = -1; - uint32_t m_ListenerTypeValue = 0; - uint32_t m_EventId = -1; public: inline uint32_t targetId() const { return m_TargetId; } @@ -52,39 +45,10 @@ targetIdChanged(); } - inline uint32_t listenerTypeValue() const { return m_ListenerTypeValue; } - void listenerTypeValue(uint32_t value) - { - if (m_ListenerTypeValue == value) - { - return; - } - m_ListenerTypeValue = value; - listenerTypeValueChanged(); - } - - inline uint32_t eventId() const { return m_EventId; } - void eventId(uint32_t value) - { - if (m_EventId == value) - { - return; - } - m_EventId = value; - eventIdChanged(); - } - - virtual void decodeViewModelPathIds(Span<const uint8_t> value) = 0; - virtual void copyViewModelPathIds( - const StateMachineListenerBase& object) = 0; - Core* clone() const override; void copy(const StateMachineListenerBase& object) { m_TargetId = object.m_TargetId; - m_ListenerTypeValue = object.m_ListenerTypeValue; - m_EventId = object.m_EventId; - copyViewModelPathIds(object); StateMachineComponent::copy(object); } @@ -95,24 +59,12 @@ case targetIdPropertyKey: m_TargetId = CoreUintType::deserialize(reader); return true; - case listenerTypeValuePropertyKey: - m_ListenerTypeValue = CoreUintType::deserialize(reader); - return true; - case eventIdPropertyKey: - m_EventId = CoreUintType::deserialize(reader); - return true; - case viewModelPathIdsPropertyKey: - decodeViewModelPathIds(CoreBytesType::deserialize(reader)); - return true; } return StateMachineComponent::deserialize(propertyKey, reader); } protected: virtual void targetIdChanged() {} - virtual void listenerTypeValueChanged() {} - virtual void eventIdChanged() {} - virtual void viewModelPathIdsChanged() {} }; } // namespace rive
diff --git a/include/rive/generated/animation/state_machine_listener_single_base.hpp b/include/rive/generated/animation/state_machine_listener_single_base.hpp new file mode 100644 index 0000000..6deeb93 --- /dev/null +++ b/include/rive/generated/animation/state_machine_listener_single_base.hpp
@@ -0,0 +1,102 @@ +#ifndef _RIVE_STATE_MACHINE_LISTENER_SINGLE_BASE_HPP_ +#define _RIVE_STATE_MACHINE_LISTENER_SINGLE_BASE_HPP_ +#include "rive/animation/state_machine_listener.hpp" +#include "rive/core/field_types/core_bytes_type.hpp" +#include "rive/core/field_types/core_uint_type.hpp" +#include "rive/span.hpp" +namespace rive +{ +class StateMachineListenerSingleBase : public StateMachineListener +{ +protected: + typedef StateMachineListener Super; + +public: + static const uint16_t typeKey = 114; + + /// Helper to quickly determine if a core object extends another without + /// RTTI at runtime. + bool isTypeOf(uint16_t typeKey) const override + { + switch (typeKey) + { + case StateMachineListenerSingleBase::typeKey: + case StateMachineListenerBase::typeKey: + case StateMachineComponentBase::typeKey: + return true; + default: + return false; + } + } + + uint16_t coreType() const override { return typeKey; } + + static const uint16_t listenerTypeValuePropertyKey = 225; + static const uint16_t eventIdPropertyKey = 399; + static const uint16_t viewModelPathIdsPropertyKey = 868; + +protected: + uint32_t m_ListenerTypeValue = 0; + uint32_t m_EventId = -1; + +public: + inline uint32_t listenerTypeValue() const { return m_ListenerTypeValue; } + void listenerTypeValue(uint32_t value) + { + if (m_ListenerTypeValue == value) + { + return; + } + m_ListenerTypeValue = value; + listenerTypeValueChanged(); + } + + inline uint32_t eventId() const { return m_EventId; } + void eventId(uint32_t value) + { + if (m_EventId == value) + { + return; + } + m_EventId = value; + eventIdChanged(); + } + + virtual void decodeViewModelPathIds(Span<const uint8_t> value) = 0; + virtual void copyViewModelPathIds( + const StateMachineListenerSingleBase& object) = 0; + + Core* clone() const override; + void copy(const StateMachineListenerSingleBase& object) + { + m_ListenerTypeValue = object.m_ListenerTypeValue; + m_EventId = object.m_EventId; + copyViewModelPathIds(object); + StateMachineListener::copy(object); + } + + bool deserialize(uint16_t propertyKey, BinaryReader& reader) override + { + switch (propertyKey) + { + case listenerTypeValuePropertyKey: + m_ListenerTypeValue = CoreUintType::deserialize(reader); + return true; + case eventIdPropertyKey: + m_EventId = CoreUintType::deserialize(reader); + return true; + case viewModelPathIdsPropertyKey: + decodeViewModelPathIds(CoreBytesType::deserialize(reader)); + return true; + } + return StateMachineListener::deserialize(propertyKey, reader); + } + +protected: + virtual void listenerTypeValueChanged() {} + virtual void eventIdChanged() {} + virtual void viewModelPathIdsChanged() {} +}; +} // 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 4b12bce..a30dc9e 100644 --- a/include/rive/generated/core_registry.hpp +++ b/include/rive/generated/core_registry.hpp
@@ -42,6 +42,9 @@ #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_types/listener_input_type.hpp" +#include "rive/animation/listener_types/listener_input_type_event.hpp" +#include "rive/animation/listener_types/listener_input_type_viewmodel.hpp" #include "rive/animation/listener_viewmodel_change.hpp" #include "rive/animation/nested_bool.hpp" #include "rive/animation/nested_input.hpp" @@ -63,6 +66,7 @@ #include "rive/animation/state_machine_layer.hpp" #include "rive/animation/state_machine_layer_component.hpp" #include "rive/animation/state_machine_listener.hpp" +#include "rive/animation/state_machine_listener_single.hpp" #include "rive/animation/state_machine_number.hpp" #include "rive/animation/state_machine_trigger.hpp" #include "rive/animation/state_transition.hpp" @@ -495,14 +499,16 @@ return new BlendAnimationDirect(); case StateMachineNumberBase::typeKey: return new StateMachineNumber(); + case StateMachineListenerBase::typeKey: + return new StateMachineListener(); + case StateMachineListenerSingleBase::typeKey: + return new StateMachineListenerSingle(); case CubicValueInterpolatorBase::typeKey: return new CubicValueInterpolator(); case TransitionTriggerConditionBase::typeKey: return new TransitionTriggerCondition(); case KeyedPropertyBase::typeKey: return new KeyedProperty(); - case StateMachineListenerBase::typeKey: - return new StateMachineListener(); case TransitionPropertyArtboardComparatorBase::typeKey: return new TransitionPropertyArtboardComparator(); case TransitionPropertyViewModelComparatorBase::typeKey: @@ -575,6 +581,12 @@ return new NestedStateMachine(); case ElasticInterpolatorBase::typeKey: return new ElasticInterpolator(); + case ListenerInputTypeBase::typeKey: + return new ListenerInputType(); + case ListenerInputTypeEventBase::typeKey: + return new ListenerInputTypeEvent(); + case ListenerInputTypeViewModelBase::typeKey: + return new ListenerInputTypeViewModel(); case ExitStateBase::typeKey: return new ExitState(); case NestedNumberBase::typeKey: @@ -1229,22 +1241,22 @@ case BlendAnimationDirectBase::blendSourcePropertyKey: object->as<BlendAnimationDirectBase>()->blendSource(value); break; + case StateMachineListenerBase::targetIdPropertyKey: + object->as<StateMachineListenerBase>()->targetId(value); + break; + case StateMachineListenerSingleBase::listenerTypeValuePropertyKey: + object->as<StateMachineListenerSingleBase>()->listenerTypeValue( + value); + break; + case StateMachineListenerSingleBase::eventIdPropertyKey: + object->as<StateMachineListenerSingleBase>()->eventId(value); + break; case TransitionInputConditionBase::inputIdPropertyKey: object->as<TransitionInputConditionBase>()->inputId(value); break; case KeyedPropertyBase::propertyKeyPropertyKey: object->as<KeyedPropertyBase>()->propertyKey(value); break; - case StateMachineListenerBase::targetIdPropertyKey: - object->as<StateMachineListenerBase>()->targetId(value); - break; - case StateMachineListenerBase::listenerTypeValuePropertyKey: - object->as<StateMachineListenerBase>()->listenerTypeValue( - value); - break; - case StateMachineListenerBase::eventIdPropertyKey: - object->as<StateMachineListenerBase>()->eventId(value); - break; case TransitionPropertyArtboardComparatorBase:: propertyTypePropertyKey: object->as<TransitionPropertyArtboardComparatorBase>() @@ -1320,6 +1332,12 @@ case ElasticInterpolatorBase::easingValuePropertyKey: object->as<ElasticInterpolatorBase>()->easingValue(value); break; + case ListenerInputTypeBase::listenerTypeValuePropertyKey: + object->as<ListenerInputTypeBase>()->listenerTypeValue(value); + break; + case ListenerInputTypeEventBase::eventIdPropertyKey: + object->as<ListenerInputTypeEventBase>()->eventId(value); + break; case BlendStateTransitionBase::exitBlendAnimationIdPropertyKey: object->as<BlendStateTransitionBase>()->exitBlendAnimationId( value); @@ -2802,17 +2820,17 @@ return object->as<BlendAnimationDirectBase>()->inputId(); case BlendAnimationDirectBase::blendSourcePropertyKey: return object->as<BlendAnimationDirectBase>()->blendSource(); + case StateMachineListenerBase::targetIdPropertyKey: + return object->as<StateMachineListenerBase>()->targetId(); + case StateMachineListenerSingleBase::listenerTypeValuePropertyKey: + return object->as<StateMachineListenerSingleBase>() + ->listenerTypeValue(); + case StateMachineListenerSingleBase::eventIdPropertyKey: + return object->as<StateMachineListenerSingleBase>()->eventId(); case TransitionInputConditionBase::inputIdPropertyKey: return object->as<TransitionInputConditionBase>()->inputId(); case KeyedPropertyBase::propertyKeyPropertyKey: return object->as<KeyedPropertyBase>()->propertyKey(); - case StateMachineListenerBase::targetIdPropertyKey: - return object->as<StateMachineListenerBase>()->targetId(); - case StateMachineListenerBase::listenerTypeValuePropertyKey: - return object->as<StateMachineListenerBase>() - ->listenerTypeValue(); - case StateMachineListenerBase::eventIdPropertyKey: - return object->as<StateMachineListenerBase>()->eventId(); case TransitionPropertyArtboardComparatorBase:: propertyTypePropertyKey: return object->as<TransitionPropertyArtboardComparatorBase>() @@ -2865,6 +2883,10 @@ return object->as<LinearAnimationBase>()->workEnd(); case ElasticInterpolatorBase::easingValuePropertyKey: return object->as<ElasticInterpolatorBase>()->easingValue(); + case ListenerInputTypeBase::listenerTypeValuePropertyKey: + return object->as<ListenerInputTypeBase>()->listenerTypeValue(); + case ListenerInputTypeEventBase::eventIdPropertyKey: + return object->as<ListenerInputTypeEventBase>()->eventId(); case BlendStateTransitionBase::exitBlendAnimationIdPropertyKey: return object->as<BlendStateTransitionBase>() ->exitBlendAnimationId(); @@ -3808,11 +3830,11 @@ case BlendAnimationBase::animationIdPropertyKey: case BlendAnimationDirectBase::inputIdPropertyKey: case BlendAnimationDirectBase::blendSourcePropertyKey: + case StateMachineListenerBase::targetIdPropertyKey: + case StateMachineListenerSingleBase::listenerTypeValuePropertyKey: + case StateMachineListenerSingleBase::eventIdPropertyKey: case TransitionInputConditionBase::inputIdPropertyKey: case KeyedPropertyBase::propertyKeyPropertyKey: - case StateMachineListenerBase::targetIdPropertyKey: - case StateMachineListenerBase::listenerTypeValuePropertyKey: - case StateMachineListenerBase::eventIdPropertyKey: case TransitionPropertyArtboardComparatorBase:: propertyTypePropertyKey: case KeyFrameIdBase::valuePropertyKey: @@ -3838,6 +3860,8 @@ case LinearAnimationBase::workStartPropertyKey: case LinearAnimationBase::workEndPropertyKey: case ElasticInterpolatorBase::easingValuePropertyKey: + case ListenerInputTypeBase::listenerTypeValuePropertyKey: + case ListenerInputTypeEventBase::eventIdPropertyKey: case BlendStateTransitionBase::exitBlendAnimationIdPropertyKey: case ShapePaintBase::blendModeValuePropertyKey: case TargetEffectBase::targetIdPropertyKey: @@ -4226,7 +4250,8 @@ case ScriptInputViewModelPropertyBase::dataBindPathIdsPropertyKey: case NestedArtboardBase::dataBindPathIdsPropertyKey: case StateMachineFireTriggerBase::viewModelPathIdsPropertyKey: - case StateMachineListenerBase::viewModelPathIdsPropertyKey: + case StateMachineListenerSingleBase::viewModelPathIdsPropertyKey: + case ListenerInputTypeViewModelBase::viewModelPathIdsPropertyKey: case MeshBase::triangleIndexBytesPropertyKey: case DataBindPathBase::pathPropertyKey: case DataConverterOperationViewModelBase::sourcePathIdsPropertyKey: @@ -4476,16 +4501,16 @@ return object->is<BlendAnimationDirectBase>(); case BlendAnimationDirectBase::blendSourcePropertyKey: return object->is<BlendAnimationDirectBase>(); + case StateMachineListenerBase::targetIdPropertyKey: + return object->is<StateMachineListenerBase>(); + case StateMachineListenerSingleBase::listenerTypeValuePropertyKey: + return object->is<StateMachineListenerSingleBase>(); + case StateMachineListenerSingleBase::eventIdPropertyKey: + return object->is<StateMachineListenerSingleBase>(); case TransitionInputConditionBase::inputIdPropertyKey: return object->is<TransitionInputConditionBase>(); case KeyedPropertyBase::propertyKeyPropertyKey: return object->is<KeyedPropertyBase>(); - case StateMachineListenerBase::targetIdPropertyKey: - return object->is<StateMachineListenerBase>(); - case StateMachineListenerBase::listenerTypeValuePropertyKey: - return object->is<StateMachineListenerBase>(); - case StateMachineListenerBase::eventIdPropertyKey: - return object->is<StateMachineListenerBase>(); case TransitionPropertyArtboardComparatorBase:: propertyTypePropertyKey: return object->is<TransitionPropertyArtboardComparatorBase>(); @@ -4535,6 +4560,10 @@ return object->is<LinearAnimationBase>(); case ElasticInterpolatorBase::easingValuePropertyKey: return object->is<ElasticInterpolatorBase>(); + case ListenerInputTypeBase::listenerTypeValuePropertyKey: + return object->is<ListenerInputTypeBase>(); + case ListenerInputTypeEventBase::eventIdPropertyKey: + return object->is<ListenerInputTypeEventBase>(); case BlendStateTransitionBase::exitBlendAnimationIdPropertyKey: return object->is<BlendStateTransitionBase>(); case ShapePaintBase::blendModeValuePropertyKey:
diff --git a/include/rive/importers/state_machine_listener_importer.hpp b/include/rive/importers/state_machine_listener_importer.hpp index 6dc6404..92c4b5b 100644 --- a/include/rive/importers/state_machine_listener_importer.hpp +++ b/include/rive/importers/state_machine_listener_importer.hpp
@@ -20,6 +20,7 @@ return m_StateMachineListener; } void addAction(std::unique_ptr<ListenerAction>); + void addListenerInputType(std::unique_ptr<ListenerInputType>); StatusCode resolve() override; }; } // namespace rive
diff --git a/src/animation/focus_listener_group.cpp b/src/animation/focus_listener_group.cpp index 938488a..a6ecfab 100644 --- a/src/animation/focus_listener_group.cpp +++ b/src/animation/focus_listener_group.cpp
@@ -12,7 +12,8 @@ m_focusData(focusData), m_listener(listener), m_stateMachineInstance(stateMachineInstance), - m_isFocusListener(listener->listenerType() == ListenerType::focus) + m_isFocusListener(listener->hasListener(ListenerType::focus)), + m_isBlurListener(listener->hasListener(ListenerType::blur)) { // Register ourselves as a listener on the FocusData m_focusData->addFocusListener(this); @@ -35,7 +36,7 @@ void FocusListenerGroup::onBlurred() { // Only queue if this is a blur listener - if (!m_isFocusListener) + if (m_isBlurListener) { m_stateMachineInstance->queueFocusEvent(this, false); }
diff --git a/src/animation/listener_types/listener_input_type.cpp b/src/animation/listener_types/listener_input_type.cpp new file mode 100644 index 0000000..6513691 --- /dev/null +++ b/src/animation/listener_types/listener_input_type.cpp
@@ -0,0 +1,23 @@ +#include "rive/animation/state_machine_listener.hpp" +#include "rive/importers/import_stack.hpp" +#include "rive/importers/state_machine_listener_importer.hpp" +#include "rive/importers/state_machine_importer.hpp" +#include "rive/animation/listener_types/listener_input_type.hpp" +#include "rive/animation/state_machine.hpp" + +using namespace rive; + +StatusCode ListenerInputType::import(ImportStack& importStack) +{ + auto stateMachineListenerImporter = + importStack.latest<StateMachineListenerImporter>( + StateMachineListenerBase::typeKey); + if (stateMachineListenerImporter == nullptr) + { + return StatusCode::MissingObject; + } + + stateMachineListenerImporter->addListenerInputType( + std::unique_ptr<ListenerInputType>(this)); + return Super::import(importStack); +}
diff --git a/src/animation/listener_types/listener_input_type_viewmodel.cpp b/src/animation/listener_types/listener_input_type_viewmodel.cpp new file mode 100644 index 0000000..a081281 --- /dev/null +++ b/src/animation/listener_types/listener_input_type_viewmodel.cpp
@@ -0,0 +1,20 @@ +#include "rive/animation/listener_types/listener_input_type_viewmodel.hpp" + +using namespace rive; + +void ListenerInputTypeViewModel::decodeViewModelPathIds( + Span<const uint8_t> value) +{ + decodeDataBindPath(value); +} + +void ListenerInputTypeViewModel::copyViewModelPathIds( + const ListenerInputTypeViewModelBase& object) +{ + copyDataBindPath(object.as<ListenerInputTypeViewModel>()->dataBindPath()); +} + +std::vector<uint32_t> ListenerInputTypeViewModel::viewModelPathIdsBuffer() const +{ + return dataBindPath()->path(); +} \ No newline at end of file
diff --git a/src/animation/state_machine_instance.cpp b/src/animation/state_machine_instance.cpp index a027ce2..17e77ff 100644 --- a/src/animation/state_machine_instance.cpp +++ b/src/animation/state_machine_instance.cpp
@@ -16,11 +16,13 @@ #include "rive/animation/state_machine_instance.hpp" #include "rive/animation/state_machine_layer.hpp" #include "rive/animation/state_machine_listener.hpp" +#include "rive/animation/state_machine_listener_single.hpp" #include "rive/animation/state_machine_number.hpp" #include "rive/animation/state_machine_trigger.hpp" #include "rive/animation/state_machine.hpp" #include "rive/animation/state_transition.hpp" #include "rive/animation/listener_action.hpp" +#include "rive/animation/listener_types/listener_input_type_viewmodel.hpp" #include "rive/animation/scripted_listener_action.hpp" #include "rive/animation/transition_condition.hpp" #include "rive/animation/transition_comparator.hpp" @@ -53,9 +55,12 @@ #include "rive/refcnt.hpp" #include "rive/animation/focus_listener_group.hpp" #include "rive/animation/text_input_listener_group.hpp" +#include "rive/animation/listener_types/listener_input_type_event.hpp" #include "rive/focus_data.hpp" #include "rive/node.hpp" +#include <memory> #include <unordered_map> +#include <vector> #include <chrono> using namespace rive; @@ -1014,73 +1019,223 @@ {} }; -class ListenerViewModel : public ViewModelValueDependent +class ListenerViewModel; + +// Helper that holds one view model property reference, listens to its dirt, +// and reports the parent ListenerViewModel when the property changes. +class ListenerViewModelPropertyBinding : public ViewModelValueDependent { public: - virtual ~ListenerViewModel() = default; + ListenerViewModelPropertyBinding(ListenerViewModel* parent, + ViewModelInstanceValue* vmProp); + virtual ~ListenerViewModelPropertyBinding(); + void addDirt(ComponentDirt value, bool recurse) override; + void relinkDataBind() override; + +protected: + ListenerViewModel* m_parent = nullptr; + rive::rcp<ViewModelInstanceValue> m_viewModelInstanceValue = nullptr; + void clearDataContext(); +}; +class ListenerViewModelPropertyBindingListener + : public ListenerViewModelPropertyBinding +{ +public: + ListenerViewModelPropertyBindingListener( + ListenerViewModel* parent, + ViewModelInstanceValue* vmProp, + const StateMachineListenerSingle* listener); + void relinkDataBind() override; + +private: + const StateMachineListenerSingle* m_listener; +}; +class ListenerViewModelPropertyBindingInput + : public ListenerViewModelPropertyBinding +{ +public: + ListenerViewModelPropertyBindingInput( + ListenerViewModel* parent, + ViewModelInstanceValue* vmProp, + const ListenerInputTypeViewModel* listenerInput); + void relinkDataBind() override; + +private: + const ListenerInputTypeViewModel* m_listenerInput; +}; + +class ListenerViewModel +{ +public: + virtual ~ListenerViewModel(); ListenerViewModel(StateMachineInstance* smInstance, const StateMachineListener* listener) : m_stateMachineInstance(smInstance), m_listener(listener) {} - void clearDataContext() - { - if (m_viewModelInstanceValue != nullptr) - { - m_viewModelInstanceValue->removeDependent(this); - m_viewModelInstanceValue = nullptr; - } - } - void relinkDataBind() - { - if (m_dataContext != nullptr) - { - auto vmProp = - m_dataContext->getViewModelProperty(m_listener->dataBindPath()); - if (vmProp != m_viewModelInstanceValue.get()) - { - clearDataContext(); - if (vmProp != nullptr) - { - m_viewModelInstanceValue = ref_rcp(vmProp); - vmProp->addDependent(this); - } - } - } - } + void clearDataContext() { m_propertyBindings.clear(); } void bindFromContext(rcp<DataContext> dataContext) { m_dataContext = dataContext; clearDataContext(); - auto vmProp = - dataContext->getViewModelProperty(m_listener->dataBindPath()); - if (vmProp != nullptr) + if (m_listener->is<StateMachineListenerSingle>()) { - m_viewModelInstanceValue = ref_rcp(vmProp); - vmProp->addDependent(this); - } - } - void addDirt(ComponentDirt value, bool recurse) - { - if (m_viewModelInstanceValue) - { - if (!m_viewModelInstanceValue->is<ViewModelInstanceTrigger>() || - m_viewModelInstanceValue->as<ViewModelInstanceTrigger>() - ->propertyValue() != 0) + auto vmProp = dataContext->getViewModelProperty( + m_listener->as<StateMachineListenerSingle>()->dataBindPath()); + if (vmProp != nullptr) { - m_stateMachineInstance->reportListenerViewModel(this); + m_propertyBindings.push_back( + rivestd::make_unique< + ListenerViewModelPropertyBindingListener>( + this, + vmProp, + m_listener->as<StateMachineListenerSingle>())); + } + } + else + { + size_t index = 0; + while (index < m_listener->listenerInputTypeCount()) + { + auto listenerInputType = m_listener->listenerInputType(index); + if (listenerInputType->is<ListenerInputTypeViewModel>()) + { + auto listenerInputTypeVM = + listenerInputType->as<ListenerInputTypeViewModel>(); + auto vmProp = dataContext->getViewModelProperty( + listenerInputTypeVM->dataBindPath()); + if (vmProp != nullptr) + { + m_propertyBindings.push_back( + rivestd::make_unique< + ListenerViewModelPropertyBindingInput>( + this, + vmProp, + listenerInputTypeVM)); + } + } + index++; } } } + void reportToStateMachine(ViewModelInstanceValue* value) + { + if (!value->is<ViewModelInstanceTrigger>() || + value->as<ViewModelInstanceTrigger>()->propertyValue() != 0) + { + m_stateMachineInstance->reportListenerViewModel(this); + } + } const StateMachineListener* listener() { return m_listener; } + DataContext* dataContext() + { + if (m_dataContext) + { + + return m_dataContext.get(); + } + return nullptr; + } private: StateMachineInstance* m_stateMachineInstance = nullptr; const StateMachineListener* m_listener = nullptr; - rcp<ViewModelInstanceValue> m_viewModelInstanceValue = nullptr; rcp<DataContext> m_dataContext = nullptr; + std::vector<std::unique_ptr<ListenerViewModelPropertyBinding>> + m_propertyBindings; }; +ListenerViewModelPropertyBinding::ListenerViewModelPropertyBinding( + ListenerViewModel* parent, + ViewModelInstanceValue* vmProp) : + m_parent(parent), m_viewModelInstanceValue(rive::ref_rcp(vmProp)) +{ + vmProp->addDependent(this); +} + +void ListenerViewModelPropertyBinding::relinkDataBind() {}; + +ListenerViewModelPropertyBinding::~ListenerViewModelPropertyBinding() +{ + clearDataContext(); +} + +void ListenerViewModelPropertyBinding::clearDataContext() +{ + + if (m_viewModelInstanceValue != nullptr) + { + m_viewModelInstanceValue->removeDependent(this); + m_viewModelInstanceValue = nullptr; + } +} + +ListenerViewModelPropertyBindingListener:: + ListenerViewModelPropertyBindingListener( + ListenerViewModel* parent, + ViewModelInstanceValue* vmProp, + const StateMachineListenerSingle* listener) : + ListenerViewModelPropertyBinding(parent, vmProp), m_listener(listener) +{} + +void ListenerViewModelPropertyBindingListener::relinkDataBind() +{ + auto dataContext = m_parent->dataContext(); + if (dataContext) + { + + auto vmProp = + dataContext->getViewModelProperty(m_listener->dataBindPath()); + if (vmProp != m_viewModelInstanceValue.get()) + { + clearDataContext(); + if (vmProp != nullptr) + { + m_viewModelInstanceValue = ref_rcp(vmProp); + vmProp->addDependent(this); + } + } + } +} + +ListenerViewModelPropertyBindingInput::ListenerViewModelPropertyBindingInput( + ListenerViewModel* parent, + ViewModelInstanceValue* vmProp, + const ListenerInputTypeViewModel* listenerInput) : + ListenerViewModelPropertyBinding(parent, vmProp), + m_listenerInput(listenerInput) +{} + +void ListenerViewModelPropertyBindingInput::relinkDataBind() +{ + auto dataContext = m_parent->dataContext(); + if (dataContext) + { + auto vmProp = + dataContext->getViewModelProperty(m_listenerInput->dataBindPath()); + if (vmProp != m_viewModelInstanceValue.get()) + { + clearDataContext(); + if (vmProp != nullptr) + { + m_viewModelInstanceValue = ref_rcp(vmProp); + vmProp->addDependent(this); + } + } + } +} + +void ListenerViewModelPropertyBinding::addDirt(ComponentDirt value, + bool recurse) +{ + if (m_parent != nullptr && m_viewModelInstanceValue != nullptr) + { + m_parent->reportToStateMachine(m_viewModelInstanceValue.get()); + } +} + +ListenerViewModel::~ListenerViewModel() { clearDataContext(); } + } // namespace rive HitResult StateMachineInstance::updateListeners(Vec2D position, @@ -1405,11 +1560,11 @@ for (std::size_t i = 0; i < machine->listenerCount(); i++) { auto listener = machine->listener(i); - if (listener->listenerType() == ListenerType::event) + if (listener->hasListener(ListenerType::event)) { continue; } - if (listener->listenerType() == ListenerType::viewModel) + if (listener->hasListener(ListenerType::viewModel)) { auto vmListener = new ListenerViewModel(this, listener); m_listenerViewModels.push_back(vmListener); @@ -1417,8 +1572,8 @@ } // Handle focus/blur listeners - they're driven by FocusManager, // not pointer events. - if (listener->listenerType() == ListenerType::focus || - listener->listenerType() == ListenerType::blur) + if (listener->hasListener(ListenerType::focus) || + listener->hasListener(ListenerType::blur)) { auto target = m_artboardInstance->resolve(listener->targetId()); if (target != nullptr && target->is<Node>()) @@ -1838,8 +1993,8 @@ bool isFocusEvent = event.isFocus; // Match listener type to event type - if ((isFocusEvent && listener->listenerType() == ListenerType::focus) || - (!isFocusEvent && listener->listenerType() == ListenerType::blur)) + if ((isFocusEvent && listener->hasListener(ListenerType::focus)) || + (!isFocusEvent && listener->hasListener(ListenerType::blur))) { listener->performChanges(this, Vec2D(), Vec2D(), 0); } @@ -2205,7 +2360,7 @@ auto listener = m_machine->listener(i); auto target = artboard()->resolve(listener->targetId()); if (listener != nullptr && - listener->listenerType() == ListenerType::event && + listener->hasListener(ListenerType::event) && (source == nullptr || source == target)) { for (const auto event : events) @@ -2240,12 +2395,43 @@ continue; } } - auto listenerEvent = - sourceArtboard->resolve(listener->eventId()); - if (listenerEvent == event.event()) + if (listener->is<StateMachineListenerSingle>()) { - listener->performChanges(this, Vec2D(), Vec2D(), 0); - break; + auto listenerEvent = sourceArtboard->resolve( + listener->as<StateMachineListenerSingle>() + ->eventId()); + if (listenerEvent == event.event()) + { + listener->performChanges(this, Vec2D(), Vec2D(), 0); + break; + } + } + else + { + size_t index = 0; + while (index < listener->listenerInputTypeCount()) + { + auto listenerInputType = + listener->listenerInputType(index); + if (listenerInputType->is<ListenerInputTypeEvent>()) + { + + auto listenerInputTypeEvent = + listenerInputType + ->as<ListenerInputTypeEvent>(); + auto listenerEvent = sourceArtboard->resolve( + listenerInputTypeEvent->eventId()); + if (listenerEvent == event.event()) + { + listener->performChanges(this, + Vec2D(), + Vec2D(), + 0); + break; + } + } + index += 1; + } } } }
diff --git a/src/animation/state_machine_listener.cpp b/src/animation/state_machine_listener.cpp index 7b39e3a..7e93131 100644 --- a/src/animation/state_machine_listener.cpp +++ b/src/animation/state_machine_listener.cpp
@@ -6,20 +6,38 @@ #include "rive/shapes/shape.hpp" #include "rive/animation/state_machine_instance.hpp" #include "rive/animation/listener_input_change.hpp" +#include "rive/animation/listener_types/listener_input_type.hpp" using namespace rive; StateMachineListener::StateMachineListener() {} StateMachineListener::~StateMachineListener() {} +bool StateMachineListener::hasListener(ListenerType listenerType) const +{ + for (auto& listenerInputType : m_listenerInputTypes) + { + if (listenerInputType->listenerTypeValue() == (int)listenerType) + { + return true; + } + } + return false; +} + void StateMachineListener::addAction(std::unique_ptr<ListenerAction> action) { m_actions.push_back(std::move(action)); } +void StateMachineListener::addListenerInputType( + std::unique_ptr<ListenerInputType> listenerInputType) +{ + m_listenerInputTypes.push_back(std::move(listenerInputType)); +} + StatusCode StateMachineListener::import(ImportStack& importStack) { - importDataBindPath(importStack); auto stateMachineImporter = importStack.latest<StateMachineImporter>(StateMachineBase::typeKey); if (stateMachineImporter == nullptr) @@ -41,6 +59,16 @@ return nullptr; } +const ListenerInputType* StateMachineListener::listenerInputType( + size_t index) const +{ + if (index < m_listenerInputTypes.size()) + { + return m_listenerInputTypes[index].get(); + } + return nullptr; +} + void StateMachineListener::performChanges( StateMachineInstance* stateMachineInstance, Vec2D position, @@ -54,20 +82,4 @@ previousPosition, pointerId); } -} - -void StateMachineListener::decodeViewModelPathIds(Span<const uint8_t> value) -{ - decodeDataBindPath(value); -} - -void StateMachineListener::copyViewModelPathIds( - const StateMachineListenerBase& object) -{ - copyDataBindPath(object.as<StateMachineListener>()->dataBindPath()); -} - -std::vector<uint32_t> StateMachineListener::viewModelPathIdsBuffer() const -{ - return dataBindPath()->path(); } \ No newline at end of file
diff --git a/src/animation/state_machine_listener_single.cpp b/src/animation/state_machine_listener_single.cpp new file mode 100644 index 0000000..3fd4cf2 --- /dev/null +++ b/src/animation/state_machine_listener_single.cpp
@@ -0,0 +1,34 @@ +#include "rive/animation/state_machine_listener_single.hpp" +#include "rive/importers/import_stack.hpp" +#include "rive/importers/state_machine_importer.hpp" +#include "rive/generated/animation/state_machine_base.hpp" +#include "rive/artboard.hpp" +#include "rive/shapes/shape.hpp" +#include "rive/animation/state_machine_instance.hpp" +#include "rive/animation/listener_input_change.hpp" +#include "rive/animation/listener_types/listener_input_type.hpp" + +using namespace rive; + +StatusCode StateMachineListenerSingle::import(ImportStack& importStack) +{ + importDataBindPath(importStack); + return Super::import(importStack); +} + +void StateMachineListenerSingle::decodeViewModelPathIds( + Span<const uint8_t> value) +{ + decodeDataBindPath(value); +} + +void StateMachineListenerSingle::copyViewModelPathIds( + const StateMachineListenerSingleBase& object) +{ + copyDataBindPath(object.as<StateMachineListenerSingle>()->dataBindPath()); +} + +std::vector<uint32_t> StateMachineListenerSingle::viewModelPathIdsBuffer() const +{ + return dataBindPath()->path(); +} \ No newline at end of file
diff --git a/src/constraints/draggable_constraint.cpp b/src/constraints/draggable_constraint.cpp index 7a94094..cc4a1f2 100644 --- a/src/constraints/draggable_constraint.cpp +++ b/src/constraints/draggable_constraint.cpp
@@ -1,4 +1,5 @@ #include "rive/animation/state_machine_instance.hpp" +#include "rive/animation/state_machine_listener_single.hpp" #include "rive/component.hpp" #include "rive/constraints/draggable_constraint.hpp" @@ -9,7 +10,7 @@ std::vector<ListenerGroupWithTargets*> result; for (auto dragProxy : draggables()) { - auto listener = new StateMachineListener(); + auto listener = new StateMachineListenerSingle(); listener->listenerTypeValue( static_cast<uint32_t>(ListenerType::componentProvided)); auto* listenerGroup =
diff --git a/src/file.cpp b/src/file.cpp index 333fdc5..a1a4037 100644 --- a/src/file.cpp +++ b/src/file.cpp
@@ -452,9 +452,11 @@ stackType = StateTransition::typeKey; break; case StateMachineListener::typeKey: + case StateMachineListenerSingle::typeKey: stackObject = rivestd::make_unique<StateMachineListenerImporter>( object->as<StateMachineListener>()); + stackType = StateMachineListener::typeKey; break; case ImageAsset::typeKey: case FontAsset::typeKey:
diff --git a/src/generated/animation/listener_types/listener_input_type_base.cpp b/src/generated/animation/listener_types/listener_input_type_base.cpp new file mode 100644 index 0000000..dd617a1 --- /dev/null +++ b/src/generated/animation/listener_types/listener_input_type_base.cpp
@@ -0,0 +1,11 @@ +#include "rive/generated/animation/listener_types/listener_input_type_base.hpp" +#include "rive/animation/listener_types/listener_input_type.hpp" + +using namespace rive; + +Core* ListenerInputTypeBase::clone() const +{ + auto cloned = new ListenerInputType(); + cloned->copy(*this); + return cloned; +}
diff --git a/src/generated/animation/listener_types/listener_input_type_event_base.cpp b/src/generated/animation/listener_types/listener_input_type_event_base.cpp new file mode 100644 index 0000000..befa4e5 --- /dev/null +++ b/src/generated/animation/listener_types/listener_input_type_event_base.cpp
@@ -0,0 +1,11 @@ +#include "rive/generated/animation/listener_types/listener_input_type_event_base.hpp" +#include "rive/animation/listener_types/listener_input_type_event.hpp" + +using namespace rive; + +Core* ListenerInputTypeEventBase::clone() const +{ + auto cloned = new ListenerInputTypeEvent(); + cloned->copy(*this); + return cloned; +}
diff --git a/src/generated/animation/listener_types/listener_input_type_viewmodel_base.cpp b/src/generated/animation/listener_types/listener_input_type_viewmodel_base.cpp new file mode 100644 index 0000000..0dc9637 --- /dev/null +++ b/src/generated/animation/listener_types/listener_input_type_viewmodel_base.cpp
@@ -0,0 +1,11 @@ +#include "rive/generated/animation/listener_types/listener_input_type_viewmodel_base.hpp" +#include "rive/animation/listener_types/listener_input_type_viewmodel.hpp" + +using namespace rive; + +Core* ListenerInputTypeViewModelBase::clone() const +{ + auto cloned = new ListenerInputTypeViewModel(); + cloned->copy(*this); + return cloned; +}
diff --git a/src/generated/animation/state_machine_listener_single_base.cpp b/src/generated/animation/state_machine_listener_single_base.cpp new file mode 100644 index 0000000..7fe37ed --- /dev/null +++ b/src/generated/animation/state_machine_listener_single_base.cpp
@@ -0,0 +1,11 @@ +#include "rive/generated/animation/state_machine_listener_single_base.hpp" +#include "rive/animation/state_machine_listener_single.hpp" + +using namespace rive; + +Core* StateMachineListenerSingleBase::clone() const +{ + auto cloned = new StateMachineListenerSingle(); + cloned->copy(*this); + return cloned; +}
diff --git a/src/importers/state_machine_listener_importer.cpp b/src/importers/state_machine_listener_importer.cpp index 19257be..82d0dfd 100644 --- a/src/importers/state_machine_listener_importer.cpp +++ b/src/importers/state_machine_listener_importer.cpp
@@ -1,4 +1,5 @@ #include "rive/animation/listener_action.hpp" +#include "rive/animation/listener_types/listener_input_type.hpp" #include "rive/importers/state_machine_listener_importer.hpp" #include "rive/animation/state_machine_listener.hpp" @@ -15,4 +16,10 @@ m_StateMachineListener->addAction(std::move(action)); } +void StateMachineListenerImporter::addListenerInputType( + std::unique_ptr<ListenerInputType> listenerInputType) +{ + m_StateMachineListener->addListenerInputType(std::move(listenerInputType)); +} + StatusCode StateMachineListenerImporter::resolve() { return StatusCode::Ok; } \ No newline at end of file
diff --git a/src/listener_group.cpp b/src/listener_group.cpp index 217e6a1..7d98b3f 100644 --- a/src/listener_group.cpp +++ b/src/listener_group.cpp
@@ -90,27 +90,24 @@ bool ListenerGroup::canEarlyOut(Component* drawable) { - auto listenerType = m_listener->listenerType(); - return !(listenerType == ListenerType::enter || - listenerType == ListenerType::exit || - listenerType == ListenerType::move || - listenerType == ListenerType::drag); + return !(m_listener->hasListener(ListenerType::enter) || + m_listener->hasListener(ListenerType::exit) || + m_listener->hasListener(ListenerType::move) || + m_listener->hasListener(ListenerType::drag)); } bool ListenerGroup::needsDownListener(Component* drawable) { - auto listenerType = m_listener->listenerType(); - return listenerType == ListenerType::down || - listenerType == ListenerType::click || - listenerType == ListenerType::drag; + return m_listener->hasListener(ListenerType::down) || + m_listener->hasListener(ListenerType::click) || + m_listener->hasListener(ListenerType::drag); } bool ListenerGroup::needsUpListener(Component* drawable) { - auto listenerType = m_listener->listenerType(); - return listenerType == ListenerType::up || - listenerType == ListenerType::click || - listenerType == ListenerType::drag; + return m_listener->hasListener(ListenerType::up) || + m_listener->hasListener(ListenerType::click) || + m_listener->hasListener(ListenerType::drag); } ProcessEventResult ListenerGroup::processEvent( @@ -179,53 +176,37 @@ m_hasDragged = false; } auto _listener = listener(); + bool shouldPerformChanges = false; // Always update hover states regardless of which specific listener type // we're trying to trigger. // If hover has changed and: // - it's hovering and the listener is of type enter // - it's not hovering and the listener is of type exit if (hoverChange && - ((isGroupHovered && _listener->listenerType() == ListenerType::enter) || - (!isGroupHovered && _listener->listenerType() == ListenerType::exit))) + ((isGroupHovered && _listener->hasListener(ListenerType::enter)) || + (!isGroupHovered && _listener->hasListener(ListenerType::exit)))) { - _listener->performChanges( - stateMachineInstance, - position, - Vec2D(previousPosition->x, previousPosition->y), - pointerId); - stateMachineInstance->markNeedsAdvance(); - consume(); + shouldPerformChanges = true; } // Perform changes if: // - the click gesture is complete and the listener is of type click // - the event type matches the listener type and it is hovering the // group if ((pointer->phase == GestureClickPhase::clicked && - _listener->listenerType() == ListenerType::click) || - (isGroupHovered && hitEvent == _listener->listenerType())) + _listener->hasListener(ListenerType::click)) || + (isGroupHovered && _listener->hasListener(hitEvent))) { - _listener->performChanges( - stateMachineInstance, - position, - Vec2D(previousPosition->x, previousPosition->y), - pointerId); - stateMachineInstance->markNeedsAdvance(); - consume(); + shouldPerformChanges = true; } // Perform changes if: // - the listener type is drag // - the clickPhase is down // - the pointer type is move if (pointer->phase == GestureClickPhase::down && - _listener->listenerType() == ListenerType::drag && + _listener->hasListener(ListenerType::drag) && hitEvent == ListenerType::move) { - _listener->performChanges( - stateMachineInstance, - position, - Vec2D(previousPosition->x, previousPosition->y), - pointerId); - stateMachineInstance->markNeedsAdvance(); + shouldPerformChanges = true; if (!m_hasDragged) { stateMachineInstance->dragStart(position, @@ -234,6 +215,16 @@ pointerId); m_hasDragged = true; } + } + if (shouldPerformChanges) + { + + _listener->performChanges( + stateMachineInstance, + position, + Vec2D(previousPosition->x, previousPosition->y), + pointerId); + stateMachineInstance->markNeedsAdvance(); consume(); } previousPosition->x = position.x;
diff --git a/tests/unit_tests/assets/multi_listeners.riv b/tests/unit_tests/assets/multi_listeners.riv new file mode 100644 index 0000000..5c44971 --- /dev/null +++ b/tests/unit_tests/assets/multi_listeners.riv Binary files differ
diff --git a/tests/unit_tests/runtime/state_machine_test.cpp b/tests/unit_tests/runtime/state_machine_test.cpp index 3a4d3d4..8c7a065 100644 --- a/tests/unit_tests/runtime/state_machine_test.cpp +++ b/tests/unit_tests/runtime/state_machine_test.cpp
@@ -15,6 +15,8 @@ #include <rive/shapes/paint/solid_color.hpp> #include <rive/shapes/paint/stroke.hpp> #include <rive/shapes/shape.hpp> +#include <rive/viewmodel/viewmodel_instance_viewmodel.hpp> +#include <rive/viewmodel/viewmodel_instance_number.hpp> #include "catch.hpp" #include "rive_file_reader.hpp" #include "utils/serializing_factory.hpp" @@ -590,4 +592,123 @@ artboard->draw(renderer.get()); CHECK(silver.matches("sorted_listeners")); +} + +TEST_CASE("Listeners with multiple types of events", "[silver]") +{ + SerializingFactory silver; + auto file = ReadRiveFile("assets/multi_listeners.riv", &silver); + + auto artboard = file->artboardDefault(); + silver.frameSize(artboard->width(), artboard->height()); + + auto stateMachine = artboard->stateMachineAt(0); + auto vmi = file->createDefaultViewModelInstance(artboard.get()); + + stateMachine->bindViewModelInstance(vmi); + auto renderer = silver.makeRenderer(); + + stateMachine->advanceAndApply(0.016f); + artboard->draw(renderer.get()); + silver.addFrame(); + + // Trigger first timeline events + stateMachine->advanceAndApply(0.5f); + artboard->draw(renderer.get()); + silver.addFrame(); + + // Trigger second timeline events + stateMachine->advanceAndApply(0.5f); + artboard->draw(renderer.get()); + silver.addFrame(); + + // Process second timeline events + stateMachine->advanceAndApply(0.016f); + artboard->draw(renderer.get()); + silver.addFrame(); + + stateMachine->pointerMove(rive::Vec2D(490.0f, 10.0f)); + stateMachine->advanceAndApply(0.016f); + artboard->draw(renderer.get()); + silver.addFrame(); + + stateMachine->pointerDown(rive::Vec2D(490.0f, 10.0f)); + stateMachine->advanceAndApply(0.016f); + artboard->draw(renderer.get()); + silver.addFrame(); + + stateMachine->pointerUp(rive::Vec2D(490.0f, 10.0f)); + stateMachine->advanceAndApply(0.016f); + artboard->draw(renderer.get()); + + CHECK(silver.matches("multi_listeners")); +} + +TEST_CASE("Listeners with multiple types of events and rebinding", "[silver]") +{ + SerializingFactory silver; + auto file = ReadRiveFile("assets/multi_listeners.riv", &silver); + + auto artboard = file->artboardNamed("Rebind"); + silver.frameSize(artboard->width(), artboard->height()); + + auto stateMachine = artboard->stateMachineAt(0); + auto vmi = file->createDefaultViewModelInstance(artboard.get()); + + auto vmiChildProp = + vmi->propertyValue("child")->as<ViewModelInstanceViewModel>(); + auto vmiChild = vmiChildProp->referenceViewModelInstance(); + + stateMachine->bindViewModelInstance(vmi); + auto renderer = silver.makeRenderer(); + + stateMachine->advanceAndApply(0.016f); + artboard->draw(renderer.get()); + silver.addFrame(); + { + auto num1 = + vmiChild->propertyValue("num")->as<ViewModelInstanceNumber>(); + auto num2 = + vmiChild->propertyValue("num2")->as<ViewModelInstanceNumber>(); + num1->propertyValue(2); + stateMachine->advanceAndApply(0.016f); + artboard->draw(renderer.get()); + silver.addFrame(); + + num2->propertyValue(2); + stateMachine->advanceAndApply(0.016f); + artboard->draw(renderer.get()); + silver.addFrame(); + } + + // Create a new view model instance for RebindChild that will be used to + // replace the vm tree + auto vmiChild2 = file->createViewModelInstance("RebindChild"); + if (vmiChild2) + { + auto num1 = + vmiChild2->propertyValue("num")->as<ViewModelInstanceNumber>(); + auto num2 = + vmiChild2->propertyValue("num2")->as<ViewModelInstanceNumber>(); + vmi->replaceViewModelByName("child", vmiChild2); + stateMachine->advanceAndApply(0.016f); + artboard->draw(renderer.get()); + silver.addFrame(); + num1->propertyValue(2); + stateMachine->advanceAndApply(0.016f); + artboard->draw(renderer.get()); + silver.addFrame(); + num2->propertyValue(2); + stateMachine->advanceAndApply(0.016f); + artboard->draw(renderer.get()); + silver.addFrame(); + // Nothing should change in this case + auto num1Old = + vmiChild->propertyValue("num")->as<ViewModelInstanceNumber>(); + num1Old->propertyValue(3); + stateMachine->advanceAndApply(0.016f); + artboard->draw(renderer.get()); + } + + CHECK(silver.matches("multi_listeners-rebind")); } \ No newline at end of file
diff --git a/tests/unit_tests/silvers/multi_listeners-rebind.sriv b/tests/unit_tests/silvers/multi_listeners-rebind.sriv new file mode 100644 index 0000000..a91e057 --- /dev/null +++ b/tests/unit_tests/silvers/multi_listeners-rebind.sriv Binary files differ
diff --git a/tests/unit_tests/silvers/multi_listeners.sriv b/tests/unit_tests/silvers/multi_listeners.sriv new file mode 100644 index 0000000..4b6f8a5 --- /dev/null +++ b/tests/unit_tests/silvers/multi_listeners.sriv Binary files differ