Support for Triggers in Custom property groups (#10322) 9af6af0361
Adds support for Triggers in Custom Property Groups. This will allow triggers to be keyframed, which can also be bound to ViewModel triggers. Currently an event has to be fired on the timeline in order to fire a ViewModel trigger via a listener.

Co-authored-by: Philip Chung <philterdesign@gmail.com>
diff --git a/.rive_head b/.rive_head
index f929ff2..ef0d699 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-4bd8c63b93672af82819728be047578d71018049
+9af6af0361de2258ff05a708cacc8e58899e0d4a
diff --git a/dev/defs/custom_property_trigger.json b/dev/defs/custom_property_trigger.json
new file mode 100644
index 0000000..45c7f0a
--- /dev/null
+++ b/dev/defs/custom_property_trigger.json
@@ -0,0 +1,30 @@
+{
+  "name": "CustomPropertyTrigger",
+  "key": {
+    "int": 613,
+    "string": "custompropertytrigger"
+  },
+  "extends": "custom_property.json",
+  "properties": {
+    "fire": {
+      "type": "callback",
+      "animates": true,
+      "key": {
+        "int": 869,
+        "string": "fire"
+      },
+      "description": "Callback type used for keying the trigger"
+    },
+    "propertyValue": {
+      "type": "uint",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 870,
+        "string": "propertyvalue"
+      },
+      "description": "Property value used to bind the trigger",
+      "bindable": true
+    }
+  }
+}
\ No newline at end of file
diff --git a/include/rive/artboard.hpp b/include/rive/artboard.hpp
index c879f42..b78be6d 100644
--- a/include/rive/artboard.hpp
+++ b/include/rive/artboard.hpp
@@ -76,6 +76,7 @@
     std::vector<ArtboardComponentList*> m_ComponentLists;
     std::vector<ArtboardHost*> m_ArtboardHosts;
     std::vector<Joystick*> m_Joysticks;
+    std::vector<ResettingComponent*> m_Resettables;
     std::vector<DataBind*> m_DataBinds;
     std::vector<DataBind*> m_AllDataBinds;
     DataContext* m_DataContext = nullptr;
diff --git a/include/rive/custom_property_trigger.hpp b/include/rive/custom_property_trigger.hpp
new file mode 100644
index 0000000..a92baa9
--- /dev/null
+++ b/include/rive/custom_property_trigger.hpp
@@ -0,0 +1,21 @@
+#ifndef _RIVE_CUSTOM_PROPERTY_TRIGGER_HPP_
+#define _RIVE_CUSTOM_PROPERTY_TRIGGER_HPP_
+#include "rive/generated/custom_property_trigger_base.hpp"
+#include "rive/resetting_component.hpp"
+#include <stdio.h>
+namespace rive
+{
+class CustomPropertyTrigger : public CustomPropertyTriggerBase,
+                              public ResettingComponent
+{
+public:
+    void fire(const CallbackData& value) override
+    {
+        propertyValue(propertyValue() + 1);
+    }
+
+    void reset() override { propertyValue(0); }
+};
+} // 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 d465527..c880aef 100644
--- a/include/rive/generated/core_registry.hpp
+++ b/include/rive/generated/core_registry.hpp
@@ -129,6 +129,7 @@
 #include "rive/custom_property_group.hpp"
 #include "rive/custom_property_number.hpp"
 #include "rive/custom_property_string.hpp"
+#include "rive/custom_property_trigger.hpp"
 #include "rive/data_bind/bindable_property.hpp"
 #include "rive/data_bind/bindable_property_artboard.hpp"
 #include "rive/data_bind/bindable_property_asset.hpp"
@@ -748,6 +749,8 @@
                 return new FileAssetContents();
             case AudioEventBase::typeKey:
                 return new AudioEvent();
+            case CustomPropertyTriggerBase::typeKey:
+                return new CustomPropertyTrigger();
         }
         return nullptr;
     }
@@ -1425,6 +1428,9 @@
             case AudioEventBase::assetIdPropertyKey:
                 object->as<AudioEventBase>()->assetId(value);
                 break;
+            case CustomPropertyTriggerBase::propertyValuePropertyKey:
+                object->as<CustomPropertyTriggerBase>()->propertyValue(value);
+                break;
         }
     }
     static void setString(Core* object, int propertyKey, std::string value)
@@ -2318,6 +2324,9 @@
             case EventBase::triggerPropertyKey:
                 object->as<EventBase>()->trigger(value);
                 break;
+            case CustomPropertyTriggerBase::firePropertyKey:
+                object->as<CustomPropertyTriggerBase>()->fire(value);
+                break;
         }
     }
     static uint32_t getUint(Core* object, int propertyKey)
@@ -2804,6 +2813,8 @@
                 return object->as<FileAssetBase>()->assetId();
             case AudioEventBase::assetIdPropertyKey:
                 return object->as<AudioEventBase>()->assetId();
+            case CustomPropertyTriggerBase::propertyValuePropertyKey:
+                return object->as<CustomPropertyTriggerBase>()->propertyValue();
         }
         return 0;
     }
