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