Choose events to fire on State and Transition start/end. Allows selection of event and when to fire it per state and transition. On a state:  On a transition:  I also kept getting the annoying combo-box stuck at the bottom of the screen with these combos so low to the bottom right, so I fixed that too. Which helps the text combos too. Popups with predictable heights will try to open in the direction where they'll have most vertical space: <img width="570" alt="CleanShot 2023-08-16 at 22 30 02@2x" src="https://github.com/rive-app/rive/assets/454182/88802e09-04df-4256-b4c1-2cc2bf490fcd"> Diffs= ad4236501 Choose events to fire on State and Transition start/end. (#5830) Co-authored-by: Luigi Rosso <luigi-rosso@users.noreply.github.com>
diff --git a/.rive_head b/.rive_head index 498b00a..bb2edbd 100644 --- a/.rive_head +++ b/.rive_head
@@ -1 +1 @@ -abe5aab1431c34a42458a9650831d6b3a7c39feb +ad4236501bdf0218046a93c54f2ee4a379747890
diff --git a/dev/defs/animation/state_machine_fire_event.json b/dev/defs/animation/state_machine_fire_event.json new file mode 100644 index 0000000..063ff2c --- /dev/null +++ b/dev/defs/animation/state_machine_fire_event.json
@@ -0,0 +1,49 @@ +{ + "name": "StateMachineFireEvent", + "key": { + "int": 169, + "string": "statemachinefireevent" + }, + "properties": { + "layerComponentId": { + "type": "Id", + "initialValue": "Core.missingId", + "key": { + "int": 391, + "string": "layercomponentid" + }, + "description": "Id of the transition or layer this belongs to.", + "runtime": false + }, + "eventId": { + "type": "Id", + "typeRuntime": "uint", + "initialValue": "Core.missingId", + "initialValueRuntime": "-1", + "key": { + "int": 392, + "string": "eventid" + }, + "description": "Id of the Event referenced." + }, + "occursValue": { + "type": "uint", + "initialValue": "0", + "key": { + "int": 393, + "string": "occursvalue" + }, + "description": "When the event fires." + }, + "fireOrder": { + "type": "FractionalIndex", + "initialValue": "FractionalIndex.invalid", + "key": { + "int": 394, + "string": "fireorder" + }, + "description": "Order value for sorting transitions in states.", + "runtime": false + } + } +} \ No newline at end of file
diff --git a/include/rive/animation/linear_animation_instance.hpp b/include/rive/animation/linear_animation_instance.hpp index a086dce..1b7c91d 100644 --- a/include/rive/animation/linear_animation_instance.hpp +++ b/include/rive/animation/linear_animation_instance.hpp
@@ -10,18 +10,6 @@ class LinearAnimationInstance : public Scene { -private: - const LinearAnimation* m_Animation = nullptr; - float m_Time; - float m_TotalTime; - float m_LastTotalTime; - float m_SpilledTime; - - // float because it gets multiplied with other floats - float m_Direction; - bool m_DidLoop; - int m_LoopValue = -1; - public: LinearAnimationInstance(const LinearAnimation*, ArtboardInstance*, float speedMultiplier = 1.0); LinearAnimationInstance(LinearAnimationInstance const&); @@ -31,17 +19,17 @@ // animation will continue to animate after this advance. bool advance(float seconds); - void clearSpilledTime() { m_SpilledTime = 0; } + void clearSpilledTime() { m_spilledTime = 0; } // Returns a pointer to the instance's animation - const LinearAnimation* animation() const { return m_Animation; } + const LinearAnimation* animation() const { return m_animation; } // Returns the current point in time at which this instance has advance // to - float time() const { return m_Time; } + float time() const { return m_time; } // Returns the direction that we are currently playing in - float direction() const { return m_Direction; } + float direction() const { return m_direction; } // Update the direction of the animation instance, positive value for // forwards Negative for backwards @@ -49,11 +37,11 @@ { if (direction > 0) { - m_Direction = 1; + m_direction = 1; } else { - m_Direction = -1; + m_direction = -1; } } @@ -63,20 +51,20 @@ // Applies the animation instance to its artboard instance. The mix (a value // between 0 and 1) is the strength at which the animation is mixed with // other animations applied to the artboard. - void apply(float mix = 1.0f) const { m_Animation->apply(m_ArtboardInstance, m_Time, mix); } + void apply(float mix = 1.0f) const { m_animation->apply(m_artboardInstance, m_time, mix); } // Set when the animation is advanced, true if the animation has stopped // (oneShot), reached the end (loop), or changed direction (pingPong) - bool didLoop() const { return m_DidLoop; } + bool didLoop() const { return m_didLoop; } bool keepGoing() const { - return m_LoopValue != static_cast<int>(rive::Loop::oneShot) || !m_DidLoop; + return m_loopValue != static_cast<int>(rive::Loop::oneShot) || !m_didLoop; } - float totalTime() const { return m_TotalTime; } - float lastTotalTime() const { return m_LastTotalTime; } - float spilledTime() const { return m_SpilledTime; } + float totalTime() const { return m_totalTime; } + float lastTotalTime() const { return m_lastTotalTime; } + float spilledTime() const { return m_spilledTime; } float durationSeconds() const override; // Forwarded from animation @@ -95,6 +83,18 @@ bool advanceAndApply(float seconds) override; std::string name() const override; void reset(float speedMultiplier); + +private: + const LinearAnimation* m_animation = nullptr; + float m_time; + float m_totalTime; + float m_lastTotalTime; + float m_spilledTime; + + // float because it gets multiplied with other floats + float m_direction; + bool m_didLoop; + int m_loopValue = -1; }; } // namespace rive #endif
diff --git a/include/rive/animation/state_machine_fire_event.hpp b/include/rive/animation/state_machine_fire_event.hpp new file mode 100644 index 0000000..47526c9 --- /dev/null +++ b/include/rive/animation/state_machine_fire_event.hpp
@@ -0,0 +1,23 @@ +#ifndef _RIVE_STATE_MACHINE_FIRE_EVENT_HPP_ +#define _RIVE_STATE_MACHINE_FIRE_EVENT_HPP_ +#include "rive/generated/animation/state_machine_fire_event_base.hpp" + +namespace rive +{ +class StateMachineInstance; +enum class StateMachineFireOccurance : int +{ + atStart = 0, + atEnd = 1 +}; + +class StateMachineFireEvent : public StateMachineFireEventBase +{ +public: + StatusCode import(ImportStack& importStack) override; + StateMachineFireOccurance occurs() const { return (StateMachineFireOccurance)occursValue(); } + void perform(StateMachineInstance* stateMachineInstance) const; +}; + +} // 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 8bd9877..e08e82d 100644 --- a/include/rive/animation/state_machine_instance.hpp +++ b/include/rive/animation/state_machine_instance.hpp
@@ -28,18 +28,8 @@ friend class SMIInput; private: - const StateMachine* m_Machine; - bool m_NeedsAdvance = false; - - std::vector<SMIInput*> m_InputInstances; // we own each pointer - size_t m_LayerCount; - StateMachineLayerInstance* m_Layers; - void markNeedsAdvance(); - std::vector<std::unique_ptr<HitShape>> m_HitShapes; - std::vector<NestedArtboard*> m_HitNestedArtboards; - /// Provide a hitListener if you want to process a down or an up for the pointer position /// too. void updateListeners(Vec2D position, ListenerType hitListener); @@ -60,9 +50,9 @@ bool needsAdvance() const; // Returns a pointer to the instance's stateMachine - const StateMachine* stateMachine() const { return m_Machine; } + const StateMachine* stateMachine() const { return m_machine; } - size_t inputCount() const override { return m_InputInstances.size(); } + size_t inputCount() const override { return m_inputInstances.size(); } SMIInput* input(size_t index) const override; SMIBool* getBool(const std::string& name) const override; SMINumber* getNumber(const std::string& name) const override; @@ -92,7 +82,7 @@ /// Allow anything referencing a concrete StateMachineInstace access to /// the backing artboard (explicitly not allowed on Scenes). - Artboard* artboard() { return m_ArtboardInstance; } + Artboard* artboard() { return m_artboardInstance; } /// Tracks an event that fired, will be cleared at the end of the next advance. void fireEvent(Event* event); @@ -105,6 +95,13 @@ private: std::vector<Event*> m_firedEvents; + const StateMachine* m_machine; + bool m_needsAdvance = false; + std::vector<SMIInput*> m_inputInstances; // we own each pointer + size_t m_layerCount; + StateMachineLayerInstance* m_layers; + std::vector<std::unique_ptr<HitShape>> m_hitShapes; + std::vector<NestedArtboard*> m_hitNestedArtboards; }; } // namespace rive #endif
diff --git a/include/rive/animation/state_machine_layer_component.hpp b/include/rive/animation/state_machine_layer_component.hpp index 4016d5e..e8c6799 100644 --- a/include/rive/animation/state_machine_layer_component.hpp +++ b/include/rive/animation/state_machine_layer_component.hpp
@@ -1,12 +1,21 @@ #ifndef _RIVE_STATE_MACHINE_LAYER_COMPONENT_HPP_ #define _RIVE_STATE_MACHINE_LAYER_COMPONENT_HPP_ #include "rive/generated/animation/state_machine_layer_component_base.hpp" -#include <stdio.h> +#include <vector> + namespace rive { +class StateMachineFireEvent; class StateMachineLayerComponent : public StateMachineLayerComponentBase { + friend class StateMachineLayerComponentImporter; + public: + const std::vector<StateMachineFireEvent*>& events() const { return m_events; } + ~StateMachineLayerComponent() override; + +private: + std::vector<StateMachineFireEvent*> m_events; }; } // namespace rive
diff --git a/include/rive/generated/animation/state_machine_fire_event_base.hpp b/include/rive/generated/animation/state_machine_fire_event_base.hpp new file mode 100644 index 0000000..f9dd827 --- /dev/null +++ b/include/rive/generated/animation/state_machine_fire_event_base.hpp
@@ -0,0 +1,87 @@ +#ifndef _RIVE_STATE_MACHINE_FIRE_EVENT_BASE_HPP_ +#define _RIVE_STATE_MACHINE_FIRE_EVENT_BASE_HPP_ +#include "rive/core.hpp" +#include "rive/core/field_types/core_uint_type.hpp" +namespace rive +{ +class StateMachineFireEventBase : public Core +{ +protected: + typedef Core Super; + +public: + static const uint16_t typeKey = 169; + + /// Helper to quickly determine if a core object extends another without RTTI + /// at runtime. + bool isTypeOf(uint16_t typeKey) const override + { + switch (typeKey) + { + case StateMachineFireEventBase::typeKey: + return true; + default: + return false; + } + } + + uint16_t coreType() const override { return typeKey; } + + static const uint16_t eventIdPropertyKey = 392; + static const uint16_t occursValuePropertyKey = 393; + +private: + uint32_t m_EventId = -1; + uint32_t m_OccursValue = 0; + +public: + inline uint32_t eventId() const { return m_EventId; } + void eventId(uint32_t value) + { + if (m_EventId == value) + { + return; + } + m_EventId = value; + eventIdChanged(); + } + + inline uint32_t occursValue() const { return m_OccursValue; } + void occursValue(uint32_t value) + { + if (m_OccursValue == value) + { + return; + } + m_OccursValue = value; + occursValueChanged(); + } + + Core* clone() const override; + void copy(const StateMachineFireEventBase& object) + { + m_EventId = object.m_EventId; + m_OccursValue = object.m_OccursValue; + } + + bool deserialize(uint16_t propertyKey, BinaryReader& reader) override + { + switch (propertyKey) + { + case eventIdPropertyKey: + m_EventId = CoreUintType::deserialize(reader); + return true; + case occursValuePropertyKey: + m_OccursValue = CoreUintType::deserialize(reader); + return true; + } + return false; + } + +protected: + virtual void eventIdChanged() {} + virtual void occursValueChanged() {} +}; +} // 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 bf09d27..7984af9 100644 --- a/include/rive/generated/core_registry.hpp +++ b/include/rive/generated/core_registry.hpp
@@ -45,6 +45,7 @@ #include "rive/animation/state_machine.hpp" #include "rive/animation/state_machine_bool.hpp" #include "rive/animation/state_machine_component.hpp" +#include "rive/animation/state_machine_fire_event.hpp" #include "rive/animation/state_machine_input.hpp" #include "rive/animation/state_machine_layer.hpp" #include "rive/animation/state_machine_layer_component.hpp" @@ -231,6 +232,8 @@ return new KeyFrameColor(); case StateMachineBase::typeKey: return new StateMachine(); + case StateMachineFireEventBase::typeKey: + return new StateMachineFireEvent(); case EntryStateBase::typeKey: return new EntryState(); case LinearAnimationBase::typeKey: @@ -511,6 +514,12 @@ case StateTransitionBase::interpolatorIdPropertyKey: object->as<StateTransitionBase>()->interpolatorId(value); break; + case StateMachineFireEventBase::eventIdPropertyKey: + object->as<StateMachineFireEventBase>()->eventId(value); + break; + case StateMachineFireEventBase::occursValuePropertyKey: + object->as<StateMachineFireEventBase>()->occursValue(value); + break; case LinearAnimationBase::fpsPropertyKey: object->as<LinearAnimationBase>()->fps(value); break; @@ -1242,6 +1251,10 @@ return object->as<StateTransitionBase>()->interpolationType(); case StateTransitionBase::interpolatorIdPropertyKey: return object->as<StateTransitionBase>()->interpolatorId(); + case StateMachineFireEventBase::eventIdPropertyKey: + return object->as<StateMachineFireEventBase>()->eventId(); + case StateMachineFireEventBase::occursValuePropertyKey: + return object->as<StateMachineFireEventBase>()->occursValue(); case LinearAnimationBase::fpsPropertyKey: return object->as<LinearAnimationBase>()->fps(); case LinearAnimationBase::durationPropertyKey: @@ -1722,6 +1735,8 @@ case StateTransitionBase::exitTimePropertyKey: case StateTransitionBase::interpolationTypePropertyKey: case StateTransitionBase::interpolatorIdPropertyKey: + case StateMachineFireEventBase::eventIdPropertyKey: + case StateMachineFireEventBase::occursValuePropertyKey: case LinearAnimationBase::fpsPropertyKey: case LinearAnimationBase::durationPropertyKey: case LinearAnimationBase::loopValuePropertyKey:
diff --git a/include/rive/importers/state_machine_layer_component_importer.hpp b/include/rive/importers/state_machine_layer_component_importer.hpp new file mode 100644 index 0000000..b9132a2 --- /dev/null +++ b/include/rive/importers/state_machine_layer_component_importer.hpp
@@ -0,0 +1,22 @@ +#ifndef _RIVE_STATE_MACHINE_LAYER_COMPONENT_IMPORTER_HPP_ +#define _RIVE_STATE_MACHINE_LAYER_COMPONENT_IMPORTER_HPP_ + +#include "rive/importers/import_stack.hpp" + +namespace rive +{ +class StateMachineLayerComponent; +class StateMachineFireEvent; + +class StateMachineLayerComponentImporter : public ImportStackObject +{ +public: + StateMachineLayerComponentImporter(StateMachineLayerComponent* component); + + void addFireEvent(StateMachineFireEvent* fireEvent); + +private: + StateMachineLayerComponent* m_stateMachineLayerComponent; +}; +} // namespace rive +#endif
diff --git a/include/rive/scene.hpp b/include/rive/scene.hpp index 20a9e6a..dd4d6ed 100644 --- a/include/rive/scene.hpp +++ b/include/rive/scene.hpp
@@ -19,14 +19,12 @@ class Scene { protected: - ArtboardInstance* m_ArtboardInstance; - Scene(ArtboardInstance*); public: virtual ~Scene() {} - Scene(Scene const& lhs) : m_ArtboardInstance(lhs.m_ArtboardInstance) {} + Scene(Scene const& lhs) : m_artboardInstance(lhs.m_artboardInstance) {} float width() const; float height() const; @@ -55,6 +53,9 @@ virtual SMIBool* getBool(const std::string&) const; virtual SMINumber* getNumber(const std::string&) const; virtual SMITrigger* getTrigger(const std::string&) const; + +protected: + ArtboardInstance* m_artboardInstance; }; } // namespace rive
diff --git a/src/animation/linear_animation_instance.cpp b/src/animation/linear_animation_instance.cpp index 0e9ea17..ac527f8 100644 --- a/src/animation/linear_animation_instance.cpp +++ b/src/animation/linear_animation_instance.cpp
@@ -11,26 +11,26 @@ ArtboardInstance* instance, float speedMultiplier) : Scene(instance), - m_Animation((assert(animation != nullptr), animation)), - m_Time((speedMultiplier >= 0) ? animation->startTime() : animation->endTime()), - m_TotalTime(0.0f), - m_LastTotalTime(0.0f), - m_SpilledTime(0.0f), - m_Direction(1) + m_animation((assert(animation != nullptr), animation)), + m_time((speedMultiplier >= 0) ? animation->startTime() : animation->endTime()), + m_totalTime(0.0f), + m_lastTotalTime(0.0f), + m_spilledTime(0.0f), + m_direction(1) { Counter::update(Counter::kLinearAnimationInstance, +1); } LinearAnimationInstance::LinearAnimationInstance(LinearAnimationInstance const& lhs) : Scene(lhs), - m_Animation(lhs.m_Animation), - m_Time(lhs.m_Time), - m_TotalTime(lhs.m_TotalTime), - m_LastTotalTime(lhs.m_LastTotalTime), - m_SpilledTime(lhs.m_SpilledTime), - m_Direction(lhs.m_Direction), - m_DidLoop(lhs.m_DidLoop), - m_LoopValue(lhs.m_LoopValue) + m_animation(lhs.m_animation), + m_time(lhs.m_time), + m_totalTime(lhs.m_totalTime), + m_lastTotalTime(lhs.m_lastTotalTime), + m_spilledTime(lhs.m_spilledTime), + m_direction(lhs.m_direction), + m_didLoop(lhs.m_didLoop), + m_loopValue(lhs.m_loopValue) { Counter::update(Counter::kLinearAnimationInstance, +1); } @@ -44,30 +44,30 @@ { bool more = this->advance(seconds); this->apply(); - m_ArtboardInstance->advance(seconds); + m_artboardInstance->advance(seconds); return more; } bool LinearAnimationInstance::advance(float elapsedSeconds) { - const LinearAnimation& animation = *m_Animation; - float deltaSeconds = elapsedSeconds * animation.speed() * m_Direction; + const LinearAnimation& animation = *m_animation; + float deltaSeconds = elapsedSeconds * animation.speed() * m_direction; if (deltaSeconds == 0) { // we say keep going, if you advance by 0. // could argue that any further advances by 0 result in nothing so you should not keep going // could argue its saying, we are not at the end of the animation yet, so keep going // our runtimes currently expect the latter, so we say keep going! - m_DidLoop = false; + m_didLoop = false; return true; } - m_LastTotalTime = m_TotalTime; - m_TotalTime += std::abs(deltaSeconds); - m_Time += deltaSeconds; + m_lastTotalTime = m_totalTime; + m_totalTime += std::abs(deltaSeconds); + m_time += deltaSeconds; int fps = animation.fps(); - float frames = m_Time * fps; + float frames = m_time * fps; int start = animation.enableWorkArea() ? animation.workStart() : 0; int end = animation.enableWorkArea() ? animation.workEnd() : animation.duration(); @@ -75,7 +75,7 @@ bool keepGoing = true; bool didLoop = false; - m_SpilledTime = 0.0f; + m_spilledTime = 0.0f; // this has some issues when deltaSeconds is 0, // right now we basically assume we default to going forwards in that case @@ -87,36 +87,36 @@ if (direction == 1 && frames > end) { keepGoing = false; - m_SpilledTime = (frames - end) / fps; + m_spilledTime = (frames - end) / fps; frames = (float)end; - m_Time = frames / fps; + m_time = frames / fps; didLoop = true; } else if (direction == -1 && frames < start) { keepGoing = false; - m_SpilledTime = (start - frames) / fps; + m_spilledTime = (start - frames) / fps; frames = (float)start; - m_Time = frames / fps; + m_time = frames / fps; didLoop = true; } break; case Loop::loop: if (direction == 1 && frames >= end) { - m_SpilledTime = (frames - end) / fps; - frames = m_Time * fps; + m_spilledTime = (frames - end) / fps; + frames = m_time * fps; frames = start + std::fmod(frames - start, (float)range); - m_Time = frames / fps; + m_time = frames / fps; didLoop = true; } else if (direction == -1 && frames <= start) { - m_SpilledTime = (start - frames) / fps; - frames = m_Time * fps; + m_spilledTime = (start - frames) / fps; + frames = m_time * fps; frames = end - std::abs(std::fmod(start - frames, (float)range)); - m_Time = frames / fps; + m_time = frames / fps; didLoop = true; } break; @@ -125,12 +125,12 @@ { if (direction == 1 && frames >= end) { - m_SpilledTime = (frames - end) / fps; + m_spilledTime = (frames - end) / fps; frames = end + (end - frames); } else if (direction == -1 && frames < start) { - m_SpilledTime = (start - frames) / fps; + m_spilledTime = (start - frames) / fps; frames = start + (start - frames); } else @@ -142,80 +142,80 @@ // where animations are not advanced on regular intervals. break; } - m_Time = frames / fps; - m_Direction *= -1; + m_time = frames / fps; + m_direction *= -1; direction *= -1; didLoop = true; } break; } - m_DidLoop = didLoop; + m_didLoop = didLoop; return keepGoing; } void LinearAnimationInstance::time(float value) { - if (m_Time == value) + if (m_time == value) { return; } - m_Time = value; + m_time = value; // Make sure to keep last and total in relative lockstep so state machines // can track change even when setting time. - auto diff = m_TotalTime - m_LastTotalTime; + auto diff = m_totalTime - m_lastTotalTime; - int start = (m_Animation->enableWorkArea() ? m_Animation->workStart() : 0) * m_Animation->fps(); - m_TotalTime = value - start; - m_LastTotalTime = m_TotalTime - diff; + int start = (m_animation->enableWorkArea() ? m_animation->workStart() : 0) * m_animation->fps(); + m_totalTime = value - start; + m_lastTotalTime = m_totalTime - diff; // leaving this RIGHT now. but is this required? it kinda messes up // playing things backwards and seeking. what purpose does it solve? - m_Direction = 1; + m_direction = 1; } void LinearAnimationInstance::reset(float speedMultiplier = 1.0) { - m_Time = (speedMultiplier >= 0) ? m_Animation->startTime() : m_Animation->endTime(); + m_time = (speedMultiplier >= 0) ? m_animation->startTime() : m_animation->endTime(); } -uint32_t LinearAnimationInstance::fps() const { return m_Animation->fps(); } +uint32_t LinearAnimationInstance::fps() const { return m_animation->fps(); } -uint32_t LinearAnimationInstance::duration() const { return m_Animation->duration(); } +uint32_t LinearAnimationInstance::duration() const { return m_animation->duration(); } -float LinearAnimationInstance::speed() const { return m_Animation->speed(); } +float LinearAnimationInstance::speed() const { return m_animation->speed(); } -float LinearAnimationInstance::startTime() const { return m_Animation->startTime(); } +float LinearAnimationInstance::startTime() const { return m_animation->startTime(); } -std::string LinearAnimationInstance::name() const { return m_Animation->name(); } +std::string LinearAnimationInstance::name() const { return m_animation->name(); } bool LinearAnimationInstance::isTranslucent() const { - return m_ArtboardInstance->isTranslucent(this); + return m_artboardInstance->isTranslucent(this); } // Returns either the animation's default or overridden loop values int LinearAnimationInstance::loopValue() const { - if (m_LoopValue != -1) + if (m_loopValue != -1) { - return m_LoopValue; + return m_loopValue; } - return m_Animation->loopValue(); + return m_animation->loopValue(); } // Override the animation's loop value void LinearAnimationInstance::loopValue(int value) { - if (m_LoopValue == value) + if (m_loopValue == value) { return; } - if (m_LoopValue == -1 && m_Animation->loopValue() == value) + if (m_loopValue == -1 && m_animation->loopValue() == value) { return; } - m_LoopValue = value; + m_loopValue = value; } -float LinearAnimationInstance::durationSeconds() const { return m_Animation->durationSeconds(); } +float LinearAnimationInstance::durationSeconds() const { return m_animation->durationSeconds(); }
diff --git a/src/animation/state_machine_instance.cpp b/src/animation/state_machine_instance.cpp index cbccf6d..1993115 100644 --- a/src/animation/state_machine_instance.cpp +++ b/src/animation/state_machine_instance.cpp
@@ -16,6 +16,7 @@ #include "rive/animation/state_machine.hpp" #include "rive/animation/state_transition.hpp" #include "rive/animation/transition_condition.hpp" +#include "rive/animation/state_machine_fire_event.hpp" #include "rive/math/aabb.hpp" #include "rive/math/hit_test.hpp" #include "rive/nested_animation.hpp" @@ -29,78 +30,61 @@ { class StateMachineLayerInstance { -private: - static const int maxIterations = 100; - const StateMachineLayer* m_Layer = nullptr; - ArtboardInstance* m_ArtboardInstance = nullptr; - - StateInstance* m_AnyStateInstance = nullptr; - StateInstance* m_CurrentState = nullptr; - StateInstance* m_StateFrom = nullptr; - - // const LayerState* m_CurrentState = nullptr; - // const LayerState* m_StateFrom = nullptr; - const StateTransition* m_Transition = nullptr; - - bool m_HoldAnimationFrom = false; - // LinearAnimationInstance* m_AnimationInstance = nullptr; - // LinearAnimationInstance* m_AnimationInstanceFrom = nullptr; - float m_Mix = 1.0f; - float m_MixFrom = 1.0f; - bool m_StateChangedOnAdvance = false; - - bool m_WaitingForExit = false; - /// Used to ensure a specific animation is applied on the next apply. - const LinearAnimation* m_HoldAnimation = nullptr; - float m_HoldTime = 0.0f; - public: ~StateMachineLayerInstance() { - delete m_AnyStateInstance; - delete m_CurrentState; - delete m_StateFrom; + delete m_anyStateInstance; + delete m_currentState; + delete m_stateFrom; } - void init(const StateMachineLayer* layer, ArtboardInstance* instance) + void init(StateMachineInstance* stateMachineInstance, + const StateMachineLayer* layer, + ArtboardInstance* instance) { - m_ArtboardInstance = instance; - assert(m_Layer == nullptr); - m_AnyStateInstance = layer->anyState()->makeInstance(instance).release(); - m_Layer = layer; - changeState(m_Layer->entryState()); + m_stateMachineInstance = stateMachineInstance; + m_artboardInstance = instance; + assert(m_layer == nullptr); + m_anyStateInstance = layer->anyState()->makeInstance(instance).release(); + m_layer = layer; + changeState(m_layer->entryState()); } void updateMix(float seconds) { - if (m_Transition != nullptr && m_StateFrom != nullptr && m_Transition->duration() != 0) + if (m_transition != nullptr && m_stateFrom != nullptr && m_transition->duration() != 0) { - m_Mix = std::min( + m_mix = std::min( 1.0f, - std::max(0.0f, (m_Mix + seconds / m_Transition->mixTime(m_StateFrom->state())))); + std::max(0.0f, (m_mix + seconds / m_transition->mixTime(m_stateFrom->state())))); + if (m_mix == 1.0f && !m_transitionCompleted) + { + m_transitionCompleted = true; + fireEvents(StateMachineFireOccurance::atEnd, m_transition->events()); + } } else { - m_Mix = 1.0f; + m_mix = 1.0f; } } bool advance(float seconds, Span<SMIInput*> inputs) { - m_StateChangedOnAdvance = false; + m_stateMachineChangedOnAdvance = false; - if (m_CurrentState != nullptr && m_CurrentState->keepGoing()) + if (m_currentState != nullptr && m_currentState->keepGoing()) { - m_CurrentState->advance(seconds, inputs); + m_currentState->advance(seconds, inputs); } updateMix(seconds); - if (m_StateFrom != nullptr && m_Mix < 1.0f && !m_HoldAnimationFrom) + if (m_stateFrom != nullptr && m_mix < 1.0f && !m_holdAnimationFrom) { // This didn't advance during our updateState, but it should now // that we realize we need to mix it in. - m_StateFrom->advance(seconds, inputs); + m_stateFrom->advance(seconds, inputs); } for (int i = 0; updateState(inputs, i != 0); i++) @@ -116,16 +100,16 @@ apply(); - m_CurrentState->clearSpilledTime(); + m_currentState->clearSpilledTime(); - return m_Mix != 1.0f || m_WaitingForExit || - (m_CurrentState != nullptr && m_CurrentState->keepGoing()); + return m_mix != 1.0f || m_waitingForExit || + (m_currentState != nullptr && m_currentState->keepGoing()); } bool isTransitioning() { - return m_Transition != nullptr && m_StateFrom != nullptr && m_Transition->duration() != 0 && - m_Mix < 1.0f; + return m_transition != nullptr && m_stateFrom != nullptr && m_transition->duration() != 0 && + m_mix < 1.0f; } bool updateState(Span<SMIInput*> inputs, bool ignoreTriggers) @@ -137,24 +121,49 @@ return false; } - m_WaitingForExit = false; + m_waitingForExit = false; - if (tryChangeState(m_AnyStateInstance, inputs, ignoreTriggers)) + if (tryChangeState(m_anyStateInstance, inputs, ignoreTriggers)) { return true; } - return tryChangeState(m_CurrentState, inputs, ignoreTriggers); + return tryChangeState(m_currentState, inputs, ignoreTriggers); + } + + void fireEvents(StateMachineFireOccurance occurs, + const std::vector<StateMachineFireEvent*>& fireEvents) + { + for (auto event : fireEvents) + { + if (event->occurs() == occurs) + { + event->perform(m_stateMachineInstance); + } + } } bool changeState(const LayerState* stateTo) { - if ((m_CurrentState == nullptr ? nullptr : m_CurrentState->state()) == stateTo) + if ((m_currentState == nullptr ? nullptr : m_currentState->state()) == stateTo) { return false; } - m_CurrentState = - stateTo == nullptr ? nullptr : stateTo->makeInstance(m_ArtboardInstance).release(); + + // Fire end events for the state we're changing from. + if (m_currentState != nullptr) + { + fireEvents(StateMachineFireOccurance::atEnd, m_currentState->state()->events()); + } + + m_currentState = + stateTo == nullptr ? nullptr : stateTo->makeInstance(m_artboardInstance).release(); + + // Fire start events for the state we're changing to. + if (m_currentState != nullptr) + { + fireEvents(StateMachineFireOccurance::atStart, m_currentState->state()->events()); + } return true; } @@ -167,22 +176,33 @@ return false; } auto stateFrom = stateFromInstance->state(); - auto outState = m_CurrentState; + auto outState = m_currentState; for (size_t i = 0, length = stateFrom->transitionCount(); i < length; i++) { auto transition = stateFrom->transition(i); auto allowed = transition->allowed(stateFromInstance, inputs, ignoreTriggers); if (allowed == AllowTransition::yes && changeState(transition->stateTo())) { - m_StateChangedOnAdvance = true; + m_stateMachineChangedOnAdvance = true; // state actually has changed - m_Transition = transition; - if (m_StateFrom != m_AnyStateInstance) + m_transition = transition; + fireEvents(StateMachineFireOccurance::atStart, transition->events()); + if (transition->duration() == 0) + { + m_transitionCompleted = true; + fireEvents(StateMachineFireOccurance::atEnd, transition->events()); + } + else + { + m_transitionCompleted = false; + } + + if (m_stateFrom != m_anyStateInstance) { // Old state from is done. - delete m_StateFrom; + delete m_stateFrom; } - m_StateFrom = outState; + m_stateFrom = outState; // If we had an exit time and wanted to pause on exit, make // sure to hold the exit time. Delegate this to the @@ -192,35 +212,35 @@ // Make sure we apply this state. This only returns true // when it's an animation state instance. auto instance = - static_cast<AnimationStateInstance*>(m_StateFrom)->animationInstance(); + static_cast<AnimationStateInstance*>(m_stateFrom)->animationInstance(); - m_HoldAnimation = instance->animation(); - m_HoldTime = instance->time(); + m_holdAnimation = instance->animation(); + m_holdTime = instance->time(); } - m_MixFrom = m_Mix; + m_mixFrom = m_mix; // Keep mixing last animation that was mixed in. - if (m_Mix != 0.0f) + if (m_mix != 0.0f) { - m_HoldAnimationFrom = transition->pauseOnExit(); + m_holdAnimationFrom = transition->pauseOnExit(); } - if (m_StateFrom != nullptr && m_StateFrom->state()->is<AnimationState>() && - m_CurrentState != nullptr) + if (m_stateFrom != nullptr && m_stateFrom->state()->is<AnimationState>() && + m_currentState != nullptr) { auto instance = - static_cast<AnimationStateInstance*>(m_StateFrom)->animationInstance(); + static_cast<AnimationStateInstance*>(m_stateFrom)->animationInstance(); auto spilledTime = instance->spilledTime(); - m_CurrentState->advance(spilledTime, inputs); + m_currentState->advance(spilledTime, inputs); } - m_Mix = 0.0f; + m_mix = 0.0f; updateMix(0.0f); - m_WaitingForExit = false; + m_waitingForExit = false; return true; } else if (allowed == AllowTransition::waitingForExit) { - m_WaitingForExit = true; + m_waitingForExit = true; } } return false; @@ -228,45 +248,69 @@ void apply(/*Artboard* artboard*/) { - if (m_HoldAnimation != nullptr) + if (m_holdAnimation != nullptr) { - m_HoldAnimation->apply(m_ArtboardInstance, m_HoldTime, m_MixFrom); - m_HoldAnimation = nullptr; + m_holdAnimation->apply(m_artboardInstance, m_holdTime, m_mixFrom); + m_holdAnimation = nullptr; } CubicInterpolator* cubic = nullptr; - if (m_Transition != nullptr && m_Transition->interpolator() != nullptr) + if (m_transition != nullptr && m_transition->interpolator() != nullptr) { - cubic = m_Transition->interpolator(); + cubic = m_transition->interpolator(); } - if (m_StateFrom != nullptr && m_Mix < 1.0f) + if (m_stateFrom != nullptr && m_mix < 1.0f) { - auto fromMix = cubic != nullptr ? cubic->transform(m_MixFrom) : m_MixFrom; - m_StateFrom->apply(fromMix); + auto fromMix = cubic != nullptr ? cubic->transform(m_mixFrom) : m_mixFrom; + m_stateFrom->apply(fromMix); } - if (m_CurrentState != nullptr) + if (m_currentState != nullptr) { - auto mix = cubic != nullptr ? cubic->transform(m_Mix) : m_Mix; - m_CurrentState->apply(mix); + auto mix = cubic != nullptr ? cubic->transform(m_mix) : m_mix; + m_currentState->apply(mix); } } - bool stateChangedOnAdvance() const { return m_StateChangedOnAdvance; } + bool stateChangedOnAdvance() const { return m_stateMachineChangedOnAdvance; } const LayerState* currentState() { - return m_CurrentState == nullptr ? nullptr : m_CurrentState->state(); + return m_currentState == nullptr ? nullptr : m_currentState->state(); } const LinearAnimationInstance* currentAnimation() const { - if (m_CurrentState == nullptr || !m_CurrentState->state()->is<AnimationState>()) + if (m_currentState == nullptr || !m_currentState->state()->is<AnimationState>()) { return nullptr; } - return static_cast<AnimationStateInstance*>(m_CurrentState)->animationInstance(); + return static_cast<AnimationStateInstance*>(m_currentState)->animationInstance(); } + +private: + static const int maxIterations = 100; + StateMachineInstance* m_stateMachineInstance = nullptr; + const StateMachineLayer* m_layer = nullptr; + ArtboardInstance* m_artboardInstance = nullptr; + + StateInstance* m_anyStateInstance = nullptr; + StateInstance* m_currentState = nullptr; + StateInstance* m_stateFrom = nullptr; + + const StateTransition* m_transition = nullptr; + bool m_transitionCompleted = false; + + bool m_holdAnimationFrom = false; + + float m_mix = 1.0f; + float m_mixFrom = 1.0f; + bool m_stateMachineChangedOnAdvance = false; + + bool m_waitingForExit = false; + /// Used to ensure a specific animation is applied on the next apply. + const LinearAnimation* m_holdAnimation = nullptr; + float m_holdTime = 0.0f; }; /// Representation of a Shape from the Artboard Instance and all the listeners it @@ -274,23 +318,23 @@ /// shapes that trigger multiple listeners. class HitShape { -private: - Shape* m_Shape; - public: - Shape* shape() const { return m_Shape; } - HitShape(Shape* shape) : m_Shape(shape) {} + Shape* shape() const { return m_shape; } + HitShape(Shape* shape) : m_shape(shape) {} bool isHovered = false; std::vector<const StateMachineListener*> listeners; + +private: + Shape* m_shape; }; } // namespace rive void StateMachineInstance::updateListeners(Vec2D position, ListenerType hitType) { - if (m_ArtboardInstance->frameOrigin()) + if (m_artboardInstance->frameOrigin()) { - position -= Vec2D(m_ArtboardInstance->originX() * m_ArtboardInstance->width(), - m_ArtboardInstance->originY() * m_ArtboardInstance->height()); + position -= Vec2D(m_artboardInstance->originX() * m_artboardInstance->width(), + m_artboardInstance->originY() * m_artboardInstance->height()); } const float hitRadius = 2; @@ -300,7 +344,7 @@ position.y + hitRadius) .round(); - for (const auto& hitShape : m_HitShapes) + for (const auto& hitShape : m_hitShapes) { // TODO: quick reject. @@ -340,7 +384,7 @@ // can be sorted by drawOrder so they can be iterated in one loop and early // out if any hit stops propagation (also require the ability to mark a hit // as able to stop propagation) - for (auto nestedArtboard : m_HitNestedArtboards) + for (auto nestedArtboard : m_hitNestedArtboards) { Vec2D nestedPosition; if (!nestedArtboard->worldToLocal(position, &nestedPosition)) @@ -389,12 +433,12 @@ StateMachineInstance::StateMachineInstance(const StateMachine* machine, ArtboardInstance* instance) : - Scene(instance), m_Machine(machine) + Scene(instance), m_machine(machine) { Counter::update(Counter::kStateMachineInstance, +1); const auto count = machine->inputCount(); - m_InputInstances.resize(count); + m_inputInstances.resize(count); for (size_t i = 0; i < count; i++) { auto input = machine->input(i); @@ -405,13 +449,13 @@ switch (input->coreType()) { case StateMachineBool::typeKey: - m_InputInstances[i] = new SMIBool(input->as<StateMachineBool>(), this); + m_inputInstances[i] = new SMIBool(input->as<StateMachineBool>(), this); break; case StateMachineNumber::typeKey: - m_InputInstances[i] = new SMINumber(input->as<StateMachineNumber>(), this); + m_inputInstances[i] = new SMINumber(input->as<StateMachineNumber>(), this); break; case StateMachineTrigger::typeKey: - m_InputInstances[i] = new SMITrigger(input->as<StateMachineTrigger>(), this); + m_inputInstances[i] = new SMITrigger(input->as<StateMachineTrigger>(), this); break; default: // Sanity check. @@ -419,11 +463,11 @@ } } - m_LayerCount = machine->layerCount(); - m_Layers = new StateMachineLayerInstance[m_LayerCount]; - for (size_t i = 0; i < m_LayerCount; i++) + m_layerCount = machine->layerCount(); + m_layers = new StateMachineLayerInstance[m_layerCount]; + for (size_t i = 0; i < m_layerCount; i++) { - m_Layers[i].init(machine->layer(i), m_ArtboardInstance); + m_layers[i].init(this, machine->layer(i), m_artboardInstance); } // Initialize listeners. Store a lookup table of shape id to hit shape @@ -442,12 +486,12 @@ auto itr = hitShapeLookup.find(id); if (itr == hitShapeLookup.end()) { - auto shape = m_ArtboardInstance->resolve(id); + auto shape = m_artboardInstance->resolve(id); if (shape != nullptr && shape->is<Shape>()) { auto hs = rivestd::make_unique<HitShape>(shape->as<Shape>()); hitShapeLookup[id] = hitShape = hs.get(); - m_HitShapes.push_back(std::move(hs)); + m_hitShapes.push_back(std::move(hs)); } else { @@ -467,18 +511,18 @@ { if (nestedArtboard->hasNestedStateMachines()) { - m_HitNestedArtboards.push_back(nestedArtboard); + m_hitNestedArtboards.push_back(nestedArtboard); } } } StateMachineInstance::~StateMachineInstance() { - for (auto inst : m_InputInstances) + for (auto inst : m_inputInstances) { delete inst; } - delete[] m_Layers; + delete[] m_layers; Counter::update(Counter::kStateMachineInstance, -1); } @@ -486,40 +530,40 @@ bool StateMachineInstance::advance(float seconds) { m_firedEvents.clear(); - m_NeedsAdvance = false; - for (size_t i = 0; i < m_LayerCount; i++) + m_needsAdvance = false; + for (size_t i = 0; i < m_layerCount; i++) { - if (m_Layers[i].advance(seconds, m_InputInstances)) + if (m_layers[i].advance(seconds, m_inputInstances)) { - m_NeedsAdvance = true; + m_needsAdvance = true; } } - for (auto inst : m_InputInstances) + for (auto inst : m_inputInstances) { inst->advanced(); } - return m_NeedsAdvance; + return m_needsAdvance; } bool StateMachineInstance::advanceAndApply(float seconds) { bool more = this->advance(seconds); - m_ArtboardInstance->advance(seconds); + m_artboardInstance->advance(seconds); return more; } -void StateMachineInstance::markNeedsAdvance() { m_NeedsAdvance = true; } -bool StateMachineInstance::needsAdvance() const { return m_NeedsAdvance; } +void StateMachineInstance::markNeedsAdvance() { m_needsAdvance = true; } +bool StateMachineInstance::needsAdvance() const { return m_needsAdvance; } -std::string StateMachineInstance::name() const { return m_Machine->name(); } +std::string StateMachineInstance::name() const { return m_machine->name(); } SMIInput* StateMachineInstance::input(size_t index) const { - if (index < m_InputInstances.size()) + if (index < m_inputInstances.size()) { - return m_InputInstances[index]; + return m_inputInstances[index]; } return nullptr; } @@ -527,7 +571,7 @@ template <typename SMType, typename InstType> InstType* StateMachineInstance::getNamedInput(const std::string& name) const { - for (const auto inst : m_InputInstances) + for (const auto inst : m_inputInstances) { auto input = inst->input(); if (input->is<SMType>() && input->name() == name) @@ -554,9 +598,9 @@ size_t StateMachineInstance::stateChangedCount() const { size_t count = 0; - for (size_t i = 0; i < m_LayerCount; i++) + for (size_t i = 0; i < m_layerCount; i++) { - if (m_Layers[i].stateChangedOnAdvance()) + if (m_layers[i].stateChangedOnAdvance()) { count++; } @@ -567,13 +611,13 @@ const LayerState* StateMachineInstance::stateChangedByIndex(size_t index) const { size_t count = 0; - for (size_t i = 0; i < m_LayerCount; i++) + for (size_t i = 0; i < m_layerCount; i++) { - if (m_Layers[i].stateChangedOnAdvance()) + if (m_layers[i].stateChangedOnAdvance()) { if (count == index) { - return m_Layers[i].currentState(); + return m_layers[i].currentState(); } count++; } @@ -584,9 +628,9 @@ size_t StateMachineInstance::currentAnimationCount() const { size_t count = 0; - for (size_t i = 0; i < m_LayerCount; i++) + for (size_t i = 0; i < m_layerCount; i++) { - if (m_Layers[i].currentAnimation() != nullptr) + if (m_layers[i].currentAnimation() != nullptr) { count++; } @@ -597,13 +641,13 @@ const LinearAnimationInstance* StateMachineInstance::currentAnimationByIndex(size_t index) const { size_t count = 0; - for (size_t i = 0; i < m_LayerCount; i++) + for (size_t i = 0; i < m_layerCount; i++) { - if (m_Layers[i].currentAnimation() != nullptr) + if (m_layers[i].currentAnimation() != nullptr) { if (count == index) { - return m_Layers[i].currentAnimation(); + return m_layers[i].currentAnimation(); } count++; }
diff --git a/src/file.cpp b/src/file.cpp index 2aea9cc..19c9a26 100644 --- a/src/file.cpp +++ b/src/file.cpp
@@ -19,6 +19,7 @@ #include "rive/importers/state_machine_layer_importer.hpp" #include "rive/importers/layer_state_importer.hpp" #include "rive/importers/state_transition_importer.hpp" +#include "rive/importers/state_machine_layer_component_importer.hpp" #include "rive/animation/blend_state_transition.hpp" #include "rive/animation/any_state.hpp" #include "rive/animation/entry_state.hpp" @@ -287,6 +288,13 @@ // Some previous stack item didn't resolve. return ImportResult::malformed; } + if (object->is<StateMachineLayerComponent>() && + importStack.makeLatest(StateMachineLayerComponent::typeKey, + new StateMachineLayerComponentImporter( + object->as<StateMachineLayerComponent>())) != StatusCode::Ok) + { + return ImportResult::malformed; + } } return !reader.hasError() && importStack.resolve() == StatusCode::Ok ? ImportResult::success
diff --git a/src/generated/animation/state_machine_fire_event.cpp b/src/generated/animation/state_machine_fire_event.cpp new file mode 100644 index 0000000..f5f4d7b --- /dev/null +++ b/src/generated/animation/state_machine_fire_event.cpp
@@ -0,0 +1,30 @@ +#include "rive/generated/animation/state_machine_fire_event_base.hpp" +#include "rive/animation/state_machine_fire_event.hpp" +#include "rive/animation/state_machine_layer_component.hpp" +#include "rive/animation/state_machine_instance.hpp" +#include "rive/event.hpp" +#include "rive/importers/state_machine_layer_component_importer.hpp" + +using namespace rive; + +StatusCode StateMachineFireEvent::import(ImportStack& importStack) +{ + auto stateImporter = + importStack.latest<StateMachineLayerComponentImporter>(StateMachineLayerComponent::typeKey); + if (stateImporter == nullptr) + { + return StatusCode::MissingObject; + } + stateImporter->addFireEvent(this); + return Super::import(importStack); +} + +void StateMachineFireEvent::perform(StateMachineInstance* stateMachineInstance) const +{ + auto coreEvent = stateMachineInstance->artboard()->resolve(eventId()); + if (coreEvent == nullptr || !coreEvent->is<Event>()) + { + return; + } + stateMachineInstance->fireEvent(coreEvent->as<Event>()); +} \ No newline at end of file
diff --git a/src/generated/animation/state_machine_fire_event_base.cpp b/src/generated/animation/state_machine_fire_event_base.cpp new file mode 100644 index 0000000..b43b331 --- /dev/null +++ b/src/generated/animation/state_machine_fire_event_base.cpp
@@ -0,0 +1,11 @@ +#include "rive/generated/animation/state_machine_fire_event_base.hpp" +#include "rive/animation/state_machine_fire_event.hpp" + +using namespace rive; + +Core* StateMachineFireEventBase::clone() const +{ + auto cloned = new StateMachineFireEvent(); + cloned->copy(*this); + return cloned; +}
diff --git a/src/importers/state_machine_layer_component_importer.cpp b/src/importers/state_machine_layer_component_importer.cpp new file mode 100644 index 0000000..d8e4c6d --- /dev/null +++ b/src/importers/state_machine_layer_component_importer.cpp
@@ -0,0 +1,23 @@ +#include "rive/importers/state_machine_layer_component_importer.hpp" +#include "rive/animation/state_machine_layer_component.hpp" +#include "rive/animation/state_machine_fire_event.hpp" + +using namespace rive; + +StateMachineLayerComponent::~StateMachineLayerComponent() +{ + for (auto event : m_events) + { + delete event; + } +} + +StateMachineLayerComponentImporter::StateMachineLayerComponentImporter( + StateMachineLayerComponent* component) : + m_stateMachineLayerComponent(component) +{} + +void StateMachineLayerComponentImporter::addFireEvent(StateMachineFireEvent* fireEvent) +{ + m_stateMachineLayerComponent->m_events.push_back(fireEvent); +} \ No newline at end of file
diff --git a/src/scene.cpp b/src/scene.cpp index b5f6467..e568553 100644 --- a/src/scene.cpp +++ b/src/scene.cpp
@@ -3,16 +3,16 @@ using namespace rive; -Scene::Scene(ArtboardInstance* abi) : m_ArtboardInstance(abi) +Scene::Scene(ArtboardInstance* abi) : m_artboardInstance(abi) { - assert(m_ArtboardInstance->isInstance()); + assert(m_artboardInstance->isInstance()); } -float Scene::width() const { return m_ArtboardInstance->width(); } +float Scene::width() const { return m_artboardInstance->width(); } -float Scene::height() const { return m_ArtboardInstance->height(); } +float Scene::height() const { return m_artboardInstance->height(); } -void Scene::draw(Renderer* renderer) { m_ArtboardInstance->draw(renderer); } +void Scene::draw(Renderer* renderer) { m_artboardInstance->draw(renderer); } void Scene::pointerDown(Vec2D) {} void Scene::pointerMove(Vec2D) {}
diff --git a/test/assets/events_on_states.riv b/test/assets/events_on_states.riv new file mode 100644 index 0000000..ea95f1c --- /dev/null +++ b/test/assets/events_on_states.riv Binary files differ
diff --git a/test/state_machine_event_test.cpp b/test/state_machine_event_test.cpp index 9ddab3d..a72e47d 100644 --- a/test/state_machine_event_test.cpp +++ b/test/state_machine_event_test.cpp
@@ -16,6 +16,7 @@ #include "rive/animation/blend_state_transition.hpp" #include "rive/animation/listener_input_change.hpp" #include "rive/animation/listener_fire_event.hpp" +#include "rive/animation/entry_state.hpp" #include "rive/node.hpp" #include "catch.hpp" #include "rive_file_reader.hpp" @@ -164,3 +165,53 @@ stateMachineInstance->advance(0.0f); REQUIRE(stateMachineInstance->firedEventCount() == 0); } + +TEST_CASE("events load correctly on a state and transition", "[events]") +{ + auto file = ReadRiveFile("../../test/assets/events_on_states.riv"); + + auto artboard = file->artboard()->instance(); + REQUIRE(artboard != nullptr); + REQUIRE(artboard->stateMachineCount() == 1); + + auto stateMachineInstance = artboard->stateMachineAt(0); + REQUIRE(stateMachineInstance != nullptr); + + artboard->advance(0.0f); + stateMachineInstance->advance(0.0f); + + REQUIRE(stateMachineInstance->stateMachine()->layerCount() == 1); + auto layer = stateMachineInstance->stateMachine()->layer(0); + REQUIRE(layer->stateCount() == 5); + REQUIRE(layer->entryState()->transitionCount() == 1); + auto transition = layer->entryState()->transition(0); + + // No events on transition from entry. + REQUIRE(transition->events().size() == 0); + REQUIRE(transition->stateTo()->is<rive::AnimationState>()); + auto firstAnimationState = transition->stateTo()->as<rive::AnimationState>(); + REQUIRE(firstAnimationState->events().size() == 2); + REQUIRE(firstAnimationState->transitionCount() == 1); + transition = firstAnimationState->transition(0); + // Transition from first animation state to next one should have two events. + REQUIRE(transition->events().size() == 2); + + // First should've fired as we immediately went to Timeline 1. + REQUIRE(stateMachineInstance->firedEventCount() == 1); + REQUIRE(stateMachineInstance->firedEventAt(0)->name() == "First"); + + stateMachineInstance->advance(1.0f); + // Exits after 2 seconds so 1 second in no events should've fired yet + REQUIRE(stateMachineInstance->firedEventCount() == 0); + + stateMachineInstance->advance(1.0f); + // At 2 seconds 2 events should fire, one for exiting the state and for taking the transition. + REQUIRE(stateMachineInstance->firedEventCount() == 2); + REQUIRE(stateMachineInstance->firedEventAt(0)->name() == "Second"); + REQUIRE(stateMachineInstance->firedEventAt(1)->name() == "Third"); + + stateMachineInstance->advance(1.0f); + // Another second in the transition should complete + REQUIRE(stateMachineInstance->firedEventCount() == 1); + REQUIRE(stateMachineInstance->firedEventAt(0)->name() == "Fourth"); +}