blob: 8a4c2dbd933539311a26599c0f2617b7939436cc [file]
#include "rive/animation/listener_invocation.hpp"
#include "rive/component_dirt.hpp"
#include "rive/assets/script_asset.hpp"
#include "rive/scripted/scripted_drawable.hpp"
using namespace rive;
#ifdef WITH_RIVE_SCRIPTING
void ScriptedDrawable::didHydrateScriptInputs()
{
m_isAdvanceActive = true;
addDirt(ComponentDirt::Paint);
}
void ScriptedDrawable::didReinit()
{
// A paused editor never ticks scripts, so advance and update driven
// content, like gpu canvas fills, would stay blank until play; force one
// zero step and an update to re-record it.
m_isAdvanceActive = true;
m_forceAdvance = true;
addDirt(ComponentDirt::Paint | ComponentDirt::ScriptUpdate);
}
void ScriptedDrawable::draw(Renderer* renderer)
{
if (!draws() || m_vm == nullptr || !m_vm->valid())
{
return;
}
float opacity = renderOpacity();
bool needsOpacitySave = (opacity != 1.0f);
if (m_needsSaveOperation || needsOpacitySave)
{
renderer->save();
}
if (needsOpacitySave)
{
renderer->modulateOpacity(opacity);
}
renderer->transform(worldTransform());
m_vm->callDraw(this, m_self, renderer);
if (m_needsSaveOperation || needsOpacitySave)
{
renderer->restore();
}
}
std::vector<HitComponent*> ScriptedDrawable::hitComponents(
StateMachineInstance* sm)
{
if (!listensToPointerEvents())
{
return {};
}
auto* hitComponent = new HitScriptedDrawable(this, sm);
return std::vector<HitComponent*>{hitComponent};
}
bool ScriptedDrawable::gamepadDispatch(const ListenerInvocation& inv)
{
if (m_vm == nullptr || !m_vm->valid())
{
return false;
}
const char* method = nullptr;
switch (inv.kind())
{
case ListenerInvocationKind::gamepadConnected:
method = "gamepadConnected";
break;
case ListenerInvocationKind::gamepadEvent:
method = "gamepadEvent";
break;
case ListenerInvocationKind::gamepadDisconnected:
method = "gamepadDisconnected";
break;
default:
return false;
}
if (!m_vm->callGamepadEvent(this, m_self, method, inv))
{
return false;
}
wakeAdvance();
return true;
}
HitResult HitScriptedDrawable::processEvent(Vec2D position,
ListenerType hitType,
bool canHit,
float timeStamp,
int pointerId)
{
HitResult hitResult = HitResult::none;
auto scriptAsset = m_drawable->scriptAsset();
auto backend = m_drawable->backend();
if (backend == nullptr || !backend->valid() || scriptAsset == nullptr ||
!handlesEvent(canHit, hitType))
{
return HitResult::none;
}
Vec2D localPos;
auto hasLocalPos = m_drawable->worldToLocal(position, &localPos);
if (!hasLocalPos)
{
return hitResult;
}
auto mName = methodName(canHit, hitType);
if (backend->callPointerEvent(m_drawable,
m_drawable->self(),
mName.c_str(),
pointerId,
localPos,
&hitResult))
{
m_drawable->wakeAdvance();
}
return hitResult;
}
HitResult HitScriptedDrawable::processGamepadInvocation(
const ListenerInvocation&,
ScriptedDrawable*)
{
// Gamepad dispatch to ScriptedDrawables runs through
// StateMachineInstance::broadcastGamepadToScriptedDrawables's walk over
// m_gamepadScriptedDrawables — that list includes gamepad-only scripts
// (which never enter m_hitComponents because hitComponents() is gated on
// listensToPointerEvents()). Returning none here avoids double-dispatch
// for drawables that handle both pointer and gamepad.
return HitResult::none;
}
bool ScriptedDrawable::keyInput(Key key,
KeyModifiers modifiers,
bool isPressed,
bool isRepeat)
{
if (!wantsKeyboardInput())
{
return false;
}
if (m_vm == nullptr || !m_vm->valid())
{
return false;
}
bool shouldStopPropagation = m_vm->callKeyboardEvent(this,
self(),
key,
modifiers,
isPressed,
isRepeat);
wakeAdvance();
return shouldStopPropagation;
}
bool ScriptedDrawable::textInput(const std::string& text)
{
if (!wantsTextInput())
{
return false;
}
if (m_vm == nullptr || !m_vm->valid())
{
return false;
}
bool shouldStopPropagation = m_vm->callTextEvent(this, self(), text);
wakeAdvance();
return shouldStopPropagation;
}
bool ScriptedDrawable::willDraw()
{
return Super::willDraw() && m_vm != nullptr && draws();
}
#else
void ScriptedDrawable::draw(Renderer* renderer) {}
std::vector<HitComponent*> ScriptedDrawable::hitComponents(
StateMachineInstance* sm)
{
return {};
}
HitResult HitScriptedDrawable::processEvent(Vec2D position,
ListenerType hitType,
bool canHit,
float timeStamp,
int pointerId)
{
return HitResult::none;
}
HitResult HitScriptedDrawable::processGamepadInvocation(
const ListenerInvocation& invocation,
ScriptedDrawable* alreadyDispatched)
{
// TODO: implement
return HitResult::none;
}
bool ScriptedDrawable::willDraw() { return Super::willDraw() && draws(); }
#endif
void ScriptedDrawable::update(ComponentDirt value)
{
Super::update(value);
if ((value & ComponentDirt::ScriptUpdate) == ComponentDirt::ScriptUpdate)
{
scriptUpdate();
m_isAdvanceActive = true;
}
}
void ScriptedDrawable::wakeAdvance()
{
m_isAdvanceActive = true;
addScriptedDirt(ComponentDirt::Paint);
}
Core* ScriptedDrawable::hitTest(HitInfo*, const Mat2D&) { return nullptr; }
StatusCode ScriptedDrawable::onAddedDirty(CoreContext* context)
{
auto code = Super::onAddedDirty(context);
if (code != StatusCode::Ok)
{
return code;
}
artboard()->addScriptedObject(this);
return StatusCode::Ok;
}
bool ScriptedDrawable::advanceComponent(float elapsedSeconds,
AdvanceFlags flags)
{
bool forced = m_forceAdvance;
m_forceAdvance = false;
if (elapsedSeconds == 0 && !forced)
{
return false;
}
if (!m_isAdvanceActive || isCollapsed())
{
return false;
}
m_isAdvanceActive = false;
if (!enums::is_flag_set(flags, AdvanceFlags::AdvanceNested))
{
elapsedSeconds = 0;
}
auto advanced = scriptAdvance(elapsedSeconds);
if (advanced)
{
m_isAdvanceActive = true;
addScriptedDirt(ComponentDirt::Paint);
}
return advanced;
}
bool ScriptedDrawable::addScriptedDirt(ComponentDirt value, bool recurse)
{
return Drawable::addDirt(value, recurse);
}
void ScriptedDrawable::addProperty(CustomProperty* prop)
{
auto scriptInput = ScriptInput::from(prop);
if (scriptInput != nullptr)
{
scriptInput->scriptedObject(this);
}
CustomPropertyContainer::addProperty(prop);
}
StatusCode ScriptedDrawable::import(ImportStack& importStack)
{
auto result = registerReferencer(importStack);
if (result != StatusCode::Ok)
{
return result;
}
return Super::import(importStack);
}
Core* ScriptedDrawable::clone() const
{
ScriptedDrawable* twin =
ScriptedDrawableBase::clone()->as<ScriptedDrawable>();
if (m_fileAsset != nullptr)
{
twin->setAsset(m_fileAsset);
}
return twin;
}
void ScriptedDrawable::markNeedsUpdate()
{
if (inUpdatePhase())
{
return;
}
addScriptedDirt(ComponentDirt::ScriptUpdate);
}
bool ScriptedDrawable::worldToLocal(Vec2D world, Vec2D* local)
{
Mat2D toMountedArtboard;
if (!worldTransform().invert(&toMountedArtboard))
{
return false;
}
*local = toMountedArtboard * world;
return true;
}
bool HitScriptedDrawable::handlesEvent(bool canHit, ListenerType hitEvent)
{
if (canHit)
{
switch (hitEvent)
{
case ListenerType::down:
return m_drawable->wantsPointerDown();
case ListenerType::up:
return m_drawable->wantsPointerUp();
case ListenerType::dragStart:
return false;
case ListenerType::dragEnd:
return false;
default:
return m_drawable->wantsPointerMove();
}
}
return m_drawable->wantsPointerExit();
}
std::string HitScriptedDrawable::methodName(bool canHit, ListenerType hitEvent)
{
if (canHit)
{
switch (hitEvent)
{
case ListenerType::down:
return "pointerDown";
case ListenerType::up:
return "pointerUp";
case ListenerType::dragStart:
case ListenerType::dragEnd:
return "";
default:
return "pointerMove";
}
}
return "pointerExit";
}