blob: e4551d87db9ab7c57911919b6a07b5e127befd6a [file] [log] [blame]
#include "rive/core/binary_reader.hpp"
#include "rive/file.hpp"
#include "rive/event.hpp"
#include "rive/shapes/shape.hpp"
#include "rive/animation/state_machine_bool.hpp"
#include "rive/animation/state_machine_layer.hpp"
#include "rive/animation/state_machine_listener.hpp"
#include "rive/animation/animation_state.hpp"
#include "rive/animation/entry_state.hpp"
#include "rive/animation/state_transition.hpp"
#include "rive/animation/state_machine_instance.hpp"
#include "rive/animation/state_machine_input_instance.hpp"
#include "rive/animation/blend_state_1d.hpp"
#include "rive/animation/blend_animation_1d.hpp"
#include "rive/animation/blend_state_direct.hpp"
#include "rive/animation/blend_state_transition.hpp"
#include "rive/animation/listener_input_change.hpp"
#include "rive/animation/listener_fire_event.hpp"
#include "rive/animation/nested_state_machine.hpp"
#include "rive/animation/entry_state.hpp"
#include "rive/node.hpp"
#include "catch.hpp"
#include "rive_file_reader.hpp"
#include <cstdio>
TEST_CASE("file with state machine listeners be read", "[file]")
{
auto file = ReadRiveFile("../../test/assets/bullet_man.riv");
auto artboard = file->artboard("Bullet Man");
REQUIRE(artboard != nullptr);
REQUIRE(artboard->stateMachineCount() == 1);
auto stateMachine = artboard->stateMachine(0);
REQUIRE(stateMachine != nullptr);
REQUIRE(stateMachine->listenerCount() == 3);
REQUIRE(stateMachine->inputCount() == 4);
// Expect each of the three listeners to have one input change each.
auto listener1 = stateMachine->listener(0);
auto target1 = artboard->resolve(listener1->targetId());
REQUIRE(target1->is<rive::Node>());
REQUIRE(target1->as<rive::Node>()->name() == "HandWickHit");
REQUIRE(listener1->actionCount() == 1);
auto inputChange1 = listener1->action(0);
REQUIRE(inputChange1 != nullptr);
REQUIRE(inputChange1->is<rive::ListenerInputChange>());
REQUIRE(inputChange1->as<rive::ListenerInputChange>()->inputId() == 0);
auto listener2 = stateMachine->listener(1);
auto target2 = artboard->resolve(listener2->targetId());
REQUIRE(target2->is<rive::Node>());
REQUIRE(target2->as<rive::Node>()->name() == "HandCannonHit");
REQUIRE(listener2->actionCount() == 1);
auto inputChange2 = listener2->action(0);
REQUIRE(inputChange2 != nullptr);
REQUIRE(inputChange2->is<rive::ListenerInputChange>());
REQUIRE(inputChange2->as<rive::ListenerInputChange>()->inputId() == 1);
auto listener3 = stateMachine->listener(2);
auto target3 = artboard->resolve(listener3->targetId());
REQUIRE(target3->is<rive::Node>());
REQUIRE(target3->as<rive::Node>()->name() == "HandHelmetHit");
REQUIRE(listener3->actionCount() == 1);
auto inputChange3 = listener3->action(0);
REQUIRE(inputChange3 != nullptr);
REQUIRE(inputChange3->is<rive::ListenerInputChange>());
REQUIRE(inputChange3->as<rive::ListenerInputChange>()->inputId() == 2);
}
TEST_CASE("hit testing via a state machine works", "[file]")
{
auto file = ReadRiveFile("../../test/assets/bullet_man.riv");
auto artboard = file->artboard("Bullet Man")->instance();
REQUIRE(artboard != nullptr);
REQUIRE(artboard->stateMachineCount() == 1);
auto stateMachine = artboard->stateMachineAt(0);
REQUIRE(stateMachine != nullptr);
// Advance artboard once so design time state is effectively in the transforms.
artboard->advance(0.0f);
stateMachine->advance(0.0f);
// Don't advance artboard again after applying state machine or our pointerDown will be off. The
// coordinates used in this test were from the design-time state.
auto trigger = stateMachine->getTrigger("Light");
REQUIRE(trigger != nullptr);
stateMachine->pointerDown(rive::Vec2D(71.0f, 263.0f));
REQUIRE(trigger->didFire());
}
TEST_CASE("hit a toggle boolean listener", "[file]")
{
auto file = ReadRiveFile("../../test/assets/light_switch.riv");
auto artboard = file->artboard()->instance();
REQUIRE(artboard != nullptr);
REQUIRE(artboard->stateMachineCount() == 1);
auto stateMachine = artboard->stateMachineAt(0);
REQUIRE(stateMachine != nullptr);
artboard->advance(0.0f);
stateMachine->advance(0.0f);
auto switchButton = stateMachine->getBool("On");
REQUIRE(switchButton != nullptr);
REQUIRE(switchButton->value() == true);
stateMachine->pointerDown(rive::Vec2D(150.0f, 258.0f));
stateMachine->pointerUp(rive::Vec2D(150.0f, 258.0f));
// Got toggled off after pressing
REQUIRE(switchButton->value() == false);
stateMachine->pointerDown(rive::Vec2D(150.0f, 258.0f));
stateMachine->pointerUp(rive::Vec2D(150.0f, 258.0f));
// Got toggled back on after pressing
REQUIRE(switchButton->value() == true);
}
TEST_CASE("can query for all rive events", "[events]")
{
auto file = ReadRiveFile("../../test/assets/event_on_listener.riv");
auto artboard = file->artboard();
auto eventCount = artboard->count<rive::Event>();
REQUIRE(eventCount == 4);
}
TEST_CASE("can query for a rive event at a given index", "[events]")
{
auto file = ReadRiveFile("../../test/assets/event_on_listener.riv");
auto artboard = file->artboard();
auto event = artboard->objectAt<rive::Event>(0);
REQUIRE(event->name() == "Somewhere.com");
}
TEST_CASE("events load correctly on a listener", "[events]")
{
auto file = ReadRiveFile("../../test/assets/event_on_listener.riv");
auto artboard = file->artboard()->instance();
REQUIRE(artboard != nullptr);
REQUIRE(artboard->stateMachineCount() == 1);
auto stateMachineInstance = artboard->stateMachineAt(0);
REQUIRE(stateMachineInstance != nullptr);
artboard->advance(0.0f);
stateMachineInstance->advance(0.0f);
auto events = artboard->find<rive::Event>();
REQUIRE(events.size() == 4);
REQUIRE(stateMachineInstance->stateMachine()->listenerCount() == 1);
auto listener1 = stateMachineInstance->stateMachine()->listener(0);
auto target1 = artboard->resolve(listener1->targetId());
REQUIRE(target1->is<rive::Shape>());
REQUIRE(listener1->actionCount() == 2);
auto fireEvent1 = listener1->action(0);
REQUIRE(fireEvent1 != nullptr);
REQUIRE(fireEvent1->is<rive::ListenerFireEvent>());
REQUIRE(fireEvent1->as<rive::ListenerFireEvent>()->eventId() != 0);
auto event = artboard->resolve(fireEvent1->as<rive::ListenerFireEvent>()->eventId());
REQUIRE(event->is<rive::Event>());
REQUIRE(event->as<rive::Event>()->name() == "Footstep");
REQUIRE(stateMachineInstance->reportedEventCount() == 0);
stateMachineInstance->pointerDown(rive::Vec2D(343.0f, 116.0f));
stateMachineInstance->pointerUp(rive::Vec2D(343.0f, 116.0f));
// There are two events on the listener.
REQUIRE(stateMachineInstance->reportedEventCount() == 2);
auto reportedEvent1 = stateMachineInstance->reportedEventAt(0);
REQUIRE(reportedEvent1.event()->name() == "Footstep");
auto reportedEvent2 = stateMachineInstance->reportedEventAt(1);
REQUIRE(reportedEvent2.event()->name() == "Event 3");
// After advancing again the reportedEventCount should return to 0.
stateMachineInstance->advance(0.0f);
REQUIRE(stateMachineInstance->reportedEventCount() == 0);
}
TEST_CASE("events load correctly on a state and transition", "[events]")
{
auto file = ReadRiveFile("../../test/assets/events_on_states.riv");
auto artboard = file->artboard()->instance();
REQUIRE(artboard != nullptr);
REQUIRE(artboard->stateMachineCount() == 1);
auto stateMachineInstance = artboard->stateMachineAt(0);
REQUIRE(stateMachineInstance != nullptr);
artboard->advance(0.0f);
stateMachineInstance->advance(0.0f);
REQUIRE(stateMachineInstance->stateMachine()->layerCount() == 1);
auto layer = stateMachineInstance->stateMachine()->layer(0);
REQUIRE(layer->stateCount() == 5);
REQUIRE(layer->entryState()->transitionCount() == 1);
auto transition = layer->entryState()->transition(0);
// No events on transition from entry.
REQUIRE(transition->events().size() == 0);
REQUIRE(transition->stateTo()->is<rive::AnimationState>());
auto firstAnimationState = transition->stateTo()->as<rive::AnimationState>();
REQUIRE(firstAnimationState->events().size() == 2);
REQUIRE(firstAnimationState->transitionCount() == 1);
transition = firstAnimationState->transition(0);
// Transition from first animation state to next one should have two events.
REQUIRE(transition->events().size() == 2);
// First should've fired as we immediately went to Timeline 1.
REQUIRE(stateMachineInstance->reportedEventCount() == 1);
REQUIRE(stateMachineInstance->reportedEventAt(0).event()->name() == "First");
stateMachineInstance->advance(1.0f);
// Exits after 2 seconds so 1 second in no events should've fired yet
REQUIRE(stateMachineInstance->reportedEventCount() == 0);
stateMachineInstance->advance(1.0f);
// At 2 seconds 2 events should fire, one for exiting the state and for taking the transition.
REQUIRE(stateMachineInstance->reportedEventCount() == 2);
REQUIRE(stateMachineInstance->reportedEventAt(0).event()->name() == "Second");
REQUIRE(stateMachineInstance->reportedEventAt(1).event()->name() == "Third");
stateMachineInstance->advance(1.0f);
// Another second in the transition should complete
REQUIRE(stateMachineInstance->reportedEventCount() == 1);
REQUIRE(stateMachineInstance->reportedEventAt(0).event()->name() == "Fourth");
}
TEST_CASE("timeline events load correctly and report", "[events]")
{
auto file = ReadRiveFile("../../test/assets/timeline_event_test.riv");
auto artboard = file->artboard()->instance();
REQUIRE(artboard != nullptr);
REQUIRE(artboard->stateMachineCount() == 1);
auto stateMachineInstance = artboard->stateMachineAt(0);
REQUIRE(stateMachineInstance != nullptr);
artboard->advance(0.0f);
stateMachineInstance->advance(0.0f);
REQUIRE(stateMachineInstance->reportedEventCount() == 0);
stateMachineInstance->advance(0.4f);
REQUIRE(stateMachineInstance->reportedEventCount() == 0);
stateMachineInstance->advance(0.2f);
REQUIRE(stateMachineInstance->reportedEventCount() == 1);
REQUIRE(stateMachineInstance->reportedEventAt(0).event()->name() == "Half");
// Event should've occurred right at 0.5 seconds.
REQUIRE(stateMachineInstance->reportedEventAt(0).secondsDelay() == Approx(0.1f));
}
TEST_CASE("events from a nested artboard propagate to a listener on a parent", "[events]")
{
auto file = ReadRiveFile("../../test/assets/nested_event_test.riv");
auto artboard = file->artboard()->instance();
REQUIRE(artboard != nullptr);
REQUIRE(artboard->stateMachineCount() == 1);
auto stateMachineInstance = artboard->stateMachineAt(0);
REQUIRE(stateMachineInstance != nullptr);
REQUIRE(stateMachineInstance->stateMachine()->inputCount() == 1);
// Input is on the main artboard
auto input = stateMachineInstance->getBool("Boolean 1");
REQUIRE(input->value() == false);
artboard->advance(0.0f);
stateMachineInstance->advance(0.0f);
auto nested = artboard->find<rive::NestedArtboard>();
REQUIRE(nested.size() == 1);
auto nestedArtboard = nested[0]->artboardInstance();
auto nestedStateMachineInstance =
nested[0]->nestedAnimations()[0]->as<rive::NestedStateMachine>()->stateMachineInstance();
REQUIRE(nestedStateMachineInstance != nullptr);
auto events = nestedArtboard->find<rive::Event>();
REQUIRE(events.size() == 1);
// Validate listener on the nested artboard
REQUIRE(nestedStateMachineInstance->stateMachine()->listenerCount() == 1);
auto listener1 = nestedStateMachineInstance->stateMachine()->listener(0);
auto target1 = nestedArtboard->resolve(listener1->targetId());
REQUIRE(target1->is<rive::Shape>());
REQUIRE(listener1->actionCount() == 1);
auto fireEvent1 = listener1->action(0);
REQUIRE(fireEvent1 != nullptr);
REQUIRE(fireEvent1->is<rive::ListenerFireEvent>());
REQUIRE(fireEvent1->as<rive::ListenerFireEvent>()->eventId() != 0);
auto event = nestedArtboard->resolve(fireEvent1->as<rive::ListenerFireEvent>()->eventId());
REQUIRE(event->is<rive::Event>());
REQUIRE(event->as<rive::Event>()->name() == "NestedEvent");
// Validate the event is reported to the nested artboard
REQUIRE(nestedStateMachineInstance->reportedEventCount() == 0);
stateMachineInstance->pointerDown(rive::Vec2D(250.0f, 100.0f));
REQUIRE(nestedStateMachineInstance->reportedEventCount() == 1);
auto nestedReportedEvent1 = nestedStateMachineInstance->reportedEventAt(0);
REQUIRE(nestedReportedEvent1.event()->name() == "NestedEvent");
artboard->advance(0.0f);
// Validate the input on the main artboard updates as a result of the event
// from the nested artboard
REQUIRE(input->value() == true);
// After advancing again the reportedEventCount should return to 0.
stateMachineInstance->advance(0.0f);
REQUIRE(stateMachineInstance->reportedEventCount() == 0);
}