Solos

So far:
- [x] Editor
- [x] Dart Tests
- [x] Flutter Runtime
- [x] C++ Runtime
- [x] C++ Tests

Hierarchy interaction:
<img width="280" alt="CleanShot 2023-03-24 at 21 44 53@2x" src="https://user-images.githubusercontent.com/454182/227696646-09f9cbe5-c482-4bab-aae9-b0b36c16047e.png">

Nesting:
<img width="394" alt="CleanShot 2023-03-24 at 21 45 23@2x" src="https://user-images.githubusercontent.com/454182/227696653-b1132ba1-5471-4c6d-9b59-20387389ae12.png">

Inspector active solo selection:
<img width="253" alt="CleanShot 2023-03-24 at 21 45 33@2x" src="https://user-images.githubusercontent.com/454182/227696660-6676acfa-15ab-4ae2-a866-4b7898bc1f52.png">

Animatable with timeline hierarchy value too:
<img width="510" alt="CleanShot 2023-03-24 at 21 46 07@2x" src="https://user-images.githubusercontent.com/454182/227696686-255064c9-43fd-4213-9e3f-9cd46cca9de3.png">

Diffs=
daaf140ba Solos (#5047)
diff --git a/.rive_head b/.rive_head
index a911f6c..3f2c367 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-44ef23f7eba8156a8d1129a18bc948e74c920a6a
+daaf140ba0a062ba9ec7f4eafa1e198d9121b220
diff --git a/dev/defs/artboard.json b/dev/defs/artboard.json
index 53158b7..3a359a6 100644
--- a/dev/defs/artboard.json
+++ b/dev/defs/artboard.json
@@ -69,17 +69,6 @@
       },
       "description": "Origin y in normalized coordinates (0.5 = center, 0 = top, 1 = bottom)."
     },
-    "editingAnimation": {
-      "type": "Id",
-      "initialValue": "Core.missingId",
-      "key": {
-        "int": 131,
-        "string": "editinganimation"
-      },
-      "description": "Id of the last open animation.",
-      "runtime": false,
-      "coop": false
-    },
     "defaultStateMachineId": {
       "type": "Id",
       "typeRuntime": "uint",
@@ -123,6 +112,17 @@
       "description": "List of expanded animation folders",
       "runtime": false,
       "coop": false
+    },
+    "selectedAnimations": {
+      "type": "List<Id>",
+      "initialValue": "[]",
+      "key": {
+        "int": 291,
+        "string": "selectedanimations"
+      },
+      "description": "List of selected animations",
+      "runtime": false,
+      "coop": false
     }
   }
 }
\ No newline at end of file
diff --git a/dev/defs/backboard.json b/dev/defs/backboard.json
index 7d709df..66bd5f3 100644
--- a/dev/defs/backboard.json
+++ b/dev/defs/backboard.json
@@ -145,6 +145,17 @@
       "description": "If bones are enabled for this file",
       "runtime": false,
       "coop": false
+    },
+    "toolbarOutputAction": {
+      "type": "uint",
+      "initialValue": "0",
+      "key": {
+        "int": 290,
+        "string": "toolbaroutputaction"
+      },
+      "description": "Primary output action in the toolbar (share, publihs, download)",
+      "runtime": false,
+      "coop": false
     }
   }
 }
\ No newline at end of file
diff --git a/dev/defs/solo.json b/dev/defs/solo.json
new file mode 100644
index 0000000..8c888b8
--- /dev/null
+++ b/dev/defs/solo.json
@@ -0,0 +1,22 @@
+{
+  "name": "Solo",
+  "key": {
+    "int": 147,
+    "string": "solo"
+  },
+  "extends": "node.json",
+  "properties": {
+    "activeComponentId": {
+      "type": "Id",
+      "typeRuntime": "uint",
+      "initialValue": "Core.missingId",
+      "initialValueRuntime": "0",
+      "animates": true,
+      "key": {
+        "int": 296,
+        "string": "activeComponentId"
+      },
+      "description": "Identifier of the active child in the solo set."
+    }
+  }
+}
\ No newline at end of file
diff --git a/include/rive/component.hpp b/include/rive/component.hpp
index 0c52a6c..0aee6ee 100644
--- a/include/rive/component.hpp
+++ b/include/rive/component.hpp
@@ -25,10 +25,12 @@
     ComponentDirt m_Dirt = ComponentDirt::Filthy;
 
 public:
