Playable as a high-level api
diff --git a/include/rive/playable.hpp b/include/rive/playable.hpp
new file mode 100644
index 0000000..2b834a7
--- /dev/null
+++ b/include/rive/playable.hpp
@@ -0,0 +1,70 @@
+#ifndef _RIVE_PLAYABLE_HPP_
+#define _RIVE_PLAYABLE_HPP_
+
+#include "rive/math/aabb.hpp"
+#include "rive/span.hpp"
+
+namespace rive {
+ class Factory;
+ class FileAssetResolver;
+ class Renderer;
+
+ class ArtboardInstance;
+ class File;
+ class LinearAnimationInstance;
+ class StateMachineInstance;
+
+ class SMIInput;
+ class SMIBool;
+ class SMINumber;
+ class SMITrigger;
+
+ class Playable {
+ private:
+ std::unique_ptr<File> m_File;
+ std::unique_ptr<ArtboardInstance> m_ABI;
+ std::unique_ptr<LinearAnimationInstance> m_LAI;
+ std::unique_ptr<StateMachineInstance> m_SMI;
+
+ Playable();
+
+ public:
+ static std::unique_ptr<Playable> import(Span<const uint8_t>,
+ Factory*,
+ FileAssetResolver* = nullptr);
+
+ static std::unique_ptr<Playable>
+ animationAt(File*, size_t artboardIndex, size_t animationIndex);
+
+ static std::unique_ptr<Playable>
+ stateMachineAt(File*, size_t artboardIndex, size_t machineIndex);
+
+ ~Playable();
+
+ AABB bounds() const;
+
+ // returns -1 for continuous
+ float durationSeconds() const;
+
+ // returns true if draw() should be called
+ bool advance(float elapsedSeconds);
+
+ void draw(Renderer*);
+
+ enum class PointerState {
+ kDown, // pointer changed from 'up' to 'down'
+ kUp, // pointer changed from 'down' to 'up'
+ kMoved, // pointer position changed
+ };
+ void pointerEvent(Vec2D, PointerState);
+
+ size_t inputCount() const;
+ SMIInput* inputAt(size_t index) const;
+ SMIBool* boolNamed(const std::string&) const;
+ SMINumber* numberNamed(const std::string&) const;
+ SMITrigger* triggerNamed(const std::string&) const;
+ };
+
+} // namespace rive
+
+#endif
diff --git a/src/playable.cpp b/src/playable.cpp
new file mode 100644
index 0000000..7c2e072
--- /dev/null
+++ b/src/playable.cpp
@@ -0,0 +1,126 @@
+#include "rive/artboard.hpp"
+#include "rive/file.hpp"
+#include "rive/file_asset_resolver.hpp"
+#include "rive/playable.hpp"
+#include "rive/animation/linear_animation_instance.hpp"
+#include "rive/animation/state_machine_instance.hpp"
+
+using namespace rive;
+
+Playable::Playable() {}
+
+Playable::~Playable() {}
+
+std::unique_ptr<Playable> Playable::animationAt(File* file,
+ size_t artboardIndex,
+ size_t animationIndex) {
+ auto abi = file->artboardAt(artboardIndex);
+ if (abi) {
+ auto lai = abi->animationAt(animationIndex);
+ if (lai) {
+ std::unique_ptr<Playable> play(new Playable);
+ play->m_ABI = std::move(abi);
+ play->m_LAI = std::move(lai);
+ return play;
+ }
+ }
+ return nullptr;
+}
+
+std::unique_ptr<Playable> Playable::stateMachineAt(File* file,
+ size_t artboardIndex,
+ size_t machineIndex) {
+ auto abi = file->artboardAt(artboardIndex);
+ if (abi) {
+ auto smi = abi->stateMachineAt(machineIndex);
+ if (smi) {
+ std::unique_ptr<Playable> play(new Playable);
+ play->m_ABI = std::move(abi);
+ play->m_SMI = std::move(smi);
+ return play;
+ }
+ }
+ return nullptr;
+}
+
+std::unique_ptr<Playable> Playable::import(Span<const uint8_t> bytes,
+ Factory* factory,
+ FileAssetResolver* resolver)
+{
+ auto file = File::import(bytes, factory, nullptr, resolver);
+ if (!file) {
+ return nullptr;
+ }
+
+ auto play = Playable::stateMachineAt(file.get(), 0, 0);
+ if (!play) {
+ play = Playable::animationAt(file.get(), 0, 0);
+ }
+ if (play) {
+ play->m_File = std::move(file);
+ }
+ return play;
+}
+
+AABB Playable::bounds() const {
+ return m_ABI->bounds();
+}
+
+float Playable::durationSeconds() const {
+ return m_SMI ? -1 : m_LAI->durationSeconds();
+}
+
+// returns true if draw() should be called
+bool Playable::advance(float elapsedSeconds) {
+ bool needsRedraw;
+ if (m_SMI) {
+ needsRedraw = m_SMI->advance(elapsedSeconds);
+ } else {
+ needsRedraw = m_LAI->advance(elapsedSeconds);
+ m_LAI->apply();
+ }
+ return needsRedraw;
+}
+
+void Playable::draw(Renderer* renderer) {
+ m_ABI->draw(renderer);
+}
+
+void Playable::pointerEvent(Vec2D pos, PointerState state) {
+ if (m_SMI) {
+ switch (state) {
+ case PointerState::kDown:
+ m_SMI->pointerDown(pos);
+ break;
+ case PointerState::kMoved:
+ m_SMI->pointerMove(pos);
+ break;
+ case PointerState::kUp:
+ m_SMI->pointerUp(pos);
+ break;
+ }
+ }
+ // else the animation just ignores the pointer events
+}
+
+// StateMachine Inputs
+
+size_t Playable::inputCount() const {
+ return m_SMI ? m_SMI->inputCount() : 0;
+}
+
+SMIInput* Playable::inputAt(size_t index) const {
+ return m_SMI ? m_SMI->input(index) : nullptr;
+}
+
+SMIBool* Playable::boolNamed(const std::string& name) const {
+ return m_SMI ? m_SMI->getBool(name) : nullptr;
+}
+
+SMINumber* Playable::numberNamed(const std::string& name) const {
+ return m_SMI ? m_SMI->getNumber(name) : nullptr;
+}
+
+SMITrigger* Playable::triggerNamed(const std::string& name) const {
+ return m_SMI ? m_SMI->getTrigger(name) : nullptr;
+}