1 /*
2 * Copyright (C) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 #include "AnimationJS.h"
16
17 #include <meta/api/make_callback.h>
18 #include <meta/interface/animation/intf_animation.h>
19 #include <meta/interface/animation/builtin_animations.h>
20 #include <meta/interface/intf_attach.h>
21 #include <scene/interface/intf_scene.h>
22 #include <scene/ext/intf_ecs_object_access.h>
23
24 #include "SceneJS.h"
25
26 class OnCallJS : public ThreadSafeCallback {
27 NapiApi::StrongRef jsThis_;
28 NapiApi::StrongRef ref_;
29
30 public:
OnCallJS(const char * name,NapiApi::Object jsThis,NapiApi::Function toCall)31 OnCallJS(const char* name, NapiApi::Object jsThis, NapiApi::Function toCall)
32 : ThreadSafeCallback(toCall.GetEnv(), name), jsThis_(jsThis), ref_(toCall.GetEnv(), toCall)
33 {}
~OnCallJS()34 ~OnCallJS()
35 {
36 jsThis_.Reset();
37 ref_.Reset();
38 Release();
39 }
Finalize(napi_env env)40 void Finalize(napi_env env)
41 {
42 jsThis_.Reset();
43 ref_.Reset();
44 }
Invoked(napi_env env)45 void Invoked(napi_env env)
46 {
47 napi_value res;
48 napi_call_function(env, jsThis_.GetValue(), ref_.GetValue(), 0, nullptr, &res);
49 }
50 };
51
Init(napi_env env,napi_value exports)52 void AnimationJS::Init(napi_env env, napi_value exports)
53 {
54 BASE_NS::vector<napi_property_descriptor> node_props;
55 SceneResourceImpl::GetPropertyDescs(node_props);
56 // Try out the helper macros.
57 // Declare NAPI_API_JS_NAME to simplify the registering.
58 #define NAPI_API_JS_NAME Animation
59
60 DeclareGetSet(bool, "enabled", GetEnabled, SetEnabled);
61 DeclareGetSet(float, "speed", GetSpeed, SetSpeed);
62 DeclareGet(float, "duration", GetDuration);
63 DeclareGet(bool, "running", GetRunning);
64 DeclareGet(float, "progress", GetProgress);
65 DeclareMethod("pause", Pause);
66 DeclareMethod("restart", Restart);
67 DeclareMethod("seek", Seek, float);
68 DeclareMethod("start", Start);
69 DeclareMethod("stop", Stop);
70 DeclareMethod("finish", Finish);
71 DeclareMethod("onFinished", OnFinished, NapiApi::Function);
72 DeclareMethod("onStarted", OnStarted, NapiApi::Function);
73
74 DeclareClass();
75 #undef NAPI_API_JS_NAME
76 }
77
AnimationJS(napi_env e,napi_callback_info i)78 AnimationJS::AnimationJS(napi_env e, napi_callback_info i)
79 : BaseObject(e, i), SceneResourceImpl(SceneResourceImpl::ANIMATION)
80 {
81 NapiApi::FunctionContext<NapiApi::Object, NapiApi::Object> fromJs(e, i);
82 NapiApi::Object meJs(fromJs.This());
83 NapiApi::Object scene = fromJs.Arg<0>(); // access to owning scene...
84 NapiApi::Object args = fromJs.Arg<1>(); // access to params.
85 scene_ = { scene };
86 if (!scene_.GetObject().GetNative<SCENE_NS::IScene>()) {
87 LOG_F("INVALID SCENE!");
88 }
89
90 if (const auto sceneJS = scene_.GetObject().GetJsWrapper<SceneJS>()) {
91 sceneJS->DisposeHook(reinterpret_cast<uintptr_t>(&scene_), meJs);
92 }
93
94 META_NS::IAnimation::Ptr anim;
95 if (args) {
96 using namespace META_NS;
97 anim = interface_pointer_cast<IAnimation>(GetNativeObject());
98 if (anim) {
99 // check if there is a speed controller already.(and use that)
100 auto attachments = interface_cast<META_NS::IAttach>(anim)->GetAttachments();
101 for (auto at : attachments) {
102 if (interface_cast<AnimationModifiers::ISpeed>(at)) {
103 // yes.. (expect at most one)
104 speedModifier_ = interface_pointer_cast<AnimationModifiers::ISpeed>(at);
105 break;
106 }
107 }
108 }
109 }
110 if (anim) {
111 #if defined(USE_ANIMATION_STATE_COMPONENT_ON_COMPLETED) && (USE_ANIMATION_STATE_COMPONENT_ON_COMPLETED == 1)
112 using namespace SCENE_NS;
113 auto acc = interface_cast<IEcsObjectAccess>(anim);
114 IEcsObject::Ptr ecsObj;
115 if ((acc) && (ecsObj = acc->GetEcsObject())) {
116 completed_ = ecsObj->CreateProperty("AnimationStateComponent.completed").GetResult();
117 if (completed_) {
118 OnCompletedEvent_ = completed_->OnChanged();
119 }
120 }
121 #else
122 // Use IAnimation OnFinished to trigger the animation ends (has a bug)
123 OnCompletedEvent_ = anim->OnFinished();
124 #endif
125 }
126 }
GetInstanceImpl(uint32_t id)127 void* AnimationJS::GetInstanceImpl(uint32_t id)
128 {
129 if (id == AnimationJS::ID)
130 return this;
131 return SceneResourceImpl::GetInstanceImpl(id);
132 }
133
Finalize(napi_env env)134 void AnimationJS::Finalize(napi_env env)
135 {
136 DisposeNative(scene_.GetObject().GetJsWrapper<SceneJS>());
137 BaseObject::Finalize(env);
138 }
~AnimationJS()139 AnimationJS::~AnimationJS()
140 {
141 LOG_V("AnimationJS -- ");
142 DisposeNative(nullptr);
143 }
144
DisposeNative(void *)145 void AnimationJS::DisposeNative(void*)
146 {
147 // do nothing for now..
148 if (!disposed_) {
149 disposed_ = true;
150
151 LOG_V("AnimationJS::DisposeNative");
152 if (const auto sceneJS = scene_.GetObject().GetJsWrapper<SceneJS>()) {
153 sceneJS->ReleaseDispose(reinterpret_cast<uintptr_t>(&scene_));
154 }
155 scene_.Reset();
156
157 // release all the native resources (callbacks, modifiers, properties)
158 if (OnCompletedEvent_ && OnFinishedToken_) {
159 OnCompletedEvent_->RemoveHandler(OnFinishedToken_);
160 }
161 OnCompletedEvent_ = {};
162 OnFinishedToken_ = 0;
163 #if defined(USE_ANIMATION_STATE_COMPONENT_ON_COMPLETED) && (USE_ANIMATION_STATE_COMPONENT_ON_COMPLETED == 1)
164 completed_.reset();
165 #endif
166 if (auto anim = interface_pointer_cast<META_NS::IAnimation>(GetNativeObject())) {
167 UnsetNativeObject();
168 // remove listeners.
169 if (OnStartedToken_) {
170 anim->OnStarted()->RemoveHandler(OnStartedToken_);
171 }
172 OnStartedToken_ = 0;
173 if (speedModifier_) {
174 auto attach = interface_cast<META_NS::IAttach>(anim);
175 if (attach) {
176 attach->Detach(speedModifier_);
177 }
178 speedModifier_.reset();
179 }
180 if (OnStartedCB_) {
181 // does a delayed delete
182 OnStartedCB_->Release();
183 OnStartedCB_ = nullptr;
184 }
185 if (OnFinishedCB_) {
186 // does a delayed delete
187 OnFinishedCB_->Release();
188 OnFinishedCB_ = nullptr;
189 }
190 }
191 }
192 }
GetSpeed(NapiApi::FunctionContext<> & ctx)193 napi_value AnimationJS::GetSpeed(NapiApi::FunctionContext<>& ctx)
194 {
195 if (!validateSceneRef()) {
196 return ctx.GetUndefined();
197 }
198
199 float speed = 1.0;
200 if (speedModifier_) {
201 speed = speedModifier_->SpeedFactor()->GetValue();
202 }
203
204 return ctx.GetNumber(speed);
205 }
206
SetSpeed(NapiApi::FunctionContext<float> & ctx)207 void AnimationJS::SetSpeed(NapiApi::FunctionContext<float>& ctx)
208 {
209 if (!validateSceneRef()) {
210 return;
211 }
212 float speed = ctx.Arg<0>();
213 if (auto a = interface_cast<META_NS::IAnimation>(GetNativeObject())) {
214 using namespace META_NS;
215 if (!speedModifier_) {
216 speedModifier_ =
217 GetObjectRegistry().Create<AnimationModifiers::ISpeed>(ClassId::SpeedAnimationModifier);
218 interface_cast<IAttach>(a)->Attach(speedModifier_);
219 }
220 speedModifier_->SpeedFactor()->SetValue(speed);
221 }
222 }
223
GetEnabled(NapiApi::FunctionContext<> & ctx)224 napi_value AnimationJS::GetEnabled(NapiApi::FunctionContext<>& ctx)
225 {
226 if (!validateSceneRef()) {
227 return ctx.GetUndefined();
228 }
229
230 bool enabled { false };
231 if (auto a = interface_cast<META_NS::IAnimation>(GetNativeObject())) {
232 enabled = a->Enabled()->GetValue();
233 }
234 return ctx.GetBoolean(enabled);
235 }
SetEnabled(NapiApi::FunctionContext<bool> & ctx)236 void AnimationJS::SetEnabled(NapiApi::FunctionContext<bool>& ctx)
237 {
238 if (!validateSceneRef()) {
239 return;
240 }
241 bool enabled = ctx.Arg<0>();
242 if (auto a = interface_cast<META_NS::IAnimation>(GetNativeObject())) {
243 a->Enabled()->SetValue(enabled);
244 }
245 }
GetDuration(NapiApi::FunctionContext<> & ctx)246 napi_value AnimationJS::GetDuration(NapiApi::FunctionContext<>& ctx)
247 {
248 if (!validateSceneRef()) {
249 return ctx.GetUndefined();
250 }
251 float duration = 0.0;
252 if (auto a = interface_cast<META_NS::IAnimation>(GetNativeObject())) {
253 duration = a->TotalDuration()->GetValue().ToSecondsFloat();
254 }
255
256 return ctx.GetNumber(duration);
257 }
258
GetRunning(NapiApi::FunctionContext<> & ctx)259 napi_value AnimationJS::GetRunning(NapiApi::FunctionContext<>& ctx)
260 {
261 if (!validateSceneRef()) {
262 return ctx.GetUndefined();
263 }
264 bool running { false };
265 if (auto a = interface_cast<META_NS::IAnimation>(GetNativeObject())) {
266 running = a->Running()->GetValue();
267 }
268 return ctx.GetBoolean(running);
269 }
GetProgress(NapiApi::FunctionContext<> & ctx)270 napi_value AnimationJS::GetProgress(NapiApi::FunctionContext<>& ctx)
271 {
272 if (!validateSceneRef()) {
273 return ctx.GetUndefined();
274 }
275 float progress = 0.0;
276 if (auto a = interface_cast<META_NS::IAnimation>(GetNativeObject())) {
277 progress = a->Progress()->GetValue();
278 }
279 return ctx.GetNumber(progress);
280 }
281
OnFinished(NapiApi::FunctionContext<NapiApi::Function> & ctx)282 napi_value AnimationJS::OnFinished(NapiApi::FunctionContext<NapiApi::Function>& ctx)
283 {
284 auto func = ctx.Arg<0>();
285 if (!OnCompletedEvent_) {
286 return ctx.GetUndefined();
287 }
288 // do we have existing callback?
289 if (OnFinishedCB_) {
290 // stop listening ...
291 OnCompletedEvent_->RemoveHandler(OnFinishedToken_);
292 OnFinishedToken_ = 0;
293 // ... and release it
294 OnFinishedCB_->Release();
295 OnFinishedCB_ = nullptr;
296 }
297 if (!validateSceneRef()) {
298 return ctx.GetUndefined();
299 }
300
301 // do we have a new callback?
302 if (func.IsDefinedAndNotNull()) {
303 // create handler...
304 OnFinishedCB_ = new OnCallJS("OnFinished", ctx.This(), func);
305 // ... and start listening
306 #if defined(USE_ANIMATION_STATE_COMPONENT_ON_COMPLETED) && (USE_ANIMATION_STATE_COMPONENT_ON_COMPLETED == 1)
307 auto cb = META_NS::MakeCallback<META_NS::IOnChanged>([this]() {
308 bool completion = false;
309 if (!completed_) {
310 return;
311 }
312 completed_->GetValue().GetValue(completion);
313 if (completion && OnFinishedCB_) {
314 OnFinishedCB_->Trigger();
315 }
316 });
317 #else
318 auto cb = META_NS::MakeCallback<META_NS::IOnChanged>(OnFinishedCB_, &OnCallJS::Trigger);
319 #endif
320 OnFinishedToken_ = OnCompletedEvent_->AddHandler(cb);
321 }
322 return ctx.GetUndefined();
323 }
324
OnStarted(NapiApi::FunctionContext<NapiApi::Function> & ctx)325 napi_value AnimationJS::OnStarted(NapiApi::FunctionContext<NapiApi::Function>& ctx)
326 {
327 auto func = ctx.Arg<0>();
328 // do we have existing callback?
329 if (OnStartedCB_) {
330 // stop listening ...
331 if (auto a = interface_cast<META_NS::IAnimation>(GetNativeObject())) {
332 a->OnStarted()->RemoveHandler(OnStartedToken_);
333 OnStartedToken_ = 0;
334 }
335 // ... and release it
336 OnStartedCB_->Release();
337 OnStartedCB_ = nullptr;
338 }
339 if (!validateSceneRef()) {
340 return ctx.GetUndefined();
341 }
342 // do we have a new callback?
343 if (func.IsDefinedAndNotNull()) {
344 // create handler...
345 OnStartedCB_ = new OnCallJS("OnStart", ctx.This(), func);
346 // ... and start listening
347 if (auto a = interface_cast<META_NS::IAnimation>(GetNativeObject())) {
348 OnStartedToken_ = a->OnStarted()->AddHandler(
349 META_NS::MakeCallback<META_NS::IOnChanged>(OnStartedCB_, &OnCallJS::Trigger));
350 }
351 }
352 return ctx.GetUndefined();
353 }
354
Pause(NapiApi::FunctionContext<> & ctx)355 napi_value AnimationJS::Pause(NapiApi::FunctionContext<>& ctx)
356 {
357 if (!validateSceneRef()) {
358 return ctx.GetUndefined();
359 }
360
361 if (auto a = interface_cast<META_NS::IStartableAnimation>(GetNativeObject())) {
362 a->Pause();
363 }
364 return ctx.GetUndefined();
365 }
Restart(NapiApi::FunctionContext<> & ctx)366 napi_value AnimationJS::Restart(NapiApi::FunctionContext<>& ctx)
367 {
368 if (!validateSceneRef()) {
369 return ctx.GetUndefined();
370 }
371 if (auto a = interface_cast<META_NS::IStartableAnimation>(GetNativeObject())) {
372 a->Restart();
373 }
374 return ctx.GetUndefined();
375 }
Seek(NapiApi::FunctionContext<float> & ctx)376 napi_value AnimationJS::Seek(NapiApi::FunctionContext<float>& ctx)
377 {
378 if (!validateSceneRef()) {
379 return ctx.GetUndefined();
380 }
381 float pos = ctx.Arg<0>();
382 if (auto a = interface_cast<META_NS::IStartableAnimation>(GetNativeObject())) {
383 a->Seek(pos);
384 }
385 return ctx.GetUndefined();
386 }
Start(NapiApi::FunctionContext<> & ctx)387 napi_value AnimationJS::Start(NapiApi::FunctionContext<>& ctx)
388 {
389 if (!validateSceneRef()) {
390 return ctx.GetUndefined();
391 }
392 if (auto a = interface_cast<META_NS::IStartableAnimation>(GetNativeObject())) {
393 a->Start();
394 }
395 return ctx.GetUndefined();
396 }
397
Stop(NapiApi::FunctionContext<> & ctx)398 napi_value AnimationJS::Stop(NapiApi::FunctionContext<>& ctx)
399 {
400 if (!validateSceneRef()) {
401 return ctx.GetUndefined();
402 }
403 if (auto a = interface_cast<META_NS::IStartableAnimation>(GetNativeObject())) {
404 a->Stop();
405 }
406 return ctx.GetUndefined();
407 }
Finish(NapiApi::FunctionContext<> & ctx)408 napi_value AnimationJS::Finish(NapiApi::FunctionContext<>& ctx)
409 {
410 if (!validateSceneRef()) {
411 return ctx.GetUndefined();
412 }
413 if (auto a = interface_cast<META_NS::IStartableAnimation>(GetNativeObject())) {
414 a->Finish();
415 }
416 return ctx.GetUndefined();
417 }
418