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 <scene_plugin/interface/intf_scene.h>
20#include "SceneJS.h"
21
22class OnCallJS : public ThreadSafeCallback {
23    NapiApi::StrongRef jsThis_;
24    NapiApi::StrongRef ref_;
25
26public:
27    OnCallJS(const char* name, napi_value jsThis, NapiApi::Function toCall) : ThreadSafeCallback(toCall.GetEnv(), name)
28    {
29        jsThis_ = { toCall.GetEnv(), jsThis };
30        ref_ = { toCall.GetEnv(), toCall };
31    }
32    ~OnCallJS()
33    {
34        jsThis_.Reset();
35        ref_.Reset();
36        Release();
37    }
38    void Finalize(napi_env env)
39    {
40        jsThis_.Reset();
41        ref_.Reset();
42    }
43    void Invoked(napi_env env)
44    {
45        napi_value res;
46        napi_call_function(env, jsThis_.GetValue(), ref_.GetValue(), 0, nullptr, &res);
47    }
48};
49
50void AnimationJS::Init(napi_env env, napi_value exports)
51{
52    BASE_NS::vector<napi_property_descriptor> node_props;
53    SceneResourceImpl::GetPropertyDescs(node_props);
54// Try out the helper macros.
55// Declare NAPI_API_JS_NAME to simplify the registering.
56#define NAPI_API_JS_NAME Animation
57
58    DeclareGetSet(bool, "enabled", GetEnabled, SetEnabled);
59    DeclareGet(float, "duration", GetDuration);
60    DeclareGet(bool, "running", GetRunning);
61    DeclareGet(float, "progress", GetProgress);
62    DeclareMethod("pause", Pause);
63    DeclareMethod("restart", Restart);
64    DeclareMethod("seek", Seek, float);
65    DeclareMethod("start", Start);
66    DeclareMethod("stop", Stop);
67    DeclareMethod("finish", Finish);
68    DeclareMethod("onFinished", OnFinished, NapiApi::Function);
69    DeclareMethod("onStarted", OnStarted, NapiApi::Function);
70    DeclareClass();
71#undef NAPI_API_JS_NAME
72}
73
74AnimationJS::AnimationJS(napi_env e, napi_callback_info i)
75    : BaseObject<AnimationJS>(e, i), SceneResourceImpl(SceneResourceImpl::ANIMATION)
76{
77    NapiApi::FunctionContext<NapiApi::Object> fromJs(e, i);
78    NapiApi::Object meJs(e, fromJs.This());
79    NapiApi::Object scene = fromJs.Arg<0>(); // access to owning scene...
80    scene_ = { scene };
81    if (!GetNativeMeta<SCENE_NS::IScene>(scene_.GetObject())) {
82        CORE_LOG_F("INVALID SCENE!");
83    }
84
85    auto* tro = scene.Native<TrueRootObject>();
86    auto* sceneJS = ((SceneJS*)tro->GetInstanceImpl(SceneJS::ID));
87    sceneJS->DisposeHook((uintptr_t)&scene_, meJs);
88}
89void* AnimationJS::GetInstanceImpl(uint32_t id)
90{
91    if (id == AnimationJS::ID) {
92        return this;
93    }
94    return SceneResourceImpl::GetInstanceImpl(id);
95}
96
97void AnimationJS::Finalize(napi_env env)
98{
99    DisposeNative();
100    BaseObject<AnimationJS>::Finalize(env);
101}
102AnimationJS::~AnimationJS()
103{
104    LOG_F("AnimationJS -- ");
105    DisposeNative();
106}
107
108void AnimationJS::DisposeNative()
109{
110    // do nothing for now..
111    if (!disposed_) {
112        disposed_ = true;
113
114        LOG_F("AnimationJS::DisposeNative");
115        NapiApi::Object obj = scene_.GetObject();
116        auto* tro = obj.Native<TrueRootObject>();
117        SceneJS* sceneJS;
118        if (tro) {
119            sceneJS = ((SceneJS*)tro->GetInstanceImpl(SceneJS::ID));
120            sceneJS->ReleaseDispose((uintptr_t)&scene_);
121        }
122        scene_.Reset();
123
124        // make sure we release postProc settings
125        if (auto animation = interface_pointer_cast<META_NS::IAnimation>(GetNativeObject())) {
126            // reset the native object refs
127            SetNativeObject(nullptr, false);
128            SetNativeObject(nullptr, true);
129            ExecSyncTask([this, anim = BASE_NS::move(animation)]() -> META_NS::IAny::Ptr {
130                // remove listeners.
131                if (OnStartedToken_) {
132                    anim->OnStarted()->RemoveHandler(OnStartedToken_);
133                }
134                if (OnFinishedToken_) {
135                    anim->OnFinished()->RemoveHandler(OnFinishedToken_);
136                }
137                return {};
138            });
139            if (OnStartedCB_) {
140                // does a delayed delete
141                OnStartedCB_->Release();
142                OnStartedCB_ = nullptr;
143            }
144            if (OnFinishedCB_) {
145                // does a delayed delete
146                OnFinishedCB_->Release();
147                OnFinishedCB_ = nullptr;
148            }
149        }
150    }
151}
152
153napi_value AnimationJS::GetEnabled(NapiApi::FunctionContext<>& ctx)
154{
155    bool enabled { false };
156    if (auto a = interface_cast<META_NS::IAnimation>(GetNativeObject())) {
157        ExecSyncTask([a, &enabled]() {
158            if (a) {
159                enabled = a->Enabled()->GetValue();
160            }
161            return META_NS::IAny::Ptr {};
162        });
163    }
164
165    return ctx.GetBoolean(enabled);
166}
167void AnimationJS::SetEnabled(NapiApi::FunctionContext<bool>& ctx)
168{
169    bool enabled = ctx.Arg<0>();
170    if (auto a = interface_cast<META_NS::IAnimation>(GetNativeObject())) {
171        ExecSyncTask([a, enabled]() {
172            a->Enabled()->SetValue(enabled);
173            return META_NS::IAny::Ptr {};
174        });
175    }
176}
177napi_value AnimationJS::GetDuration(NapiApi::FunctionContext<>& ctx)
178{
179    float duration = 0.0;
180    if (auto a = interface_cast<META_NS::IAnimation>(GetNativeObject())) {
181        ExecSyncTask([a, &duration]() {
182            if (a) {
183                duration = a->TotalDuration()->GetValue().ToSecondsFloat();
184            }
185            return META_NS::IAny::Ptr {};
186        });
187    }
188
189    return NapiApi::Value<float>(ctx, duration);
190}
191
192napi_value AnimationJS::GetRunning(NapiApi::FunctionContext<>& ctx)
193{
194    bool running { false };
195    if (auto a = interface_cast<META_NS::IAnimation>(GetNativeObject())) {
196        ExecSyncTask([a, &running]() {
197            if (a) {
198                running = a->Running()->GetValue();
199            }
200            return META_NS::IAny::Ptr {};
201        });
202    }
203
204    return ctx.GetBoolean(running);
205}
206napi_value AnimationJS::GetProgress(NapiApi::FunctionContext<>& ctx)
207{
208    float progress = 0.0;
209    if (auto a = interface_cast<META_NS::IAnimation>(GetNativeObject())) {
210        ExecSyncTask([a, &progress]() {
211            if (a) {
212                progress = a->Progress()->GetValue();
213            }
214            return META_NS::IAny::Ptr {};
215        });
216    }
217
218    return NapiApi::Value<float>(ctx, progress);
219}
220
221napi_value AnimationJS::OnFinished(NapiApi::FunctionContext<NapiApi::Function>& ctx)
222{
223    auto func = ctx.Arg<0>();
224    // do we have existing callback?
225    if (OnFinishedCB_) {
226        // stop listening ...
227        if (auto a = interface_cast<META_NS::IAnimation>(GetNativeObject())) {
228            ExecSyncTask([this, a]() -> META_NS::IAny::Ptr {
229                a->OnFinished()->RemoveHandler(OnFinishedToken_);
230                OnFinishedToken_ = 0;
231                return {};
232            });
233        }
234        // ... and release it
235        OnFinishedCB_->Release();
236    }
237    // do we have a new callback?
238    if (func) {
239        // create handler...
240        OnFinishedCB_ = new OnCallJS("OnFinished", ctx.This(), func);
241        // ... and start listening
242        if (auto a = interface_cast<META_NS::IAnimation>(GetNativeObject())) {
243            ExecSyncTask([this, a]() -> META_NS::IAny::Ptr {
244                OnFinishedToken_ = a->OnFinished()->AddHandler(
245                    META_NS::MakeCallback<META_NS::IOnChanged>(OnFinishedCB_, &OnCallJS::Trigger));
246                return {};
247            });
248        }
249    }
250    return ctx.GetUndefined();
251}
252
253napi_value AnimationJS::OnStarted(NapiApi::FunctionContext<NapiApi::Function>& ctx)
254{
255    auto func = ctx.Arg<0>();
256    // do we have existing callback?
257    if (OnStartedCB_) {
258        // stop listening ...
259        if (auto a = interface_cast<META_NS::IAnimation>(GetNativeObject())) {
260            ExecSyncTask([this, a]() -> META_NS::IAny::Ptr {
261                a->OnStarted()->RemoveHandler(OnStartedToken_);
262                OnStartedToken_ = 0;
263                return {};
264            });
265        }
266        // ... and release it
267        OnStartedCB_->Release();
268    }
269    // do we have a new callback?
270    if (func) {
271        // create handler...
272        OnStartedCB_ = new OnCallJS("OnStart", ctx.This(), func);
273        // ... and start listening
274        if (auto a = interface_cast<META_NS::IAnimation>(GetNativeObject())) {
275            ExecSyncTask([this, a]() -> META_NS::IAny::Ptr {
276                OnStartedToken_ = a->OnStarted()->AddHandler(
277                    META_NS::MakeCallback<META_NS::IOnChanged>(OnStartedCB_, &OnCallJS::Trigger));
278                return {};
279            });
280        }
281    }
282    return ctx.GetUndefined();
283}
284
285napi_value AnimationJS::Pause(NapiApi::FunctionContext<>& ctx)
286{
287    if (auto a = interface_cast<META_NS::IStartableAnimation>(GetNativeObject())) {
288        ExecSyncTask([a]() {
289            a->Pause();
290            return META_NS::IAny::Ptr {};
291        });
292    }
293    return ctx.GetUndefined();
294}
295napi_value AnimationJS::Restart(NapiApi::FunctionContext<>& ctx)
296{
297    if (auto a = interface_cast<META_NS::IStartableAnimation>(GetNativeObject())) {
298        ExecSyncTask([a]() {
299            a->Restart();
300            return META_NS::IAny::Ptr {};
301        });
302    }
303    return ctx.GetUndefined();
304}
305napi_value AnimationJS::Seek(NapiApi::FunctionContext<float>& ctx)
306{
307    float pos = ctx.Arg<0>();
308    if (auto a = interface_cast<META_NS::IStartableAnimation>(GetNativeObject())) {
309        ExecSyncTask([a, pos]() {
310            a->Seek(pos);
311            return META_NS::IAny::Ptr {};
312        });
313    }
314    return ctx.GetUndefined();
315}
316napi_value AnimationJS::Start(NapiApi::FunctionContext<>& ctx)
317{
318    if (auto a = interface_cast<META_NS::IStartableAnimation>(GetNativeObject())) {
319        ExecSyncTask([a]() {
320            a->Start();
321            return META_NS::IAny::Ptr {};
322        });
323    }
324    return ctx.GetUndefined();
325}
326
327napi_value AnimationJS::Stop(NapiApi::FunctionContext<>& ctx)
328{
329    if (auto a = interface_cast<META_NS::IStartableAnimation>(GetNativeObject())) {
330        ExecSyncTask([a]() {
331            a->Stop();
332            return META_NS::IAny::Ptr {};
333        });
334    }
335    return ctx.GetUndefined();
336}
337napi_value AnimationJS::Finish(NapiApi::FunctionContext<>& ctx)
338{
339    if (auto a = interface_cast<META_NS::IStartableAnimation>(GetNativeObject())) {
340        ExecSyncTask([a]() {
341            a->Finish();
342            return META_NS::IAny::Ptr {};
343        });
344    }
345    return ctx.GetUndefined();
346}