+    virtual bool collapse(bool value);
     inline Artboard* artboard() const { return m_Artboard; }
     StatusCode onAddedDirty(CoreContext* context) override;
     inline ContainerComponent* parent() const { return m_Parent; }
     const std::vector<Component*>& dependents() const { return m_Dependents; }
+
     void addDependent(Component* component);
 
     // TODO: re-evaluate when more of the lib is complete...
@@ -48,6 +50,11 @@
     }
 
     StatusCode import(ImportStack& importStack) override;
+
+    bool isCollapsed() const
+    {
+        return (m_Dirt & ComponentDirt::Collapsed) == ComponentDirt::Collapsed;
+    }
 };
 } // namespace rive
 
diff --git a/include/rive/component_dirt.hpp b/include/rive/component_dirt.hpp
index e1f5c1b..b01bd1f 100644
--- a/include/rive/component_dirt.hpp
+++ b/include/rive/component_dirt.hpp
@@ -8,51 +8,56 @@
 {
     None = 0,
 
-    Dependents = 1 << 0,
+    /// Used to mark that a component (and subsequently it's children) do not
+    /// update with the Artboard update cycle and that any drawable should also
+    /// be hidden. Used by Solos.
+    Collapsed = 1 << 0,
+
+    Dependents = 1 << 1,
 
     /// General flag for components are dirty (if this is up, the update
     /// cycle runs). It gets automatically applied with any other dirt.
-    Components = 1 << 1,
+    Components = 1 << 2,
 
     /// Draw order needs to be re-computed.
-    DrawOrder = 1 << 2,
+    DrawOrder = 1 << 3,
 
     /// Path is dirty and needs to be rebuilt.
-    Path = 1 << 3,
+    Path = 1 << 4,
 
     /// TextShape is dirty and needs to be rebuilt.
-    TextShape = 1 << 3,
+    TextShape = 1 << 4,
 
     /// Skin needs to recompute bone transformations.
-    Skin = 1 << 3,
+    Skin = 1 << 4,
 
     /// Vertices have changed, re-order cached lists.
-    Vertices = 1 << 4,
+    Vertices = 1 << 5,
 
     /// Used by any component that needs to recompute their local transform.
     /// Usually components that have their transform dirty will also have
     /// their worldTransform dirty.
-    Transform = 1 << 5,
+    Transform = 1 << 6,
 
     /// Used by any component that needs to update its world transform.
-    WorldTransform = 1 << 6,
+    WorldTransform = 1 << 7,
 
     /// Marked when the stored render opacity needs to be updated.
-    RenderOpacity = 1 << 7,
+    RenderOpacity = 1 << 8,
 
     /// Dirt used to mark some stored paint needs to be rebuilt or that we
     /// just want to trigger an update cycle so painting occurs.
-    Paint = 1 << 8,
+    Paint = 1 << 9,
 
     /// Used by the gradients track when the stops need to be re-ordered.
-    Stops = 1 << 9,
+    Stops = 1 << 10,
 
     /// Blend modes need to be updated
     // TODO: do we need this?
     // BlendMode = 1 << 9,
 
-    // Everything is dirty.
-    Filthy = 0xFFFF
+    /// All dirty. Every flag (apart from Collapsed) is set.
+    Filthy = 0xFFFE
 };
 
 inline constexpr ComponentDirt operator&(ComponentDirt lhs, ComponentDirt rhs)
diff --git a/include/rive/container_component.hpp b/include/rive/container_component.hpp
index 81b38a3..bc32628 100644
--- a/include/rive/container_component.hpp
+++ b/include/rive/container_component.hpp
@@ -5,7 +5,15 @@
 namespace rive
 {
 class ContainerComponent : public ContainerComponentBase
-{};
+{
+private:
+    std::vector<Component*> m_children;
+
+public:
+    const std::vector<Component*>& children() const { return m_children; }
+    void addChild(Component* component);
+    bool collapse(bool value) override;
+};
 } // namespace rive
 
 #endif