@@ -3636,6 +3647,7 @@
             case TextValueRunBase::styleIdPropertyKey:
             case FileAssetBase::assetIdPropertyKey:
             case AudioEventBase::assetIdPropertyKey:
+            case CustomPropertyTriggerBase::propertyValuePropertyKey:
                 return CoreUintType::id;
             case ViewModelComponentBase::namePropertyKey:
             case DataEnumCustomBase::namePropertyKey:
@@ -3942,6 +3954,7 @@
         {
             case NestedTriggerBase::firePropertyKey:
             case EventBase::triggerPropertyKey:
+            case CustomPropertyTriggerBase::firePropertyKey:
                 return true;
             default:
                 return false;
@@ -4359,6 +4372,8 @@
                 return object->is<FileAssetBase>();
             case AudioEventBase::assetIdPropertyKey:
                 return object->is<AudioEventBase>();
+            case CustomPropertyTriggerBase::propertyValuePropertyKey:
+                return object->is<CustomPropertyTriggerBase>();
             case ViewModelComponentBase::namePropertyKey:
                 return object->is<ViewModelComponentBase>();
             case DataEnumCustomBase::namePropertyKey:
@@ -4927,6 +4942,8 @@
                 return object->is<NestedTriggerBase>();
             case EventBase::triggerPropertyKey:
                 return object->is<EventBase>();
+            case CustomPropertyTriggerBase::firePropertyKey:
+                return object->is<CustomPropertyTriggerBase>();
         }
         return false;
     }
