blob: 25eb4f78856be86ff09cc347a6b74436e60d022b [file]
#include "rive/animation/state_machine_instance.hpp"
#include "rive/artboard.hpp"
#include "rive/bones/skin.hpp"
#include "rive/constraints/constraint.hpp"
#include "rive/constraints/targeted_constraint.hpp"
#include "rive/shapes/paint/stroke.hpp"
#include "rive/shapes/paint/trim_path.hpp"
#include "rive/shapes/points_path.hpp"
#include "rive/shapes/shape.hpp"
#include "rive/shapes/shape_paint_path.hpp"
#include "rive/transform_component.hpp"
#include "rive_file_reader.hpp"
#include <catch.hpp>
// zombie_skins.riv: 25 skins and 17 constraints (IK, translation, rotation and
// scale) in its default artboard. Advanced without a state machine, nothing
// moves once the first update has settled it.
TEST_CASE("a fade neither re-skins nor re-constrains", "[opacity]")
{
auto file = ReadRiveFile("assets/zombie_skins.riv");
auto artboard = file->artboardDefault();
REQUIRE(artboard != nullptr);
artboard->advance(0.0f);
auto skins = artboard->find<rive::Skin>();
auto constraints = artboard->find<rive::Constraint>();
REQUIRE(!skins.empty());
REQUIRE(!constraints.empty());
// The new opacity reaches every skin through its bones and every
// constraint through the component it constrains. Neither moves anything.
artboard->opacity(0.5f);
for (auto* skin : skins)
{
// The skinned path (or mesh), which a re-skin marks dirty.
auto* skinned = skin->parent();
CHECK_FALSE(skinned->hasDirt(rive::ComponentDirt::Path));
CHECK_FALSE(skinned->hasDirt(rive::ComponentDirt::Vertices));
}
for (auto* constraint : constraints)
{
CHECK_FALSE(
constraint->parent()->hasDirt(rive::ComponentDirt::Transform));
}
}
// A constraint collapses along with the component it constrains (a Solo hides
// both), and neither updates while hidden. Its target can still move in the
// meantime, alongside a fade: once shown again, the constrained part has to
// land where it would have without ever being hidden.
TEST_CASE("constrained parts hidden during a fade come back constrained",
"[opacity]")
{
auto file = ReadRiveFile("assets/zombie_skins.riv");
auto hidden = file->artboardDefault();
auto shown = file->artboardDefault();
hidden->advance(0.0f);
shown->advance(0.0f);
auto hiddenConstraints = hidden->find<rive::TargetedConstraint>();
auto shownConstraints = shown->find<rive::TargetedConstraint>();
REQUIRE(!hiddenConstraints.empty());
REQUIRE(hiddenConstraints.size() == shownConstraints.size());
float opacity = 1.0f;
for (size_t i = 0; i < hiddenConstraints.size(); i++)
{
auto* constrained =
hiddenConstraints[i]->parent()->as<rive::TransformComponent>();
auto* reference =
shownConstraints[i]->parent()->as<rive::TransformComponent>();
auto* hiddenObject = hidden->resolve(hiddenConstraints[i]->targetId());
auto* shownObject = shown->resolve(shownConstraints[i]->targetId());
if (hiddenObject == nullptr ||
!hiddenObject->is<rive::TransformComponent>())
{
continue;
}
auto* hiddenTarget = hiddenObject->as<rive::TransformComponent>();
auto* shownTarget = shownObject->as<rive::TransformComponent>();
INFO(constrained->name());
opacity = opacity == 1.0f ? 0.5f : 1.0f;
constrained->collapse(true);
hidden->advance(0.0f);
hiddenTarget->rotation(hiddenTarget->rotation() + 0.3f);
hidden->opacity(opacity);
hidden->advance(0.0f);
constrained->collapse(false);
hidden->advance(0.0f);
shownTarget->rotation(shownTarget->rotation() + 0.3f);
shown->opacity(opacity);
shown->advance(0.0f);
CHECK(constrained->worldTransform() == reference->worldTransform());
}
}
static bool hasSkinnedPath(rive::Shape* shape)
{
for (auto path : shape->paths())
{
if (path->is<rive::PointsPath>() &&
path->as<rive::PointsPath>()->skin() != nullptr)
{
return true;
}
}
return false;
}
TEST_CASE("a skinned shape measures its trim path when it fades in",
"[opacity]")
{
// electrified_button_simple hides skinned shapes, stroked with an animated
// trim path, at zero opacity and fades them in on hover. A paint skips its
// effects while it is transparent and relies on being re-invalidated when
// it shows. A transparent shape normally defers its path, and rebuilding
// it on show is what re-invalidates the paint. Skinned shapes never defer,
// and once a fade stopped re-skinning them nothing re-invalidated their
// paints, so they faded in with an empty trim path.
auto file = ReadRiveFile("assets/electrified_button_simple.riv");
auto artboard = file->artboardDefault();
REQUIRE(artboard != nullptr);
auto machine = artboard->stateMachineNamed("button");
REQUIRE(machine != nullptr);
machine->advanceAndApply(0.0f);
std::vector<std::pair<rive::TrimPath*, rive::Stroke*>> hidden;
for (auto trim : artboard->find<rive::TrimPath>())
{
auto parent = trim->parent();
if (parent == nullptr || !parent->is<rive::Stroke>())
{
continue;
}
auto stroke = parent->as<rive::Stroke>();
auto shape = stroke->parent();
if (shape != nullptr && shape->is<rive::Shape>() &&
hasSkinnedPath(shape->as<rive::Shape>()) &&
stroke->renderOpacity() == 0)
{
hidden.emplace_back(trim, stroke);
}
}
REQUIRE(!hidden.empty());
machine->pointerMove(rive::Vec2D(250.0f, 250.0f));
machine->advanceAndApply(0.0f);
for (auto [trim, stroke] : hidden)
{
REQUIRE(stroke->renderOpacity() != 0);
auto effectPath = trim->effectPath(stroke);
REQUIRE(effectPath != nullptr);
CHECK(!effectPath->rawPath()->empty());
}
}