| #include "rive/joystick.hpp" | |
| #include "rive/artboard.hpp" | |
| #include "rive/transform_component.hpp" | |
| #include "rive/animation/keyed_object.hpp" | |
| #include "rive/animation/linear_animation.hpp" | |
| #include "rive/animation/linear_animation_instance.hpp" | |
| using namespace rive; | |
| Joystick::Joystick() = default; | |
| Joystick::~Joystick() = default; | |
| StatusCode Joystick::onAddedDirty(CoreContext* context) | |
| { | |
| StatusCode status = Super::onAddedDirty(context); | |
| if (status != StatusCode::Ok) | |
| { | |
| return status; | |
| } | |
| if (handleSourceId() != Core::emptyId) | |
| { | |
| auto coreObject = context->resolve(handleSourceId()); | |
| if (coreObject == nullptr || !coreObject->is<TransformComponent>()) | |
| { | |
| return StatusCode::MissingObject; | |
| } | |
| m_handleSource = static_cast<TransformComponent*>(coreObject); | |
| } | |
| return StatusCode::Ok; | |
| } | |
| StatusCode Joystick::onAddedClean(CoreContext* context) | |
| { | |
| m_xAnimation = artboard()->animation(xId()); | |
| m_yAnimation = artboard()->animation(yId()); | |
| // On an artboard instance, create a playback context per animation so that | |
| // data-bound keyframe values resolve when the joystick applies them (the | |
| // shared LinearAnimation is applied directly, with no scene driving it). | |
| // The source artboard is never played, so skip it (and it isn't an | |
| // ArtboardInstance to construct the context against). | |
| if (artboard()->isInstance()) | |
| { | |
| auto* instance = static_cast<ArtboardInstance*>(artboard()); | |
| if (m_xAnimation != nullptr) | |
| { | |
| m_xAnimationInstance = | |
| std::make_unique<LinearAnimationInstance>(m_xAnimation, | |
| instance); | |
| } | |
| if (m_yAnimation != nullptr) | |
| { | |
| m_yAnimationInstance = | |
| std::make_unique<LinearAnimationInstance>(m_yAnimation, | |
| instance); | |
| } | |
| } | |
| return StatusCode::Ok; | |
| } | |
| void Joystick::buildDependencies() | |
| { | |
| // We only need to update if we're in world space (and that is only required | |
| // at runtime if we have a custom handle source). | |
| if (m_handleSource != nullptr && parent() != nullptr) | |
| { | |
| parent()->addDependent(this); | |
| m_handleSource->addDependent(this); | |
| } | |
| } | |
| void Joystick::update(ComponentDirt value) | |
| { | |
| if (m_handleSource == nullptr) | |
| { | |
| return; | |
| } | |
| if (hasDirt(value, | |
| ComponentDirt::WorldTransform | ComponentDirt::Transform)) | |
| { | |
| Mat2D world = Mat2D::fromTranslate(posX(), posY()); | |
| if (parent() != nullptr && parent()->is<WorldTransformComponent>()) | |
| { | |
| world = parent()->as<WorldTransformComponent>()->worldTransform() * | |
| world; | |
| } | |
| if (m_worldTransform != world) | |
| { | |
| m_worldTransform = world; | |
| m_inverseWorldTransform = world.invertOrIdentity(); | |
| } | |
| auto pos = m_inverseWorldTransform * m_handleSource->worldTranslation(); | |
| auto localBounds = AABB(-width() * originX(), | |
| -height() * originY(), | |
| -width() * originX() + width(), | |
| -height() * originY() + height()); | |
| auto local = localBounds.factorFrom(pos); | |
| x(local.x); | |
| y(local.y); | |
| } | |
| } | |
| void Joystick::apply(Artboard* artboard) const | |
| { | |
| if (m_xAnimation != nullptr) | |
| { | |
| m_xAnimation->apply( | |
| artboard, | |
| ((isJoystickFlagged(JoystickFlags::invertX) ? -x() : x()) + 1.0f) / | |
| 2.0f * m_xAnimation->durationSeconds(), | |
| 1.0f, | |
| m_xAnimationInstance.get()); | |
| } | |
| if (m_yAnimation != nullptr) | |
| { | |
| m_yAnimation->apply( | |
| artboard, | |
| ((isJoystickFlagged(JoystickFlags::invertY) ? -y() : y()) + 1.0f) / | |
| 2.0f * m_yAnimation->durationSeconds(), | |
| 1.0f, | |
| m_yAnimationInstance.get()); | |
| } | |
| for (const auto& nestedRemapAnimation : m_dependents) | |
| { | |
| nestedRemapAnimation->advance(0, false); | |
| } | |
| } | |
| Vec2D Joystick::measureLayout(float width, | |
| LayoutMeasureMode widthMode, | |
| float height, | |
| LayoutMeasureMode heightMode) | |
| { | |
| return Vec2D(std::min((widthMode == LayoutMeasureMode::undefined | |
| ? std::numeric_limits<float>::max() | |
| : width), | |
| Joystick::width()), | |
| std::min((heightMode == LayoutMeasureMode::undefined | |
| ? std::numeric_limits<float>::max() | |
| : height), | |
| Joystick::height())); | |
| } | |
| void Joystick::controlSize(Vec2D size, | |
| LayoutScaleType widthScaleType, | |
| LayoutScaleType heightScaleType, | |
| LayoutDirection direction) | |
| { | |
| width(size.x); | |
| height(size.y); | |
| posX(size.x * originX()); | |
| posY(size.y * originY()); | |
| } | |
| void Joystick::xChanged() { artboard()->addDirt(ComponentDirt::Components); } | |
| void Joystick::yChanged() { artboard()->addDirt(ComponentDirt::Components); } | |
| void Joystick::posXChanged() { artboard()->addDirt(ComponentDirt::Components); } | |
| void Joystick::posYChanged() { artboard()->addDirt(ComponentDirt::Components); } | |
| void Joystick::widthChanged() | |
| { | |
| artboard()->addDirt(ComponentDirt::Components); | |
| } | |
| void Joystick::heightChanged() | |
| { | |
| artboard()->addDirt(ComponentDirt::Components); | |
| } | |
| void Joystick::addAnimationDependents(Artboard* artboard, | |
| LinearAnimation* animation) | |
| { | |
| auto totalObjects = animation->numKeyedObjects(); | |
| for (int i = 0; i < totalObjects; i++) | |
| { | |
| auto object = animation->getObject(i); | |
| auto coreObject = artboard->resolve(object->objectId()); | |
| if (coreObject != nullptr && coreObject->is<NestedRemapAnimation>()) | |
| { | |
| m_dependents.push_back(coreObject->as<NestedRemapAnimation>()); | |
| } | |
| } | |
| } | |
| void Joystick::addDependents(Artboard* artboard) | |
| { | |
| if (m_yAnimation != nullptr) | |
| { | |
| addAnimationDependents(artboard, m_yAnimation); | |
| } | |
| if (m_xAnimation != nullptr) | |
| { | |
| addAnimationDependents(artboard, m_xAnimation); | |
| } | |
| } |