Joysticks & Slider!

Diffs=
7acff50d4 Joysticks & Slider! (#5249)
diff --git a/.rive_head b/.rive_head
index fcc26cc..5fe4eb6 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-e65175b88afb40b101b54617a29b1bedc6f63d99
+7acff50d4632d9e88b1474d6f23b3b1c8d7f7334
diff --git a/dev/defs/joystick.json b/dev/defs/joystick.json
new file mode 100644
index 0000000..4ef5e38
--- /dev/null
+++ b/dev/defs/joystick.json
@@ -0,0 +1,108 @@
+{
+  "name": "Joystick",
+  "key": {
+    "int": 148,
+    "string": "joystick"
+  },
+  "extends": "component.json",
+  "properties": {
+    "x": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "group": "position",
+      "key": {
+        "int": 299,
+        "string": "x"
+      }
+    },
+    "y": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "group": "position",
+      "key": {
+        "int": 300,
+        "string": "y"
+      }
+    },
+    "posX": {
+      "type": "double",
+      "initialValue": "0",
+      "key": {
+        "int": 303,
+        "string": "posx"
+      },
+      "runtime": false
+    },
+    "posY": {
+      "type": "double",
+      "initialValue": "0",
+      "key": {
+        "int": 304,
+        "string": "posy"
+      },
+      "runtime": false
+    },
+    "originX": {
+      "type": "double",
+      "initialValue": "0.5",
+      "key": {
+        "int": 307,
+        "string": "originx"
+      },
+      "description": "Origin x in normalized coordinates (0.5 = center, 0 = left, 1 = right).",
+      "runtime": false
+    },
+    "originY": {
+      "type": "double",
+      "initialValue": "0.5",
+      "key": {
+        "int": 308,
+        "string": "originy"
+      },
+      "description": "Origin y in normalized coordinates (0.5 = center, 0 = top, 1 = bottom).",
+      "runtime": false
+    },
+    "width": {
+      "type": "double",
+      "initialValue": "100",
+      "key": {
+        "int": 305,
+        "string": "width"
+      },
+      "runtime": false
+    },
+    "height": {
+      "type": "double",
+      "initialValue": "100",
+      "key": {
+        "int": 306,
+        "string": "height"
+      },
+      "runtime": false
+    },
+    "xId": {
+      "type": "Id",
+      "typeRuntime": "uint",
+      "initialValue": "Core.missingId",
+      "initialValueRuntime": "-1",
+      "key": {
+        "int": 301,
+        "string": "x_id"
+      },
+      "description": "Identifier used to track the animation used for the x axis of the joystick."
+    },
+    "yId": {
+      "type": "Id",
+      "typeRuntime": "uint",
+      "initialValue": "Core.missingId",
+      "initialValueRuntime": "-1",
+      "key": {
+        "int": 302,
+        "string": "y_id"
+      },
+      "description": "Identifier used to track the animation used for the y axis of the joystick."
+    }
+  }
+}
\ No newline at end of file
diff --git a/include/rive/artboard.hpp b/include/rive/artboard.hpp
index dd1bf12..39e9034 100644
--- a/include/rive/artboard.hpp
+++ b/include/rive/artboard.hpp
@@ -26,6 +26,7 @@
 class LinearAnimationInstance;
 class Scene;
 class StateMachineInstance;