\ No newline at end of file
diff --git a/include/rive/drawable.hpp b/include/rive/drawable.hpp
index aa048b1..08a40c2 100644
--- a/include/rive/drawable.hpp
+++ b/include/rive/drawable.hpp
@@ -35,7 +35,7 @@
     {
         // For now we have a single drawable flag, when we have more we can
         // make an actual enum for this.
-        return (drawableFlags() & 0x1) == 0x1;
+        return (drawableFlags() & 0x1) == 0x1 || hasDirt(ComponentDirt::Collapsed);
     }
 };
 } // namespace rive
diff --git a/include/rive/generated/core_registry.hpp b/include/rive/generated/core_registry.hpp
index 0e16ec4..d6f5af6 100644
--- a/include/rive/generated/core_registry.hpp
+++ b/include/rive/generated/core_registry.hpp
@@ -119,6 +119,7 @@
 #include "rive/shapes/straight_vertex.hpp"
 #include "rive/shapes/triangle.hpp"
 #include "rive/shapes/vertex.hpp"
+#include "rive/solo.hpp"
 #include "rive/text/text.hpp"
 #include "rive/text/text_style.hpp"
 #include "rive/text/text_style_axis.hpp"
@@ -152,6 +153,8 @@
                 return new Node();
             case NestedArtboardBase::typeKey:
                 return new NestedArtboard();
+            case SoloBase::typeKey:
+                return new Solo();
             case NestedSimpleAnimationBase::typeKey:
                 return new NestedSimpleAnimation();
             case AnimationStateBase::typeKey:
@@ -382,6 +385,9 @@
             case NestedAnimationBase::animationIdPropertyKey:
                 object->as<NestedAnimationBase>()->animationId(value);
                 break;
+            case SoloBase::activeComponentIdPropertyKey:
+                object->as<SoloBase>()->activeComponentId(value);
+                break;
             case ListenerInputChangeBase::inputIdPropertyKey:
                 object->as<ListenerInputChangeBase>()->inputId(value);
                 break;
@@ -946,6 +952,8 @@
                 return object->as<NestedArtboardBase>()->artboardId();
             case NestedAnimationBase::animationIdPropertyKey:
                 return object->as<NestedAnimationBase>()->animationId();
+            case SoloBase::activeComponentIdPropertyKey:
+                return object->as<SoloBase>()->activeComponentId();
             case ListenerInputChangeBase::inputIdPropertyKey:
                 return object->as<ListenerInputChangeBase>()->inputId();
             case AnimationStateBase::animationIdPropertyKey:
@@ -1324,6 +1332,7 @@
             case DrawableBase::drawableFlagsPropertyKey:
             case NestedArtboardBase::artboardIdPropertyKey:
             case NestedAnimationBase::animationIdPropertyKey:
+            case SoloBase::activeComponentIdPropertyKey:
             case ListenerInputChangeBase::inputIdPropertyKey:
             case AnimationStateBase::animationIdPropertyKey:
             case NestedInputBase::inputIdPropertyKey:
