Starting to process events in state machine instance.
diff --git a/include/rive/animation/event_bool_change.hpp b/include/rive/animation/event_bool_change.hpp index 79d3933..61f79a2 100644 --- a/include/rive/animation/event_bool_change.hpp +++ b/include/rive/animation/event_bool_change.hpp
@@ -1,10 +1,12 @@ #ifndef _RIVE_EVENT_BOOL_CHANGE_HPP_ #define _RIVE_EVENT_BOOL_CHANGE_HPP_ #include "rive/generated/animation/event_bool_change_base.hpp" -#include <stdio.h> + namespace rive { class EventBoolChange : public EventBoolChangeBase { public: + bool validateInputType(const StateMachineInput* input) const override; + void perform(StateMachineInstance* stateMachineInstance) const override; }; } // namespace rive
diff --git a/include/rive/animation/event_input_change.hpp b/include/rive/animation/event_input_change.hpp index ee3e724..b3ae0ce 100644 --- a/include/rive/animation/event_input_change.hpp +++ b/include/rive/animation/event_input_change.hpp
@@ -3,9 +3,13 @@ #include "rive/generated/animation/event_input_change_base.hpp" namespace rive { + class StateMachineInstance; + class StateMachineInput; class EventInputChange : public EventInputChangeBase { public: StatusCode import(ImportStack& importStack) override; + virtual void perform(StateMachineInstance* stateMachineInstance) const = 0; + virtual bool validateInputType(const StateMachineInput* input) const { return true; } }; } // namespace rive
diff --git a/include/rive/animation/event_number_change.hpp b/include/rive/animation/event_number_change.hpp index 27ac05f..6e123d0 100644 --- a/include/rive/animation/event_number_change.hpp +++ b/include/rive/animation/event_number_change.hpp
@@ -1,10 +1,12 @@ #ifndef _RIVE_EVENT_NUMBER_CHANGE_HPP_ #define _RIVE_EVENT_NUMBER_CHANGE_HPP_ #include "rive/generated/animation/event_number_change_base.hpp" -#include <stdio.h> + namespace rive { class EventNumberChange : public EventNumberChangeBase { public: + bool validateInputType(const StateMachineInput* input) const override; + void perform(StateMachineInstance* stateMachineInstance) const override; }; } // namespace rive
diff --git a/include/rive/animation/event_trigger_change.hpp b/include/rive/animation/event_trigger_change.hpp index b6245af..20200b5 100644 --- a/include/rive/animation/event_trigger_change.hpp +++ b/include/rive/animation/event_trigger_change.hpp
@@ -1,10 +1,12 @@ #ifndef _RIVE_EVENT_TRIGGER_CHANGE_HPP_ #define _RIVE_EVENT_TRIGGER_CHANGE_HPP_ #include "rive/generated/animation/event_trigger_change_base.hpp" -#include <stdio.h> + namespace rive { class EventTriggerChange : public EventTriggerChangeBase { public: + bool validateInputType(const StateMachineInput* input) const override; + void perform(StateMachineInstance* stateMachineInstance) const override; }; } // namespace rive
diff --git a/include/rive/animation/state_machine_event.hpp b/include/rive/animation/state_machine_event.hpp index b3d7606..ea6cb5a 100644 --- a/include/rive/animation/state_machine_event.hpp +++ b/include/rive/animation/state_machine_event.hpp
@@ -1,21 +1,30 @@ #ifndef _RIVE_STATE_MACHINE_EVENT_HPP_ #define _RIVE_STATE_MACHINE_EVENT_HPP_ #include "rive/generated/animation/state_machine_event_base.hpp" +#include "rive/event_type.hpp" namespace rive { + class Shape; class StateMachineEventImporter; class EventInputChange; + class StateMachineInstance; class StateMachineEvent : public StateMachineEventBase { friend class StateMachineEventImporter; private: + std::vector<uint32_t> m_HitShapesIds; std::vector<EventInputChange*> m_InputChanges; void addInputChange(EventInputChange* inputChange); public: + EventType eventType() const { return (EventType)eventTypeValue(); } size_t inputChangeCount() const { return m_InputChanges.size(); } const EventInputChange* inputChange(size_t index) const; StatusCode import(ImportStack& importStack) override; + StatusCode onAddedClean(CoreContext* context) override; + + const std::vector<uint32_t>& hitShapeIds() const { return m_HitShapesIds; } + void performChanges(StateMachineInstance* stateMachineInstance) const; }; } // namespace rive
diff --git a/include/rive/animation/state_machine_instance.hpp b/include/rive/animation/state_machine_instance.hpp index 2226ef9..b995265 100644 --- a/include/rive/animation/state_machine_instance.hpp +++ b/include/rive/animation/state_machine_instance.hpp
@@ -3,7 +3,9 @@ #include <string> #include <stddef.h> +#include <vector> #include "rive/animation/linear_animation_instance.hpp" +#include "rive/event_type.hpp" namespace rive { class StateMachine; @@ -13,8 +15,9 @@ class SMIBool; class SMINumber; class SMITrigger; - + class Shape; class StateMachineLayerInstance; + class HitShape; class StateMachineInstance { friend class SMIInput; @@ -31,6 +34,9 @@ void markNeedsAdvance(); + std::vector<HitShape*> m_HitShapes; + void processEvent(Vec2D position, EventType hitEvent); + public: StateMachineInstance(const StateMachine* machine, ArtboardInstance* instance); ~StateMachineInstance();
diff --git a/include/rive/artboard.hpp b/include/rive/artboard.hpp index ad19de3..297cedb 100644 --- a/include/rive/artboard.hpp +++ b/include/rive/artboard.hpp
@@ -67,6 +67,7 @@ StatusCode initialize(); Core* resolve(uint32_t id) const override; + uint32_t idOf(Core* object) const; // EXPERIMENTAL -- for internal testing only for now. // DO NOT RELY ON THIS as it may change/disappear in the future. @@ -88,7 +89,7 @@ // Returns true iff calling popMessage() will return true. bool hasMessages() const; - + // If there are any queued messages... // copies the first message into msg parameter // removes that message from the queue @@ -138,6 +139,16 @@ StateMachine* stateMachine(std::string name) const; StateMachine* stateMachine(size_t index) const; + /// Make an instance of a state machine given the name of the source state machine in this + /// artboard. The StateMachineInstance must be explictly deleted when no longer needed. + StateMachineInstance* stateMachineInstance(std::string name); + + /// Make an instance of a state machine given the index of the source state machine in this + /// artboard. The StateMachineInstance must be explictly deleted when no longer needed. + StateMachineInstance* stateMachineInstance(size_t index); + + size_t stateMachineCount() const { return m_StateMachines.size(); } + /// Make an instance of this artboard, must be explictly deleted when no /// longer needed. // Deprecated...
diff --git a/include/rive/event_type.hpp b/include/rive/event_type.hpp new file mode 100644 index 0000000..d50df00 --- /dev/null +++ b/include/rive/event_type.hpp
@@ -0,0 +1,11 @@ +#ifndef _RIVE_EVENT_TYPE_HPP_ +#define _RIVE_EVENT_TYPE_HPP_ +namespace rive { + enum class EventType : unsigned int { + enter = 0, + exit = 1, + down = 2, + up = 3, + }; +} +#endif \ No newline at end of file
diff --git a/include/rive/generated/animation/event_input_change_base.hpp b/include/rive/generated/animation/event_input_change_base.hpp index 61f30d1..7212efb 100644 --- a/include/rive/generated/animation/event_input_change_base.hpp +++ b/include/rive/generated/animation/event_input_change_base.hpp
@@ -26,11 +26,11 @@ static const uint16_t inputIdPropertyKey = 227; private: - int m_InputId = -1; + uint32_t m_InputId = -1; public: - inline int inputId() const { return m_InputId; } - void inputId(int value) { + inline uint32_t inputId() const { return m_InputId; } + void inputId(uint32_t value) { if (m_InputId == value) { return; }
diff --git a/include/rive/generated/animation/state_machine_event_base.hpp b/include/rive/generated/animation/state_machine_event_base.hpp index 2aa9f01..a67e39c 100644 --- a/include/rive/generated/animation/state_machine_event_base.hpp +++ b/include/rive/generated/animation/state_machine_event_base.hpp
@@ -28,12 +28,12 @@ static const uint16_t eventTypeValuePropertyKey = 225; private: - int m_TargetId = 0; - int m_EventTypeValue = 0; + uint32_t m_TargetId = 0; + uint32_t m_EventTypeValue = 0; public: - inline int targetId() const { return m_TargetId; } - void targetId(int value) { + inline uint32_t targetId() const { return m_TargetId; } + void targetId(uint32_t value) { if (m_TargetId == value) { return; } @@ -41,8 +41,8 @@ targetIdChanged(); } - inline int eventTypeValue() const { return m_EventTypeValue; } - void eventTypeValue(int value) { + inline uint32_t eventTypeValue() const { return m_EventTypeValue; } + void eventTypeValue(uint32_t value) { if (m_EventTypeValue == value) { return; }
diff --git a/src/animation/event_bool_change.cpp b/src/animation/event_bool_change.cpp new file mode 100644 index 0000000..946f15a --- /dev/null +++ b/src/animation/event_bool_change.cpp
@@ -0,0 +1,22 @@ +#include "rive/animation/event_bool_change.hpp" +#include "rive/animation/state_machine_instance.hpp" +#include "rive/animation/state_machine_bool.hpp" +#include "rive/animation/state_machine_input_instance.hpp" +using namespace rive; + +bool EventBoolChange::validateInputType(const StateMachineInput* input) const { + // A null input is valid as the StateMachine can attempt to limp along if we + // introduce new input types that old conditions are expected to handle in + // newer runtimes. The older runtimes will just evaluate them to true. + return input == nullptr || input->is<StateMachineBool>(); +} + +void EventBoolChange::perform(StateMachineInstance* stateMachineInstance) const { + auto inputInstance = stateMachineInstance->input(inputId()); + if (inputInstance == nullptr) { + return; + } + // If it's not null, it must be our correct type (why we validate at load time). + auto boolInput = reinterpret_cast<SMIBool*>(inputInstance); + boolInput->value(value()); +} \ No newline at end of file
diff --git a/src/animation/event_input_change.cpp b/src/animation/event_input_change.cpp index d319855..7914ae3 100644 --- a/src/animation/event_input_change.cpp +++ b/src/animation/event_input_change.cpp
@@ -1,17 +1,27 @@ #include "rive/animation/state_machine_event.hpp" #include "rive/importers/import_stack.hpp" #include "rive/importers/state_machine_event_importer.hpp" +#include "rive/importers/state_machine_importer.hpp" #include "rive/animation/event_input_change.hpp" -#include "rive/generated/animation/state_machine_base.hpp" +#include "rive/animation/state_machine.hpp" using namespace rive; StatusCode EventInputChange::import(ImportStack& importStack) { + auto stateMachineImporter = importStack.latest<StateMachineImporter>(StateMachine::typeKey); + if (stateMachineImporter == nullptr) { + return StatusCode::MissingObject; + } + auto stateMachineEventImporter = importStack.latest<StateMachineEventImporter>(StateMachineEventBase::typeKey); if (stateMachineEventImporter == nullptr) { return StatusCode::MissingObject; } + + if (!validateInputType(stateMachineImporter->stateMachine()->input((size_t)inputId()))) { + return StatusCode::InvalidObject; + } stateMachineEventImporter->addInputChange(this); return Super::import(importStack); }
diff --git a/src/animation/event_number_change.cpp b/src/animation/event_number_change.cpp new file mode 100644 index 0000000..a97d306 --- /dev/null +++ b/src/animation/event_number_change.cpp
@@ -0,0 +1,23 @@ +#include "rive/animation/event_number_change.hpp" +#include "rive/animation/state_machine_instance.hpp" +#include "rive/animation/state_machine_number.hpp" +#include "rive/animation/state_machine_input_instance.hpp" + +using namespace rive; + +bool EventNumberChange::validateInputType(const StateMachineInput* input) const { + // A null input is valid as the StateMachine can attempt to limp along if we + // introduce new input types that old conditions are expected to handle in + // newer runtimes. The older runtimes will just evaluate them to true. + return input == nullptr || input->is<StateMachineNumber>(); +} + +void EventNumberChange::perform(StateMachineInstance* stateMachineInstance) const { + auto inputInstance = stateMachineInstance->input(inputId()); + if (inputInstance == nullptr) { + return; + } + // If it's not null, it must be our correct type (why we validate at load time). + auto numberInput = reinterpret_cast<SMINumber*>(inputInstance); + numberInput->value(value()); +}
diff --git a/src/animation/event_trigger_change.cpp b/src/animation/event_trigger_change.cpp new file mode 100644 index 0000000..e2b8d9d --- /dev/null +++ b/src/animation/event_trigger_change.cpp
@@ -0,0 +1,23 @@ +#include "rive/animation/event_trigger_change.hpp" +#include "rive/animation/state_machine_instance.hpp" +#include "rive/animation/state_machine_trigger.hpp" +#include "rive/animation/state_machine_input_instance.hpp" + +using namespace rive; + +bool EventTriggerChange::validateInputType(const StateMachineInput* input) const { + // A null input is valid as the StateMachine can attempt to limp along if we + // introduce new input types that old conditions are expected to handle in + // newer runtimes. The older runtimes will just evaluate them to true. + return input == nullptr || input->is<StateMachineTrigger>(); +} + +void EventTriggerChange::perform(StateMachineInstance* stateMachineInstance) const { + auto inputInstance = stateMachineInstance->input(inputId()); + if (inputInstance == nullptr) { + return; + } + // If it's not null, it must be our correct type (why we validate at load time). + auto triggerInput = reinterpret_cast<SMITrigger*>(inputInstance); + triggerInput->fire(); +} \ No newline at end of file
diff --git a/src/animation/state_machine_event.cpp b/src/animation/state_machine_event.cpp index d6791b3..fd367ed 100644 --- a/src/animation/state_machine_event.cpp +++ b/src/animation/state_machine_event.cpp
@@ -2,6 +2,10 @@ #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/event_input_change.hpp" using namespace rive; @@ -23,4 +27,34 @@ return m_InputChanges[index]; } return nullptr; +} + +StatusCode StateMachineEvent::onAddedClean(CoreContext* context) { + auto artboard = static_cast<Artboard*>(context); + for (auto core : artboard->objects()) { + if (core == nullptr) { + continue; + } + auto target = artboard->resolve(targetId()); + + // Iterate artboard to find Shapes that are parented to the target + if (core->is<Shape>()) { + auto shape = core->as<Shape>(); + for (ContainerComponent* component = shape; component != nullptr; + component = component->parent()) { + if (component == target) { + m_HitShapesIds.push_back(artboard->idOf(shape)); + break; + } + } + } + } + + return Super::onAddedClean(context); +} + +void StateMachineEvent::performChanges(StateMachineInstance* stateMachineInstance) const { + for (auto inputChange : m_InputChanges) { + inputChange->perform(stateMachineInstance); + } } \ No newline at end of file
diff --git a/src/animation/state_machine_instance.cpp b/src/animation/state_machine_instance.cpp index b5bfa84..1129c4f 100644 --- a/src/animation/state_machine_instance.cpp +++ b/src/animation/state_machine_instance.cpp
@@ -13,6 +13,10 @@ #include "rive/animation/animation_state.hpp" #include "rive/animation/state_instance.hpp" #include "rive/animation/animation_state_instance.hpp" +#include "rive/animation/state_machine_event.hpp" +#include "rive/shapes/shape.hpp" +#include "rive/math/aabb.hpp" +#include <unordered_map> using namespace rive; namespace rive { @@ -69,7 +73,7 @@ } } - bool advance(/*Artboard* artboard, */float seconds, SMIInput** inputs, size_t inputCount) { + bool advance(/*Artboard* artboard, */ float seconds, SMIInput** inputs, size_t inputCount) { m_StateChangedOnAdvance = false; if (m_CurrentState != nullptr) { @@ -124,7 +128,8 @@ if ((m_CurrentState == nullptr ? nullptr : m_CurrentState->state()) == stateTo) { return false; } - m_CurrentState = stateTo == nullptr ? nullptr : stateTo->makeInstance(m_ArtboardInstance).release(); + m_CurrentState = + stateTo == nullptr ? nullptr : stateTo->makeInstance(m_ArtboardInstance).release(); return true; } @@ -213,12 +218,62 @@ return static_cast<AnimationStateInstance*>(m_CurrentState)->animationInstance(); } }; + + /// Representation of a Shape from the Artboard Instance and all the events it + /// triggers. Allows tracking hover and performing hit detection only once on + /// shapes that trigger multiple events. + class HitShape { + private: + Shape* m_Shape; + + public: + Shape* shape() const { return m_Shape; } + HitShape(Shape* shape) : m_Shape(shape) {} + bool isHovered = false; + std::vector<const StateMachineEvent*> events; + }; } // namespace rive -StateMachineInstance::StateMachineInstance(const StateMachine* machine, ArtboardInstance* instance) - : m_Machine(machine) - , m_ArtboardInstance(instance) -{ +void StateMachineInstance::processEvent(Vec2D position, EventType hitEvent) { + const int hitRadius = 2; + auto hitArea = AABB(position.x() - hitRadius, + position.y() - hitRadius, + position.x() + hitRadius, + position.y() + hitRadius) + .round(); + for (auto hitShape : m_HitShapes) { + + // TODO: quick reject. + + // TODO: hit test + bool isOver = false; + + bool hoverChange = hitShape->isHovered != isOver; + hitShape->isHovered = isOver; + // iterate all events associated with this hit shape + for (auto event : hitShape->events) { + // Always update hover states regardless of which specific event type + // we're trying to trigger. + if (hoverChange) { + if (isOver && event->eventType() == EventType::enter) { + event->performChanges(this); + markNeedsAdvance(); + } else if (!isOver && event->eventType() == EventType::exit) { + event->performChanges(this); + markNeedsAdvance(); + } + } + if (isOver && hitEvent == event->eventType()) { + event->performChanges(this); + markNeedsAdvance(); + } + } + } +} + +StateMachineInstance::StateMachineInstance(const StateMachine* machine, + ArtboardInstance* instance) : + m_Machine(machine), m_ArtboardInstance(instance) { assert(instance->isInstance()); m_InputCount = machine->inputCount(); m_InputInstances = new SMIInput*[m_InputCount]; @@ -250,9 +305,40 @@ for (size_t i = 0; i < m_LayerCount; i++) { m_Layers[i].init(machine->layer(i), m_ArtboardInstance); } + + // Initialize events. Store a lookup table of shape id to hit shape + // representation (an object that stores all the events triggered by the + // shape producing an event). + std::unordered_map<uint32_t, HitShape*> hitShapeLookup; + for (std::size_t i = 0; i < machine->eventCount(); i++) { + auto event = machine->event(i); + + // Iterate actual leaf hittable shapes tied to this event and resolve + // corresponding ones in the artboard instance. + for (auto id : event->hitShapeIds()) { + HitShape* hitShape; + auto itr = hitShapeLookup.find(id); + if (itr == hitShapeLookup.end()) { + auto shape = artboard->resolve(id); + if (shape != nullptr && shape->is<Shape>()) { + hitShapeLookup[id] = hitShape = new HitShape(shape->as<Shape>()); + m_HitShapes.push_back(hitShape); + } else { + // No object or not a shape... + continue; + } + } else { + hitShape = itr->second; + } + hitShape->events.push_back(event); + } + } } StateMachineInstance::~StateMachineInstance() { + for (auto hitShape : m_HitShapes) { + delete hitShape; + } // TODO: can this and others be rewritten as for (auto inst : m_InputInstances) ? for (size_t i = 0; i < m_InputCount; i++) { delete m_InputInstances[i]; @@ -279,9 +365,7 @@ 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_InputCount) {
diff --git a/src/artboard.cpp b/src/artboard.cpp index 940b835..b52587f 100644 --- a/src/artboard.cpp +++ b/src/artboard.cpp
@@ -14,6 +14,7 @@ #include "rive/importers/import_stack.hpp" #include "rive/importers/backboard_importer.hpp" #include "rive/nested_artboard.hpp" +#include "rive/animation/state_machine_instance.hpp" #include <stack> #include <unordered_map> @@ -287,6 +288,8 @@ return m_Objects[id]; } +uint32_t Artboard::idOf(Core* object) const { return 0; } + void Artboard::onComponentDirty(Component* component) { m_Dirt |= ComponentDirt::Components; @@ -506,6 +509,22 @@ return m_StateMachines[index]; } +StateMachineInstance* Artboard::stateMachineInstance(std::string name) { + StateMachine* machine = stateMachine(name); + if (machine != nullptr) { + return new StateMachineInstance(machine, this); + } + return nullptr; +} + +StateMachineInstance* Artboard::stateMachineInstance(size_t index) { + StateMachine* machine = stateMachine(index); + if (machine != nullptr) { + return new StateMachineInstance(machine, this); + } + return nullptr; +} + std::unique_ptr<ArtboardInstance> Artboard::instance() const { std::unique_ptr<ArtboardInstance> artboardClone(new ArtboardInstance); artboardClone->copy(*this); @@ -577,7 +596,8 @@ assert(gButtonIsDown); gButtonIsDown = false; break; - default: break; + default: + break; } #if 0 @@ -603,13 +623,9 @@ #endif } -void Artboard::testing_only_enque_message(const Message& msg) { - m_MessageQueue.push(msg); -} +void Artboard::testing_only_enque_message(const Message& msg) { m_MessageQueue.push(msg); } -bool Artboard::hasMessages() const { - return !m_MessageQueue.empty(); -} +bool Artboard::hasMessages() const { return !m_MessageQueue.empty(); } bool Artboard::nextMessage(Message* msg) { if (m_MessageQueue.empty()) { @@ -626,26 +642,22 @@ #include "rive/animation/linear_animation_instance.hpp" #include "rive/animation/state_machine_instance.hpp" -std::unique_ptr<LinearAnimationInstance> -ArtboardInstance::animationAt(size_t index) { +std::unique_ptr<LinearAnimationInstance> ArtboardInstance::animationAt(size_t index) { auto la = this->animation(index); return la ? std::make_unique<LinearAnimationInstance>(la, this) : nullptr; } -std::unique_ptr<LinearAnimationInstance> -ArtboardInstance::animationNamed(std::string name) { +std::unique_ptr<LinearAnimationInstance> ArtboardInstance::animationNamed(std::string name) { auto la = this->animation(name); return la ? std::make_unique<LinearAnimationInstance>(la, this) : nullptr; } -std::unique_ptr<StateMachineInstance> -ArtboardInstance::stateMachineAt(size_t index) { +std::unique_ptr<StateMachineInstance> ArtboardInstance::stateMachineAt(size_t index) { auto sm = this->stateMachine(index); return sm ? std::make_unique<StateMachineInstance>(sm, this) : nullptr; } -std::unique_ptr<StateMachineInstance> -ArtboardInstance::stateMachineNamed(std::string name) { +std::unique_ptr<StateMachineInstance> ArtboardInstance::stateMachineNamed(std::string name) { auto sm = this->stateMachine(name); return sm ? std::make_unique<StateMachineInstance>(sm, this) : nullptr; }
diff --git a/test/state_machine_test.cpp b/test/state_machine_test.cpp index b4b0f97..3424c5f 100644 --- a/test/state_machine_test.cpp +++ b/test/state_machine_test.cpp
@@ -73,6 +73,7 @@ auto abi = artboard->instance(); rive::StateMachineInstance smi(artboard->stateMachine("Button"), abi.get()); + REQUIRE(smi.getBool("Hover")->name() == "Hover"); REQUIRE(smi.getBool("Press")->name() == "Press"); REQUIRE(smi.getBool("Hover") != nullptr);