+class Joystick;
 
 class Artboard : public ArtboardBase, public CoreContext, public ShapePaintContainer
 {
@@ -41,6 +42,7 @@
     std::vector<Drawable*> m_Drawables;
     std::vector<DrawTarget*> m_DrawTargets;
     std::vector<NestedArtboard*> m_NestedArtboards;
+    std::vector<Joystick*> m_Joysticks;
 
     unsigned int m_DirtDepth = 0;
     std::unique_ptr<RenderPath> m_BackgroundPath;
@@ -62,7 +64,6 @@
     void addObject(Core* object);
     void addAnimation(LinearAnimation* object);
     void addStateMachine(StateMachine* object);
-    void addNestedArtboard(NestedArtboard* object);
 
 public:
     Artboard() {}
diff --git a/include/rive/generated/core_registry.hpp b/include/rive/generated/core_registry.hpp
index 4688e77..3dd4042 100644
--- a/include/rive/generated/core_registry.hpp
+++ b/include/rive/generated/core_registry.hpp
@@ -87,6 +87,7 @@
 #include "rive/draw_rules.hpp"
 #include "rive/draw_target.hpp"
 #include "rive/drawable.hpp"
+#include "rive/joystick.hpp"
 #include "rive/nested_animation.hpp"
 #include "rive/nested_artboard.hpp"
 #include "rive/node.hpp"
@@ -285,6 +286,8 @@
                 return new DrawRules();
             case ArtboardBase::typeKey:
                 return new Artboard();
+            case JoystickBase::typeKey:
+                return new Joystick();
             case BackboardBase::typeKey:
                 return new Backboard();
             case WeightBase::typeKey:
@@ -508,6 +511,12 @@
             case ArtboardBase::defaultStateMachineIdPropertyKey:
                 object->as<ArtboardBase>()->defaultStateMachineId(value);
                 break;
+            case JoystickBase::xIdPropertyKey:
+                object->as<JoystickBase>()->xId(value);
+                break;
+            case JoystickBase::yIdPropertyKey:
+                object->as<JoystickBase>()->yId(value);
+                break;
             case WeightBase::valuesPropertyKey:
                 object->as<WeightBase>()->values(value);
                 break;
@@ -766,6 +775,12 @@
             case ArtboardBase::originYPropertyKey:
                 object->as<ArtboardBase>()->originY(value);
                 break;
+            case JoystickBase::xPropertyKey:
+                object->as<JoystickBase>()->x(value);
+                break;
+            case JoystickBase::yPropertyKey:
+                object->as<JoystickBase>()->y(value);
+                break;
             case BoneBase::lengthPropertyKey:
                 object->as<BoneBase>()->length(value);
                 break;
@@ -1040,6 +1055,10 @@
                 return object->as<DrawRulesBase>()->drawTargetId();
             case ArtboardBase::defaultStateMachineIdPropertyKey:
                 return object->as<ArtboardBase>()->defaultStateMachineId();
+            case JoystickBase::xIdPropertyKey:
+                return object->as<JoystickBase>()->xId();
+            case JoystickBase::yIdPropertyKey:
+                return object->as<JoystickBase>()->yId();
             case WeightBase::valuesPropertyKey:
                 return object->as<WeightBase>()->values();
             case WeightBase::indicesPropertyKey:
@@ -1215,6 +1234,10 @@
                 return object->as<ArtboardBase>()->originX();
             case ArtboardBase::originYPropertyKey:
                 return object->as<ArtboardBase>()->originY();
+            case JoystickBase::xPropertyKey:
+                return object->as<JoystickBase>()->x();
+            case JoystickBase::yPropertyKey:
+                return object->as<JoystickBase>()->y();
             case BoneBase::lengthPropertyKey:
                 return object->as<BoneBase>()->length();
             case RootBoneBase::xPropertyKey:
@@ -1383,6 +1406,8 @@
             case ImageBase::assetIdPropertyKey:
             case DrawRulesBase::drawTargetIdPropertyKey:
             case ArtboardBase::defaultStateMachineIdPropertyKey:
+            case JoystickBase::xIdPropertyKey:
+            case JoystickBase::yIdPropertyKey:
             case WeightBase::valuesPropertyKey:
             case WeightBase::indicesPropertyKey:
             case TendonBase::boneIdPropertyKey:
@@ -1468,6 +1493,8 @@
             case ArtboardBase::yPropertyKey:
             case ArtboardBase::originXPropertyKey:
             case ArtboardBase::originYPropertyKey:
+            case JoystickBase::xPropertyKey:
+            case JoystickBase::yPropertyKey:
             case BoneBase::lengthPropertyKey:
             case RootBoneBase::xPropertyKey:
             case RootBoneBase::yPropertyKey:
diff --git a/include/rive/generated/joystick_base.hpp b/include/rive/generated/joystick_base.hpp
new file mode 100644
index 0000000..401188e
--- /dev/null
+++ b/include/rive/generated/joystick_base.hpp
@@ -0,0 +1,126 @@
+#ifndef _RIVE_JOYSTICK_BASE_HPP_
+#define _RIVE_JOYSTICK_BASE_HPP_
+#include "rive/component.hpp"
+#include "rive/core/field_types/core_double_type.hpp"
+#include "rive/core/field_types/core_uint_type.hpp"
+namespace rive
+{
+class JoystickBase : public Component
+{
+protected:
+    typedef Component Super;
+
+public:
+    static const uint16_t typeKey = 148;
+
+    /// Helper to quickly determine if a core object extends another without RTTI
+    /// at runtime.
+    bool isTypeOf(uint16_t typeKey) const override
+    {
+        switch (typeKey)
+        {
+            case JoystickBase::typeKey:
+            case ComponentBase::typeKey:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    uint16_t coreType() const override { return typeKey; }
+
+    static const uint16_t xPropertyKey = 299;
+    static const uint16_t yPropertyKey = 300;
+    static const uint16_t xIdPropertyKey = 301;
+    static const uint16_t yIdPropertyKey = 302;
+
+private:
+    float m_X = 0.0f;
+    float m_Y = 0.0f;
+    uint32_t m_XId = -1;
+    uint32_t m_YId = -1;
+
+public:
+    inline float x() const { return m_X; }
+    void x(float value)
+    {
+        if (m_X == value)
+        {
+            return;
+        }
+        m_X = value;
+        xChanged();
+    }
+
+    inline float y() const { return m_Y; }
+    void y(float value)
+    {
+        if (m_Y == value)
+        {
+            return;
+        }
+        m_Y = value;
+        yChanged();
+    }
+
+    inline uint32_t xId() const { return m_XId; }
+    void xId(uint32_t value)
+    {
+        if (m_XId == value)
+        {
+            return;
+        }
+        m_XId = value;
+        xIdChanged();
+    }
+
+    inline uint32_t yId() const { return m_YId; }
+    void yId(uint32_t value)
+    {
+        if (m_YId == value)
+        {
+            return;
+        }
+        m_YId = value;
+        yIdChanged();
+    }
+
+    Core* clone() const override;
+    void copy(const JoystickBase& object)
+    {
+        m_X = object.m_X;
+        m_Y = object.m_Y;
+        m_XId = object.m_XId;
+        m_YId = object.m_YId;
+        Component::copy(object);
+    }
+
+    bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
+    {
+        switch (propertyKey)
+        {
+            case xPropertyKey:
+                m_X = CoreDoubleType::deserialize(reader);
+                return true;
+            case yPropertyKey:
+                m_Y = CoreDoubleType::deserialize(reader);
+                return true;
+            case xIdPropertyKey:
+                m_XId = CoreUintType::deserialize(reader);
+                return true;
+            case yIdPropertyKey:
+                m_YId = CoreUintType::deserialize(reader);
+                return true;
+        }
+        return Component::deserialize(propertyKey, reader);
+    }
+
+protected:
+    virtual void xChanged() {}
+    virtual void yChanged() {}
+    virtual void xIdChanged() {}
+    virtual void yIdChanged() {}
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/joystick.hpp b/include/rive/joystick.hpp
new file mode 100644
index 0000000..d076a14
--- /dev/null
+++ b/include/rive/joystick.hpp
@@ -0,0 +1,21 @@
+#ifndef _RIVE_JOYSTICK_HPP_
+#define _RIVE_JOYSTICK_HPP_
+#include "rive/generated/joystick_base.hpp"
+#include <stdio.h>
+namespace rive
+{
+class Artboard;
+class LinearAnimation;
+class Joystick : public JoystickBase
+{
+public:
+    void apply(Artboard* artboard) const;
+    StatusCode onAddedClean(CoreContext* context) override;
+
+private:
+    LinearAnimation* m_xAnimation = nullptr;
+    LinearAnimation* m_yAnimation = nullptr;
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/src/artboard.cpp b/src/artboard.cpp
index fa08fd1..ed104ee 100644
--- a/src/artboard.cpp
+++ b/src/artboard.cpp
@@ -15,6 +15,7 @@
 #include "rive/importers/import_stack.hpp"
 #include "rive/importers/backboard_importer.hpp"
 #include "rive/nested_artboard.hpp"
+#include "rive/joystick.hpp"
 #include "rive/animation/state_machine_instance.hpp"
 #include "rive/shapes/shape.hpp"
 
@@ -147,6 +148,10 @@
             case NestedArtboardBase::typeKey:
                 m_NestedArtboards.push_back(object->as<NestedArtboard>());
                 break;
+
+            case JoystickBase::typeKey:
+                m_Joysticks.push_back(object->as<Joystick>());
+                break;
         }
     }
 
@@ -356,11 +361,6 @@
 
 void Artboard::addStateMachine(StateMachine* object) { m_StateMachines.push_back(object); }
 
-void Artboard::addNestedArtboard(NestedArtboard* artboard)
-{
-    m_NestedArtboards.push_back(artboard);
-}
-
 Core* Artboard::resolve(uint32_t id) const
 {
     if (id >= static_cast<int>(m_Objects.size()))
@@ -465,6 +465,11 @@
 
 bool Artboard::advance(double elapsedSeconds)
 {
+    for (auto joystick : m_Joysticks)
+    {
+        joystick->apply(this);
+    }
+
     bool didUpdate = updateComponents();
     for (auto nestedArtboard : m_NestedArtboards)
     {
diff --git a/src/generated/joystick_base.cpp b/src/generated/joystick_base.cpp
new file mode 100644
index 0000000..19e3602
--- /dev/null
+++ b/src/generated/joystick_base.cpp
@@ -0,0 +1,11 @@
+#include "rive/generated/joystick_base.hpp"
+#include "rive/joystick.hpp"
+
+using namespace rive;
+
+Core* JoystickBase::clone() const
+{
+    auto cloned = new Joystick();
+    cloned->copy(*this);
+    return cloned;
+}
diff --git a/src/joystick.cpp b/src/joystick.cpp
new file mode 100644
index 0000000..5a61281
--- /dev/null
+++ b/src/joystick.cpp
@@ -0,0 +1,23 @@
+#include "rive/joystick.hpp"
+#include "rive/artboard.hpp"
+
+using namespace rive;
+
+StatusCode Joystick::onAddedClean(CoreContext* context)
+{
+    m_xAnimation = artboard()->animation(xId());
+    m_yAnimation = artboard()->animation(yId());
+    return StatusCode::Ok;
+}
+
+void Joystick::apply(Artboard* artboard) const
+{
+    if (m_xAnimation != nullptr)
+    {
+        m_xAnimation->apply(artboard, (x() + 1.0f) / 2.0f * m_xAnimation->durationSeconds());
+    }
+    if (m_yAnimation != nullptr)
+    {
+        m_yAnimation->apply(artboard, (y() + 1.0f) / 2.0f * m_yAnimation->durationSeconds());
+    }
+}