diff --git a/include/rive/generated/solo_base.hpp b/include/rive/generated/solo_base.hpp
new file mode 100644
index 0000000..2923702
--- /dev/null
+++ b/include/rive/generated/solo_base.hpp
@@ -0,0 +1,75 @@
+#ifndef _RIVE_SOLO_BASE_HPP_
+#define _RIVE_SOLO_BASE_HPP_
+#include "rive/core/field_types/core_uint_type.hpp"
+#include "rive/node.hpp"
+namespace rive
+{
+class SoloBase : public Node
+{
+protected:
+    typedef Node Super;
+
+public:
+    static const uint16_t typeKey = 147;
+
+    /// Helper to quickly determine if a core object extends another without RTTI
+    /// at runtime.
+    bool isTypeOf(uint16_t typeKey) const override
+    {
+        switch (typeKey)
+        {
+            case SoloBase::typeKey:
+            case NodeBase::typeKey:
+            case TransformComponentBase::typeKey:
+            case WorldTransformComponentBase::typeKey:
+            case ContainerComponentBase::typeKey:
+            case ComponentBase::typeKey:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    uint16_t coreType() const override { return typeKey; }
+
+    static const uint16_t activeComponentIdPropertyKey = 296;
+
+private:
+    uint32_t m_ActiveComponentId = 0;
+
+public:
+    inline uint32_t activeComponentId() const { return m_ActiveComponentId; }
+    void activeComponentId(uint32_t value)
+    {
+        if (m_ActiveComponentId == value)
+        {
+            return;
+        }
+        m_ActiveComponentId = value;
+        activeComponentIdChanged();
+    }
+
+    Core* clone() const override;
+    void copy(const SoloBase& object)
+    {
+        m_ActiveComponentId = object.m_ActiveComponentId;
+        Node::copy(object);
+    }
+
+    bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
+    {
+        switch (propertyKey)
+        {
+            case activeComponentIdPropertyKey:
+                m_ActiveComponentId = CoreUintType::deserialize(reader);
+                return true;
+        }
+        return Node::deserialize(propertyKey, reader);
+    }
+
+protected:
+    virtual void activeComponentIdChanged() {}
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/solo.hpp b/include/rive/solo.hpp
new file mode 100644
index 0000000..fcfe8e3
--- /dev/null
+++ b/include/rive/solo.hpp
@@ -0,0 +1,17 @@
+#ifndef _RIVE_SOLO_HPP_
+#define _RIVE_SOLO_HPP_
+#include "rive/generated/solo_base.hpp"
+namespace rive
+{
+class Solo : public SoloBase
+{
+public:
+    void activeComponentIdChanged() override;
+    StatusCode onAddedClean(CoreContext* context) override;
+
+private:
+    void propagateCollapse();
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/src/artboard.cpp b/src/artboard.cpp
index d380660..74dc6c0 100644
--- a/src/artboard.cpp
+++ b/src/artboard.cpp
@@ -439,11 +439,12 @@
                 auto component = m_DependencyOrder[i];
                 m_DirtDepth = i;
                 auto d = component->m_Dirt;
-                if (d == ComponentDirt::None)
+                if (d == ComponentDirt::None ||
+                    (d & ComponentDirt::Collapsed) == ComponentDirt::Collapsed)
                 {
                     continue;
                 }
-                component->m_Dirt = ComponentDirt::None;
+                component->m_Dirt &= ComponentDirt::Collapsed;
                 component->update(d);
 
                 // If the update changed the dirt depth by adding dirt
diff --git a/src/component.cpp b/src/component.cpp
index 7f58d17..b68ffdf 100644
--- a/src/component.cpp
+++ b/src/component.cpp
@@ -22,6 +22,7 @@
         return StatusCode::MissingObject;
     }
     m_Parent = static_cast<ContainerComponent*>(coreObject);
+    m_Parent->addChild(this);
     return StatusCode::Ok;
 }
 
@@ -80,3 +81,22 @@
     artboardImporter->addComponent(this);
     return Super::import(importStack);
 }
+
+bool Component::collapse(bool value)
+{
+    if (isCollapsed() == value)
+    {
+        return false;
+    }
+    if (value)
+    {
+        m_Dirt |= ComponentDirt::Collapsed;
+    }
+    else
+    {
+        m_Dirt &= ~ComponentDirt::Collapsed;
+    }
+    onDirty(m_Dirt);
+    m_Artboard->onComponentDirty(this);
+    return true;
+}
\ No newline at end of file
diff --git a/src/container_component.cpp b/src/container_component.cpp
new file mode 100644
index 0000000..0a25260
--- /dev/null
+++ b/src/container_component.cpp
@@ -0,0 +1,17 @@
+#include "rive/container_component.hpp"
+using namespace rive;
+
+void ContainerComponent::addChild(Component* component) { m_children.push_back(component); }
+
+bool ContainerComponent::collapse(bool value)
+{
+    if (!Super::collapse(value))
+    {
+        return false;
+    }
+    for (Component* child : m_children)
+    {
+        child->collapse(value);
+    }
+    return true;
+}
\ No newline at end of file
diff --git a/src/generated/solo_base.cpp b/src/generated/solo_base.cpp
new file mode 100644
index 0000000..132eb74
--- /dev/null
+++ b/src/generated/solo_base.cpp
@@ -0,0 +1,11 @@
+#include "rive/generated/solo_base.hpp"
+#include "rive/solo.hpp"
+
+using namespace rive;
+
+Core* SoloBase::clone() const
+{
+    auto cloned = new Solo();
+    cloned->copy(*this);
+    return cloned;
+}
diff --git a/src/solo.cpp b/src/solo.cpp
new file mode 100644
index 0000000..0934430
--- /dev/null
+++ b/src/solo.cpp
@@ -0,0 +1,26 @@
+#include "rive/solo.hpp"
+#include "rive/artboard.hpp"
+
+using namespace rive;
+
+void Solo::propagateCollapse()
+{
+    Core* active = artboard()->resolve(activeComponentId());
+    for (Component* child : children())
+    {
+        child->collapse(child != active);
+    }
+}
+void Solo::activeComponentIdChanged() { propagateCollapse(); }
+
+StatusCode Solo::onAddedClean(CoreContext* context)
+{
+    StatusCode code = Super::onAddedClean(context);
+    if (code != StatusCode::Ok)
+    {
+        return code;
+    }
+
+    propagateCollapse();
+    return StatusCode::Ok;
+}
\ No newline at end of file
diff --git a/test/assets/solo_test.riv b/test/assets/solo_test.riv
new file mode 100644
index 0000000..345e7e6
--- /dev/null
+++ b/test/assets/solo_test.riv
Binary files differ
diff --git a/test/solo_test.cpp b/test/solo_test.cpp
new file mode 100644
index 0000000..f653930
--- /dev/null
+++ b/test/solo_test.cpp
@@ -0,0 +1,60 @@
+#include <rive/solo.hpp>
+#include <rive/shapes/shape.hpp>
+#include <rive/animation/state_machine_instance.hpp>
+#include "rive_file_reader.hpp"
+#include <catch.hpp>
+#include <cstdio>
+
+TEST_CASE("children load correclty", "[solo]")
+{
+    auto file = ReadRiveFile("../../test/assets/solo_test.riv");
+
+    auto artboard = file->artboard()->instance();
+    artboard->advance(0.0f);
+    auto solos = artboard->find<rive::Solo>();
+    REQUIRE(solos.size() == 1);
+    auto solo = solos[0];
+    REQUIRE(solo != nullptr);
+    REQUIRE(solo->children().size() == 3);
+    REQUIRE(solo->children()[0]->is<rive::Shape>());
+    REQUIRE(solo->children()[0]->name() == "Blue");
+    REQUIRE(solo->children()[1]->is<rive::Shape>());
+    REQUIRE(solo->children()[1]->name() == "Green");
+    REQUIRE(solo->children()[2]->is<rive::Shape>());
+    REQUIRE(solo->children()[2]->name() == "Red");
+
+    auto blue = solo->children()[0]->as<rive::Shape>();
+    auto green = solo->children()[1]->as<rive::Shape>();
+    auto red = solo->children()[2]->as<rive::Shape>();
+
+    REQUIRE(!blue->isHidden());
+    REQUIRE(green->isHidden());
+    REQUIRE(red->isHidden());
+
+    REQUIRE(green->children().size() == 2);
+    REQUIRE(green->children()[0]->isCollapsed());
+    REQUIRE(green->children()[1]->isCollapsed());
+
+    REQUIRE(red->children().size() == 2);
+    REQUIRE(red->children()[0]->isCollapsed());
+    REQUIRE(red->children()[1]->isCollapsed());
+
+    auto machine = artboard->defaultStateMachine();
+    machine->advanceAndApply(0.0);
+    // Red visible at start
+    REQUIRE(blue->isHidden());
+    REQUIRE(green->isHidden());
+    REQUIRE(!red->isHidden());
+
+    machine->advanceAndApply(0.5);
+    // Green visible after 0.5 seconds.
+    REQUIRE(blue->isHidden());
+    REQUIRE(!green->isHidden());
+    REQUIRE(red->isHidden());
+
+    machine->advanceAndApply(0.5);
+    // Blue visible at end
+    REQUIRE(!blue->isHidden());
+    REQUIRE(green->isHidden());
+    REQUIRE(red->isHidden());
+}