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());
+}