/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "AnimationJS.h"
#include
#include
#include
#include
#include
#include
#include "SceneJS.h"
class OnCallJS : public ThreadSafeCallback {
NapiApi::StrongRef jsThis_;
NapiApi::StrongRef ref_;
public:
OnCallJS(const char* name, NapiApi::Object jsThis, NapiApi::Function toCall)
: ThreadSafeCallback(toCall.GetEnv(), name), jsThis_(jsThis), ref_(toCall.GetEnv(), toCall)
{}
~OnCallJS()
{
jsThis_.Reset();
ref_.Reset();
Release();
}
void Finalize(napi_env env)
{
jsThis_.Reset();
ref_.Reset();
}
void Invoked(napi_env env)
{
napi_value res;
napi_call_function(env, jsThis_.GetValue(), ref_.GetValue(), 0, nullptr, &res);
}
};
void AnimationJS::Init(napi_env env, napi_value exports)
{
BASE_NS::vector node_props;
SceneResourceImpl::GetPropertyDescs(node_props);
// Try out the helper macros.
// Declare NAPI_API_JS_NAME to simplify the registering.
#define NAPI_API_JS_NAME Animation
DeclareGetSet(bool, "enabled", GetEnabled, SetEnabled);
DeclareGetSet(float, "speed", GetSpeed, SetSpeed);
DeclareGet(float, "duration", GetDuration);
DeclareGet(bool, "running", GetRunning);
DeclareGet(float, "progress", GetProgress);
DeclareMethod("pause", Pause);
DeclareMethod("restart", Restart);
DeclareMethod("seek", Seek, float);
DeclareMethod("start", Start);
DeclareMethod("stop", Stop);
DeclareMethod("finish", Finish);
DeclareMethod("onFinished", OnFinished, NapiApi::Function);
DeclareMethod("onStarted", OnStarted, NapiApi::Function);
DeclareClass();
#undef NAPI_API_JS_NAME
}
AnimationJS::AnimationJS(napi_env e, napi_callback_info i)
: BaseObject(e, i), SceneResourceImpl(SceneResourceImpl::ANIMATION)
{
NapiApi::FunctionContext fromJs(e, i);
NapiApi::Object meJs(fromJs.This());
NapiApi::Object scene = fromJs.Arg<0>(); // access to owning scene...
NapiApi::Object args = fromJs.Arg<1>(); // access to params.
scene_ = { scene };
if (!scene_.GetObject().GetNative()) {
LOG_F("INVALID SCENE!");
}
if (const auto sceneJS = scene_.GetObject().GetJsWrapper()) {
sceneJS->DisposeHook(reinterpret_cast(&scene_), meJs);
}
META_NS::IAnimation::Ptr anim;
if (args) {
using namespace META_NS;
anim = interface_pointer_cast(GetNativeObject());
if (anim) {
// check if there is a speed controller already.(and use that)
auto attachments = interface_cast(anim)->GetAttachments();
for (auto at : attachments) {
if (interface_cast(at)) {
// yes.. (expect at most one)
speedModifier_ = interface_pointer_cast(at);
break;
}
}
}
}
if (anim) {
#if defined(USE_ANIMATION_STATE_COMPONENT_ON_COMPLETED) && (USE_ANIMATION_STATE_COMPONENT_ON_COMPLETED == 1)
using namespace SCENE_NS;
auto acc = interface_cast(anim);
IEcsObject::Ptr ecsObj;
if ((acc) && (ecsObj = acc->GetEcsObject())) {
completed_ = ecsObj->CreateProperty("AnimationStateComponent.completed").GetResult();
if (completed_) {
OnCompletedEvent_ = completed_->OnChanged();
}
}
#else
// Use IAnimation OnFinished to trigger the animation ends (has a bug)
OnCompletedEvent_ = anim->OnFinished();
#endif
}
}
void* AnimationJS::GetInstanceImpl(uint32_t id)
{
if (id == AnimationJS::ID)
return this;
return SceneResourceImpl::GetInstanceImpl(id);
}
void AnimationJS::Finalize(napi_env env)
{
DisposeNative(scene_.GetObject().GetJsWrapper());
BaseObject::Finalize(env);
}
AnimationJS::~AnimationJS()
{
LOG_V("AnimationJS -- ");
DisposeNative(nullptr);
}
void AnimationJS::DisposeNative(void*)
{
// do nothing for now..
if (!disposed_) {
disposed_ = true;
LOG_V("AnimationJS::DisposeNative");
if (const auto sceneJS = scene_.GetObject().GetJsWrapper()) {
sceneJS->ReleaseDispose(reinterpret_cast(&scene_));
}
scene_.Reset();
// release all the native resources (callbacks, modifiers, properties)
if (OnCompletedEvent_ && OnFinishedToken_) {
OnCompletedEvent_->RemoveHandler(OnFinishedToken_);
}
OnCompletedEvent_ = {};
OnFinishedToken_ = 0;
#if defined(USE_ANIMATION_STATE_COMPONENT_ON_COMPLETED) && (USE_ANIMATION_STATE_COMPONENT_ON_COMPLETED == 1)
completed_.reset();
#endif
if (auto anim = interface_pointer_cast(GetNativeObject())) {
UnsetNativeObject();
// remove listeners.
if (OnStartedToken_) {
anim->OnStarted()->RemoveHandler(OnStartedToken_);
}
OnStartedToken_ = 0;
if (speedModifier_) {
auto attach = interface_cast(anim);
if (attach) {
attach->Detach(speedModifier_);
}
speedModifier_.reset();
}
if (OnStartedCB_) {
// does a delayed delete
OnStartedCB_->Release();
OnStartedCB_ = nullptr;
}
if (OnFinishedCB_) {
// does a delayed delete
OnFinishedCB_->Release();
OnFinishedCB_ = nullptr;
}
}
}
}
napi_value AnimationJS::GetSpeed(NapiApi::FunctionContext<>& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
float speed = 1.0;
if (speedModifier_) {
speed = speedModifier_->SpeedFactor()->GetValue();
}
return ctx.GetNumber(speed);
}
void AnimationJS::SetSpeed(NapiApi::FunctionContext& ctx)
{
if (!validateSceneRef()) {
return;
}
float speed = ctx.Arg<0>();
if (auto a = interface_cast(GetNativeObject())) {
using namespace META_NS;
if (!speedModifier_) {
speedModifier_ =
GetObjectRegistry().Create(ClassId::SpeedAnimationModifier);
interface_cast(a)->Attach(speedModifier_);
}
speedModifier_->SpeedFactor()->SetValue(speed);
}
}
napi_value AnimationJS::GetEnabled(NapiApi::FunctionContext<>& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
bool enabled { false };
if (auto a = interface_cast(GetNativeObject())) {
enabled = a->Enabled()->GetValue();
}
return ctx.GetBoolean(enabled);
}
void AnimationJS::SetEnabled(NapiApi::FunctionContext& ctx)
{
if (!validateSceneRef()) {
return;
}
bool enabled = ctx.Arg<0>();
if (auto a = interface_cast(GetNativeObject())) {
a->Enabled()->SetValue(enabled);
}
}
napi_value AnimationJS::GetDuration(NapiApi::FunctionContext<>& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
float duration = 0.0;
if (auto a = interface_cast(GetNativeObject())) {
duration = a->TotalDuration()->GetValue().ToSecondsFloat();
}
return ctx.GetNumber(duration);
}
napi_value AnimationJS::GetRunning(NapiApi::FunctionContext<>& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
bool running { false };
if (auto a = interface_cast(GetNativeObject())) {
running = a->Running()->GetValue();
}
return ctx.GetBoolean(running);
}
napi_value AnimationJS::GetProgress(NapiApi::FunctionContext<>& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
float progress = 0.0;
if (auto a = interface_cast(GetNativeObject())) {
progress = a->Progress()->GetValue();
}
return ctx.GetNumber(progress);
}
napi_value AnimationJS::OnFinished(NapiApi::FunctionContext& ctx)
{
auto func = ctx.Arg<0>();
if (!OnCompletedEvent_) {
return ctx.GetUndefined();
}
// do we have existing callback?
if (OnFinishedCB_) {
// stop listening ...
OnCompletedEvent_->RemoveHandler(OnFinishedToken_);
OnFinishedToken_ = 0;
// ... and release it
OnFinishedCB_->Release();
OnFinishedCB_ = nullptr;
}
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
// do we have a new callback?
if (func.IsDefinedAndNotNull()) {
// create handler...
OnFinishedCB_ = new OnCallJS("OnFinished", ctx.This(), func);
// ... and start listening
#if defined(USE_ANIMATION_STATE_COMPONENT_ON_COMPLETED) && (USE_ANIMATION_STATE_COMPONENT_ON_COMPLETED == 1)
auto cb = META_NS::MakeCallback([this]() {
bool completion = false;
if (!completed_) {
return;
}
completed_->GetValue().GetValue(completion);
if (completion && OnFinishedCB_) {
OnFinishedCB_->Trigger();
}
});
#else
auto cb = META_NS::MakeCallback(OnFinishedCB_, &OnCallJS::Trigger);
#endif
OnFinishedToken_ = OnCompletedEvent_->AddHandler(cb);
}
return ctx.GetUndefined();
}
napi_value AnimationJS::OnStarted(NapiApi::FunctionContext& ctx)
{
auto func = ctx.Arg<0>();
// do we have existing callback?
if (OnStartedCB_) {
// stop listening ...
if (auto a = interface_cast(GetNativeObject())) {
a->OnStarted()->RemoveHandler(OnStartedToken_);
OnStartedToken_ = 0;
}
// ... and release it
OnStartedCB_->Release();
OnStartedCB_ = nullptr;
}
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
// do we have a new callback?
if (func.IsDefinedAndNotNull()) {
// create handler...
OnStartedCB_ = new OnCallJS("OnStart", ctx.This(), func);
// ... and start listening
if (auto a = interface_cast(GetNativeObject())) {
OnStartedToken_ = a->OnStarted()->AddHandler(
META_NS::MakeCallback(OnStartedCB_, &OnCallJS::Trigger));
}
}
return ctx.GetUndefined();
}
napi_value AnimationJS::Pause(NapiApi::FunctionContext<>& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
if (auto a = interface_cast(GetNativeObject())) {
a->Pause();
}
return ctx.GetUndefined();
}
napi_value AnimationJS::Restart(NapiApi::FunctionContext<>& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
if (auto a = interface_cast(GetNativeObject())) {
a->Restart();
}
return ctx.GetUndefined();
}
napi_value AnimationJS::Seek(NapiApi::FunctionContext& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
float pos = ctx.Arg<0>();
if (auto a = interface_cast(GetNativeObject())) {
a->Seek(pos);
}
return ctx.GetUndefined();
}
napi_value AnimationJS::Start(NapiApi::FunctionContext<>& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
if (auto a = interface_cast(GetNativeObject())) {
a->Start();
}
return ctx.GetUndefined();
}
napi_value AnimationJS::Stop(NapiApi::FunctionContext<>& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
if (auto a = interface_cast(GetNativeObject())) {
a->Stop();
}
return ctx.GetUndefined();
}
napi_value AnimationJS::Finish(NapiApi::FunctionContext<>& ctx)
{
if (!validateSceneRef()) {
return ctx.GetUndefined();
}
if (auto a = interface_cast(GetNativeObject())) {
a->Finish();
}
return ctx.GetUndefined();
}