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>
This commit is contained in:
philter
2025-08-08 01:49:24 +00:00
parent c4420c2afc
commit 27e81b2a43
12 changed files with 213 additions and 8 deletions

View File

@@ -1 +1 @@
4bd8c63b93672af82819728be047578d71018049
9af6af0361de2258ff05a708cacc8e58899e0d4a

View File

@@ -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
}
}
}

View File

@@ -76,6 +76,7 @@ private:
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;

View File

@@ -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

View File

@@ -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 @@ public:
return new FileAssetContents();
case AudioEventBase::typeKey:
return new AudioEvent();
case CustomPropertyTriggerBase::typeKey:
return new CustomPropertyTrigger();
}
return nullptr;
}
@@ -1425,6 +1428,9 @@ public:
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 @@ public:
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 @@ public:
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 @@ public:
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 @@ public:
{
case NestedTriggerBase::firePropertyKey:
case EventBase::triggerPropertyKey:
case CustomPropertyTriggerBase::firePropertyKey:
return true;
default:
return false;
@@ -4359,6 +4372,8 @@ public:
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 @@ public:
return object->is<NestedTriggerBase>();
case EventBase::triggerPropertyKey:
return object->is<EventBase>();
case CustomPropertyTriggerBase::firePropertyKey:
return object->is<CustomPropertyTriggerBase>();
}
return false;
}

View File

@@ -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

View File

@@ -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 @@ StatusCode Artboard::initialize()
{
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 @@ bool Artboard::advanceInternal(float elapsedSeconds, AdvanceFlags flags)
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();
}
}

View File

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

View File

@@ -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 @@ ResettingComponent* ResettingComponent::from(Component* component)
return component->as<NestedArtboard>();
case ArtboardComponentListBase::typeKey:
return component->as<ArtboardComponentList>();
case CustomPropertyTriggerBase::typeKey:
return component->as<CustomPropertyTrigger>();
}
return nullptr;
}

Binary file not shown.

View File

@@ -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 @@ TEST_CASE("Trigger based listeners", "[data binding]")
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"));
}