diff --git a/include/rive/generated/custom_property_trigger_base.hpp b/include/rive/generated/custom_property_trigger_base.hpp
new file mode 100644
index 0000000..21e588b
--- /dev/null
+++ b/include/rive/generated/custom_property_trigger_base.hpp
@@ -0,0 +1,76 @@
+#ifndef _RIVE_CUSTOM_PROPERTY_TRIGGER_BASE_HPP_
+#define _RIVE_CUSTOM_PROPERTY_TRIGGER_BASE_HPP_
+#include "rive/core/field_types/core_callback_type.hpp"
+#include "rive/core/field_types/core_uint_type.hpp"
+#include "rive/custom_property.hpp"
+namespace rive
+{
+class CustomPropertyTriggerBase : public CustomProperty
+{
+protected:
+    typedef CustomProperty Super;
+
+public:
+    static const uint16_t typeKey = 613;
+
+    /// Helper to quickly determine if a core object extends another without
+    /// RTTI at runtime.
+    bool isTypeOf(uint16_t typeKey) const override
+    {
+        switch (typeKey)
+        {
+            case CustomPropertyTriggerBase::typeKey:
+            case CustomPropertyBase::typeKey:
+            case ComponentBase::typeKey:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    uint16_t coreType() const override { return typeKey; }
+
+    static const uint16_t firePropertyKey = 869;
+    static const uint16_t propertyValuePropertyKey = 870;
+
+protected:
+    uint32_t m_PropertyValue = 0;
+
+public:
+    virtual void fire(const CallbackData& value) = 0;
+
+    inline uint32_t propertyValue() const { return m_PropertyValue; }
+    void propertyValue(uint32_t value)
+    {
+        if (m_PropertyValue == value)
+        {
+            return;
+        }
+        m_PropertyValue = value;
+        propertyValueChanged();
+    }
+
+    Core* clone() const override;
+    void copy(const CustomPropertyTriggerBase& object)
+    {
+        m_PropertyValue = object.m_PropertyValue;
+        CustomProperty::copy(object);
+    }
+
+    bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
+    {
+        switch (propertyKey)
+        {
+            case propertyValuePropertyKey:
+                m_PropertyValue = CoreUintType::deserialize(reader);
+                return true;
+        }
+        return CustomProperty::deserialize(propertyKey, reader);
+    }
+
+protected:
+    virtual void propertyValueChanged() {}
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/src/artboard.cpp b/src/artboard.cpp
index 659e8d5..ffcf2c8 100644
--- a/src/artboard.cpp
+++ b/src/artboard.cpp
@@ -2,6 +2,7 @@
 #include "rive/artboard_component_list.hpp"
 #include "rive/backboard.hpp"
 #include "rive/animation/linear_animation_instance.hpp"
+#include "rive/custom_property_trigger.hpp"
 #include "rive/dependency_sorter.hpp"
 #include "rive/data_bind/data_bind.hpp"
 #include "rive/data_bind/data_bind_context.hpp"
@@ -223,6 +224,14 @@
         {
             return code;
         }
+        if (object->is<Component>())
+        {
+            auto resettable = ResettingComponent::from(object->as<Component>());
+            if (resettable)
+            {
+                m_Resettables.push_back(resettable);
+            }
+        }
         switch (object->coreType())
         {
             case DrawRulesBase::typeKey:
@@ -995,13 +1004,9 @@
 
 void Artboard::reset()
 {
-    for (auto dep : m_DependencyOrder)
+    for (auto obj : m_Resettables)
     {
-        auto adv = ResettingComponent::from(dep);
-        if (adv != nullptr)
-        {
-            adv->reset();
-        }
+        obj->reset();
     }
 }
 
diff --git a/src/generated/custom_property_trigger_base.cpp b/src/generated/custom_property_trigger_base.cpp
new file mode 100644
index 0000000..ed6a5ab
--- /dev/null
+++ b/src/generated/custom_property_trigger_base.cpp
@@ -0,0 +1,11 @@
+#include "rive/generated/custom_property_trigger_base.hpp"
+#include "rive/custom_property_trigger.hpp"
+
+using namespace rive;
+
+Core* CustomPropertyTriggerBase::clone() const
+{
+    auto cloned = new CustomPropertyTrigger();
+    cloned->copy(*this);
+    return cloned;
+}
diff --git a/src/resetting_component.cpp b/src/resetting_component.cpp
index 074c99a..72f8945 100644
--- a/src/resetting_component.cpp
+++ b/src/resetting_component.cpp
@@ -2,6 +2,7 @@
 #include "rive/resetting_component.hpp"
 #include "rive/artboard.hpp"
 #include "rive/artboard_component_list.hpp"
+#include "rive/custom_property_trigger.hpp"
 #include "rive/nested_artboard.hpp"
 #include "rive/nested_artboard_layout.hpp"
 #include "rive/nested_artboard_leaf.hpp"
@@ -18,6 +19,8 @@
             return component->as<NestedArtboard>();
         case ArtboardComponentListBase::typeKey:
             return component->as<ArtboardComponentList>();
+        case CustomPropertyTriggerBase::typeKey:
+            return component->as<CustomPropertyTrigger>();
     }
     return nullptr;
 }
\ No newline at end of file
diff --git a/tests/unit_tests/assets/custom_property_trigger.riv b/tests/unit_tests/assets/custom_property_trigger.riv
new file mode 100644
index 0000000..a6fafa3
--- /dev/null
+++ b/tests/unit_tests/assets/custom_property_trigger.riv
Binary files differ
diff --git a/tests/unit_tests/runtime/data_binding_test.cpp b/tests/unit_tests/runtime/data_binding_test.cpp
index 6dd843d..2109229 100644
--- a/tests/unit_tests/runtime/data_binding_test.cpp
+++ b/tests/unit_tests/runtime/data_binding_test.cpp
@@ -8,9 +8,10 @@
 #include <rive/shapes/paint/fill.hpp>
 #include <rive/shapes/paint/solid_color.hpp>
 #include <rive/text/text_value_run.hpp>
+#include <rive/custom_property_boolean.hpp>
 #include <rive/custom_property_number.hpp>
 #include <rive/custom_property_string.hpp>
-#include <rive/custom_property_boolean.hpp>
+#include <rive/custom_property_trigger.hpp>
 #include <rive/constraints/follow_path_constraint.hpp>
 #include <rive/viewmodel/viewmodel_instance_number.hpp>
 #include <rive/viewmodel/viewmodel_instance_color.hpp>
@@ -1182,3 +1183,43 @@
 
     CHECK(silver.matches("trigger_based_listeners"));
 }
+
+TEST_CASE("Custom Property Trigger Binding", "[data binding]")
+{
+    rive::SerializingFactory silver;
+    auto file = ReadRiveFile("assets/custom_property_trigger.riv", &silver);
+
+    auto artboard = file->artboard("Main")->instance();
+    REQUIRE(artboard != nullptr);
+    silver.frameSize(artboard->width(), artboard->height());
+
+    auto viewModelInstance =
+        file->createDefaultViewModelInstance(artboard.get());
+    REQUIRE(viewModelInstance != nullptr);
+    auto machine = artboard->defaultStateMachine();
+    machine->bindViewModelInstance(viewModelInstance);
+    REQUIRE(machine != nullptr);
+    // Advance state machine
+    machine->advanceAndApply(0.0f);
+
+    auto circle = artboard->find<rive::Shape>("MainCircle");
+    REQUIRE(circle != nullptr);
+    REQUIRE(circle->scaleX() == 1.0f);
+    REQUIRE(circle->scaleY() == 1.0f);
+
+    auto trig = artboard->find<rive::CustomPropertyTrigger>("Trig");
+    REQUIRE(trig != nullptr);
+
+    auto renderer = silver.makeRenderer();
+    artboard->draw(renderer.get());
+
+    int frames = (int)(1.0f / 0.16f);
+    for (int i = 0; i < frames; i++)
+    {
+        silver.addFrame();
+        machine->advanceAndApply(0.16f);
+        artboard->draw(renderer.get());
+    }
+
+    CHECK(silver.matches("custom_property_trigger_bind"));
+}
\ No newline at end of file
diff --git a/tests/unit_tests/silvers/custom_property_trigger_bind.sriv b/tests/unit_tests/silvers/custom_property_trigger_bind.sriv
new file mode 100644
index 0000000..da1201d
--- /dev/null
+++ b/tests/unit_tests/silvers/custom_property_trigger_bind.sriv
Binary files differ