[NUI] Clear old resources cached data
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / BaseComponents / LottieAnimationView.cs
1 /*
2  * Copyright(c) 2020 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 using global::System;
19 using global::System.Runtime.InteropServices;
20 using System.ComponentModel;
21 using System.Collections.Generic;
22 using System.Globalization;
23
24 namespace Tizen.NUI.BaseComponents
25 {
26     /// <summary>
27     /// LottieAnimationView renders an animated vector image (Lottie file).
28     /// </summary>
29     /// <since_tizen> 7 </since_tizen>
30     public partial class LottieAnimationView : ImageView
31     {
32
33         #region Constructor, Destructor, Dispose
34         /// <summary>
35         /// LottieAnimationView constructor
36         /// </summary>
37         /// <param name="scale">The factor of scaling image, default : 1.0f</param>
38         /// <param name="shown">false : not displayed (hidden), true : displayed (shown), default : true</param>
39         /// <remarks>
40         /// If the shown parameter is false, the animation is not visible even if the LottieAnimationView instance is created.
41         /// </remarks>
42         /// <example>
43         /// <code>
44         /// LottieAnimationView myLottie = new LottieAnimationView();
45         /// LottieAnimationView myLottie2 = new LottieAnimationView(2.0f);
46         /// LottieAnimationView myLottie3 = new LottieAnimationView(1.0f, false);
47         /// </code>
48         /// </example>
49         /// <since_tizen> 7 </since_tizen>
50         public LottieAnimationView(float scale = 1.0f, bool shown = true) : base()
51         {
52             ActionPlay = Interop.LottieAnimationView.AnimatedVectorImageVisualActionPlayGet();
53             ActionPause = Interop.LottieAnimationView.AnimatedVectorImageVisualActionPauseGet();
54             ActionStop = Interop.LottieAnimationView.AnimatedVectorImageVisualActionStopGet();
55
56             NUILog.Debug($"< constructor GetId={GetId()} >");
57             currentStates.url = "";
58             currentStates.loopCount = 1;
59             currentStates.loopMode = LoopingModeType.Restart;
60             currentStates.stopEndAction = StopBehaviorType.CurrentFrame;
61             currentStates.framePlayRangeMin = -1;
62             currentStates.framePlayRangeMax = -1;
63             currentStates.totalFrame = -1;
64             currentStates.scale = scale;
65             currentStates.redrawInScalingDown = true;
66
67             // Set changed flag as true when initalized state.
68             // After some properties change, LottieAnimationView.UpdateImage will apply these inital values.
69             currentStates.changed = true;
70             SetVisible(shown);
71         }
72
73         /// <summary>
74         /// Dispose(DisposeTypes type)
75         /// </summary>
76         /// <param name="type"></param>
77         // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
78         [EditorBrowsable(EditorBrowsableState.Never)]
79         protected override void Dispose(DisposeTypes type)
80         {
81             if (disposed)
82             {
83                 return;
84             }
85
86             CleanCallbackDictionaries();
87
88             //Release your own unmanaged resources here.
89             //You should not access any managed member here except static instance.
90             //because the execution order of Finalizes is non-deterministic.
91
92             //disconnect event signal
93             if (finishedEventHandler != null && visualEventSignalCallback != null)
94             {
95                 using VisualEventSignal visualEvent = VisualEventSignal();
96                 visualEvent.Disconnect(visualEventSignalCallback);
97                 finishedEventHandler = null;
98                 NUILog.Debug($"disconnect event signal");
99             }
100
101             base.Dispose(type);
102         }
103
104         // This is used for internal purpose. hidden API.
105         [EditorBrowsable(EditorBrowsableState.Never)]
106         protected override void Dispose(bool disposing)
107         {
108             CleanCallbackDictionaries();
109             base.Dispose(disposing);
110         }
111         #endregion Constructor, Destructor, Dispose
112
113
114         #region Property
115         /// <summary>
116         /// Set or Get resource URL of Lottie file.
117         /// </summary>
118         /// <since_tizen> 7 </since_tizen>
119         public string URL
120         {
121             get
122             {
123                 return GetValue(URLProperty) as string;
124             }
125             set
126             {
127                 SetValue(URLProperty, value);
128                 NotifyPropertyChanged();
129             }
130         }
131
132         private string InternalURL
133         {
134             set
135             {
136                 // Reset cached infomations.
137                 currentStates.contentInfo = null;
138                 currentStates.mark1 = null;
139                 currentStates.mark2 = null;
140                 currentStates.framePlayRangeMin = -1;
141                 currentStates.framePlayRangeMax = -1;
142                 currentStates.totalFrame = -1;
143
144                 string ret = (value == null ? "" : value);
145                 currentStates.url = ret;
146
147                 NUILog.Debug($"<[{GetId()}]SET url={currentStates.url}");
148
149                 // TODO : Could create new Image without additional creation?
150                 using PropertyMap map = new PropertyMap();
151                 using PropertyValue type = new PropertyValue((int)Visual.Type.AnimatedVectorImage);
152                 using PropertyValue url = new PropertyValue(currentStates.url);
153                 using PropertyValue loopCnt = new PropertyValue(currentStates.loopCount);
154                 using PropertyValue stopAction = new PropertyValue((int)currentStates.stopEndAction);
155                 using PropertyValue loopMode = new PropertyValue((int)currentStates.loopMode);
156                 using PropertyValue redrawInScalingDown = new PropertyValue(currentStates.redrawInScalingDown);
157
158                 map.Add(Visual.Property.Type, type)
159                     .Add(ImageVisualProperty.URL, url)
160                     .Add(ImageVisualProperty.LoopCount, loopCnt)
161                     .Add(ImageVisualProperty.StopBehavior, stopAction)
162                     .Add(ImageVisualProperty.LoopingMode, loopMode)
163                     .Add(ImageVisualProperty.RedrawInScalingDown, redrawInScalingDown);
164                 Image = map;
165
166                 // All states applied well.
167                 currentStates.changed = false;
168
169                 if (currentStates.scale != 1.0f)
170                 {
171                     Scale = new Vector3(currentStates.scale, currentStates.scale, currentStates.scale);
172                 }
173                 NUILog.Debug($"<[{GetId()}]>");
174             }
175             get
176             {
177                 string ret = currentStates.url;
178                 NUILog.Debug($"<[{GetId()}] GET");
179                 NUILog.Debug($"gotten url={ret} >");
180                 return ret;
181             }
182         }
183
184         /// <summary>
185         /// Gets the playing state
186         /// </summary>
187         /// <since_tizen> 7 </since_tizen>
188         public PlayStateType PlayState
189         {
190             get
191             {
192                 NUILog.Debug($"< Get!");
193
194                 int ret = 0;
195                 Interop.View.InternalRetrievingVisualPropertyInt(this.SwigCPtr, ImageView.Property.IMAGE, ImageVisualProperty.PlayState, out ret);
196
197                 currentStates.playState = (PlayStateType)ret;
198                 NUILog.Debug($"gotten play state={currentStates.playState} >");
199
200                 return currentStates.playState;
201             }
202         }
203
204         /// <summary>
205         /// Get the number of total frames
206         /// </summary>
207         /// <since_tizen> 7 </since_tizen>
208         public int TotalFrame
209         {
210             get
211             {
212                 int ret = currentStates.totalFrame;
213                 if (ret == -1)
214                 {
215                     Interop.View.InternalRetrievingVisualPropertyInt(this.SwigCPtr, ImageView.Property.IMAGE, ImageVisualProperty.TotalFrameNumber, out ret);
216
217                     currentStates.totalFrame = ret;
218                     NUILog.Debug($"TotalFrameNumber get! ret={ret}");
219                 }
220                 return ret;
221             }
222         }
223
224         /// <summary>
225         /// Set or get the current frame. When setting a specific frame, it is displayed as a still image.
226         /// </summary>
227         /// <remarks>
228         /// Gets the value set by a user. If the setting value is out-ranged, it is reset as a minimum frame or a maximum frame.
229         /// </remarks>
230         /// <example>
231         /// We assume that the animation in myLottie.json file has 100 frames originally. If so, its frame index will be 0 - 99.
232         /// <code>
233         /// LottieAnimationView myLottie = new LottieAnimationView();
234         /// myLottie.URL = Tizen.Applications.Application.Current.DirectoryInfo.Resource + "myLottie.json"; //myLottie.json's total frame is 100 (frame: 0~99)
235         /// NUIApplication.GetDefaultWindow().GetDefaultLayer().Add(myLottie);
236         /// myLottie.CurrentFrame = 200; //display 99 frame
237         /// myLottie.SetMinMaxFrame(10, 20);
238         /// myLottie.CurrentFrame = 15; //display 15 frame
239         /// myLottie.CurrentFrame = 50; //display 20 frame, because the MinMax is set (10,20) above
240         /// </code>
241         /// </example>
242         /// <since_tizen> 7 </since_tizen>
243         public int CurrentFrame
244         {
245             get
246             {
247                 return (int)GetValue(CurrentFrameProperty);
248             }
249             set
250             {
251                 SetValue(CurrentFrameProperty, value);
252                 NotifyPropertyChanged();
253             }
254         }
255
256         private int InternalCurrentFrame
257         {
258             set
259             {
260                 NUILog.Debug($"<[{GetId()}]SET frame={value}>");
261
262                 Interop.View.DoActionWithSingleIntAttributes(this.SwigCPtr, ImageView.Property.IMAGE, ActionJumpTo, value);
263             }
264             get
265             {
266                 int ret = 0;
267
268                 Interop.View.InternalRetrievingVisualPropertyInt(this.SwigCPtr, ImageView.Property.IMAGE, ImageVisualProperty.CurrentFrameNumber, out ret);
269
270                 NUILog.Debug($"CurrentFrameNumber get! val={ret}");
271                 return ret;
272             }
273         }
274
275         /// <summary>
276         /// Sets or gets the looping mode of Lottie animation.
277         /// </summary>
278         /// <since_tizen> 7 </since_tizen>
279         public LoopingModeType LoopingMode
280         {
281             get
282             {
283                 return (LoopingModeType)GetValue(LoopingModeProperty);
284             }
285             set
286             {
287                 SetValue(LoopingModeProperty, value);
288                 NotifyPropertyChanged();
289             }
290         }
291
292         private LoopingModeType InternalLoopingMode
293         {
294             set
295             {
296                 if (currentStates.loopMode != (LoopingModeType)value)
297                 {
298                     currentStates.changed = true;
299                     currentStates.loopMode = (LoopingModeType)value;
300
301                     NUILog.Debug($"<[{GetId()}] SET loopMode={currentStates.loopMode}>");
302
303                     Interop.View.InternalUpdateVisualPropertyInt(this.SwigCPtr, ImageView.Property.IMAGE, ImageVisualProperty.LoopingMode, (int)currentStates.loopMode);
304                 }
305             }
306             get
307             {
308                 NUILog.Debug($"LoopMode get! {currentStates.loopMode}");
309                 return currentStates.loopMode;
310             }
311         }
312
313         /// <summary>
314         /// Sets or gets the loop count.
315         /// </summary>
316         /// <remarks>
317         /// The minus value means the infinite loop count.
318         /// </remarks>
319         /// <example>
320         /// <code>
321         /// LottieAnimationView myLottie = new LottieAnimationView();
322         /// myLottie.URL = Tizen.Applications.Application.Current.DirectoryInfo.Resource + "myLottie.json"; //myLottie.json's total frame is 100 (frame: 0~99)
323         /// NUIApplication.GetDefaultWindow().GetDefaultLayer().Add(myLottie);
324         /// myLottie.LoopCount = -1; //infinite loop
325         /// myLottie.Play();
326         /// myLottie.Stop(); //it plays continuously unless Stop() is called
327         /// myLottie.LoopCount = 2;
328         /// myLottie.Play(); //it plays only 2 times and stops automatically
329         /// </code>
330         /// </example>
331         /// <since_tizen> 7 </since_tizen>
332         public int LoopCount
333         {
334             get
335             {
336                 return (int)GetValue(LoopCountProperty);
337             }
338             set
339             {
340                 SetValue(LoopCountProperty, value);
341                 NotifyPropertyChanged();
342             }
343         }
344
345         private int InternalLoopCount
346         {
347             set
348             {
349                 if (currentStates.loopCount != value)
350                 {
351                     currentStates.changed = true;
352                     currentStates.loopCount = value;
353
354                     NUILog.Debug($"<[{GetId()}]SET currentStates.loopCount={currentStates.loopCount}>");
355
356                     Interop.View.InternalUpdateVisualPropertyInt(this.SwigCPtr, ImageView.Property.IMAGE, ImageVisualProperty.LoopCount, currentStates.loopCount);
357                 }
358             }
359             get
360             {
361                 NUILog.Debug($"LoopCount get! {currentStates.loopCount}");
362                 return currentStates.loopCount;
363             }
364         }
365
366         /// <summary>
367         /// Sets or gets the stop behavior.
368         /// </summary>
369         /// <since_tizen> 7 </since_tizen>
370         public StopBehaviorType StopBehavior
371         {
372             get
373             {
374                 return (StopBehaviorType)GetValue(StopBehaviorProperty);
375             }
376             set
377             {
378                 SetValue(StopBehaviorProperty, value);
379                 NotifyPropertyChanged();
380             }
381         }
382
383         private StopBehaviorType InternalStopBehavior
384         {
385             set
386             {
387                 if (currentStates.stopEndAction != (StopBehaviorType)value)
388                 {
389                     currentStates.changed = true;
390                     currentStates.stopEndAction = (StopBehaviorType)value;
391
392                     NUILog.Debug($"<[{GetId()}]SET val={currentStates.stopEndAction}>");
393
394                     Interop.View.InternalUpdateVisualPropertyInt(this.SwigCPtr, ImageView.Property.IMAGE, ImageVisualProperty.StopBehavior, (int)currentStates.stopEndAction);
395                 }
396             }
397             get
398             {
399                 NUILog.Debug($"StopBehavior get! {currentStates.stopEndAction}");
400                 return currentStates.stopEndAction;
401             }
402         }
403
404         /// <summary>
405         /// Whether to redraw the image when the visual is scaled down.
406         /// </summary>
407         /// <remarks>
408         /// Inhouse API.
409         /// It is used in the AnimatedVectorImageVisual.The default is true.
410         /// </remarks>
411         [EditorBrowsable(EditorBrowsableState.Never)]
412         public bool RedrawInScalingDown
413         {
414             get
415             {
416                 return (bool)GetValue(RedrawInScalingDownProperty);
417             }
418             set
419             {
420                 SetValue(RedrawInScalingDownProperty, value);
421                 NotifyPropertyChanged();
422             }
423         }
424
425         private bool InternalRedrawInScalingDown
426         {
427             set
428             {
429                 if (currentStates.redrawInScalingDown != value)
430                 {
431                     currentStates.changed = true;
432                     currentStates.redrawInScalingDown = value;
433
434                     NUILog.Debug($"<[{GetId()}]SET currentStates.redrawInScalingDown={currentStates.redrawInScalingDown}>");
435
436                     Interop.View.InternalUpdateVisualPropertyBool(this.SwigCPtr, ImageView.Property.IMAGE, ImageVisualProperty.RedrawInScalingDown, currentStates.redrawInScalingDown);
437                 }
438             }
439             get
440             {
441                 NUILog.Debug($"RedrawInScalingDown get! {currentStates.redrawInScalingDown}");
442                 return currentStates.redrawInScalingDown;
443             }
444         }
445
446
447         /// <summary>
448         /// Actions property value to Jump to the specified frame.
449         /// This property can be redefined by child class if it use different value.
450         /// </summary>
451         [EditorBrowsable(EditorBrowsableState.Never)]
452         protected int ActionJumpTo { get; set; } = Interop.LottieAnimationView.AnimatedVectorImageVisualActionJumpToGet();
453
454         // This is used for internal purpose. hidden API.
455         [EditorBrowsable(EditorBrowsableState.Never)]
456         protected int SetDynamicProperty => ActionJumpTo + 1;
457         #endregion Property
458
459
460         #region Method
461         /// <summary>
462         /// Set the minimum and the maximum frame.
463         /// </summary>
464         /// <param name="minFrame">minimum frame</param>
465         /// <param name="maxFrame">maximum frame</param>
466         /// <since_tizen> 7 </since_tizen>
467         public void SetMinMaxFrame(int minFrame, int maxFrame)
468         {
469             if (currentStates.framePlayRangeMin != minFrame || currentStates.framePlayRangeMax != maxFrame)
470             {
471                 NUILog.Debug($"< [{GetId()}] SetPlayRange({minFrame}, {maxFrame})");
472                 currentStates.changed = true;
473                 currentStates.framePlayRangeMin = minFrame;
474                 currentStates.framePlayRangeMax = maxFrame;
475
476                 Interop.View.InternalUpdateVisualPropertyIntPair(this.SwigCPtr, ImageView.Property.IMAGE, ImageVisualProperty.PlayRange, currentStates.framePlayRangeMin, currentStates.framePlayRangeMax);
477
478                 NUILog.Debug($"  [{GetId()}] currentStates.min:({currentStates.framePlayRangeMin}, max:{currentStates.framePlayRangeMax})>");
479             }
480         }
481
482         /// <summary>
483         /// Play Animation.
484         /// </summary>
485         /// <since_tizen> 7 </since_tizen>
486         public new void Play()
487         {
488             NUILog.Debug($"<[{GetId()}] Play()");
489             debugPrint();
490             base.Play();
491             NUILog.Debug($"[{GetId()}]>");
492         }
493
494         /// <summary>
495         /// Pause Animation.
496         /// </summary>
497         /// <since_tizen> 7 </since_tizen>
498         public new void Pause()
499         {
500             NUILog.Debug($"<[{GetId()}] Pause()>");
501             debugPrint();
502             base.Pause();
503             NUILog.Debug($"[{GetId()}]>");
504         }
505
506         /// <summary>
507         /// Stop Animation.
508         /// </summary>
509         /// <since_tizen> 7 </since_tizen>
510         public new void Stop()
511         {
512             NUILog.Debug($"<[{GetId()}] Stop()");
513             debugPrint();
514             base.Stop();
515             NUILog.Debug($"[{GetId()}]>");
516         }
517
518         /// <summary>
519         /// Get the list of layers' information such as the start frame and the end frame in the Lottie file.
520         /// </summary>
521         /// <returns>List of Tuple (string of layer name, integer of start frame, integer of end frame)</returns>
522         /// <since_tizen> 7 </since_tizen>
523         public List<Tuple<string, int, int>> GetContentInfo()
524         {
525             if (currentStates.contentInfo != null)
526             {
527                 return currentStates.contentInfo;
528             }
529
530             NUILog.Debug($"<");
531
532             PropertyMap imageMap = base.Image;
533             if (imageMap != null)
534             {
535                 PropertyValue val = imageMap.Find(ImageVisualProperty.ContentInfo);
536                 PropertyMap contentMap = new PropertyMap();
537                 if (val?.Get(ref contentMap) == true)
538                 {
539                     currentStates.contentInfo = new List<Tuple<string, int, int>>();
540                     for (uint i = 0; i < contentMap.Count(); i++)
541                     {
542                         using PropertyKey propertyKey = contentMap.GetKeyAt(i);
543                         string key = propertyKey.StringKey;
544
545                         using PropertyValue arrVal = contentMap.GetValue(i);
546                         using PropertyArray arr = new PropertyArray();
547                         if (arrVal.Get(arr))
548                         {
549                             int startFrame = -1;
550                             using PropertyValue start = arr.GetElementAt(0);
551                             start?.Get(out startFrame);
552
553                             int endFrame = -1;
554                             using PropertyValue end = arr.GetElementAt(1);
555                             end?.Get(out endFrame);
556
557                             NUILog.Debug($"[{i}] layer name={key}, startFrame={startFrame}, endFrame={endFrame}");
558
559                             Tuple<string, int, int> item = new Tuple<string, int, int>(key, startFrame, endFrame);
560
561                             currentStates.contentInfo?.Add(item);
562                         }
563                     }
564                 }
565                 contentMap.Dispose();
566                 val?.Dispose();
567             }
568             NUILog.Debug($">");
569
570             return currentStates.contentInfo;
571         }
572
573         /// <summary>
574         /// A marker has its start frame and end frame.
575         /// Animation will play between the start frame and the end frame of the marker if one marker is specified.
576         /// Or animation will play between the start frame of the first marker and the end frame of the second marker if two markers are specified.   *
577         /// </summary>
578         /// <param name="marker1">First marker</param>
579         /// <param name="marker2">Second marker</param>
580         // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
581         [EditorBrowsable(EditorBrowsableState.Never)]
582         public void SetMinMaxFrameByMarker(string marker1, string marker2 = null)
583         {
584             if (currentStates.mark1 != marker1 || currentStates.mark2 != marker2)
585             {
586                 NUILog.Debug($"< [{GetId()}] SetMinMaxFrameByMarker({marker1}, {marker2})");
587
588                 currentStates.changed = true;
589                 currentStates.mark1 = marker1;
590                 currentStates.mark2 = marker2;
591
592                 if (string.IsNullOrEmpty(currentStates.mark2))
593                 {
594                     Interop.View.InternalUpdateVisualPropertyString(this.SwigCPtr, ImageView.Property.IMAGE, ImageVisualProperty.PlayRange, currentStates.mark1);
595                 }
596                 else
597                 {
598                     Interop.View.InternalUpdateVisualPropertyStringPair(this.SwigCPtr, ImageView.Property.IMAGE, ImageVisualProperty.PlayRange, currentStates.mark1, currentStates.mark2);
599                 }
600
601                 NUILog.Debug($"  [{GetId()}] currentStates.mark1:{currentStates.mark1}, mark2:{currentStates.mark2} >");
602             }
603         }
604
605         /// <summary>
606         /// Get MinMax Frame
607         /// </summary>
608         /// <returns>Tuple of Min and Max frames</returns>
609         // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
610         [EditorBrowsable(EditorBrowsableState.Never)]
611         public Tuple<int, int> GetMinMaxFrame()
612         {
613             NUILog.Debug($"< [{GetId()}] GetMinMaxFrame()! total frame={currentStates.totalFrame}");
614
615             using PropertyMap map = Image;
616             if (map != null)
617             {
618                 using PropertyValue val = map.Find(ImageVisualProperty.PlayRange);
619                 if (val != null)
620                 {
621                     using PropertyArray array = new PropertyArray();
622                     if (val.Get(array))
623                     {
624                         uint cnt = array.Count();
625                         int item1 = -1, item2 = -1;
626                         for (uint i = 0; i < cnt; i++)
627                         {
628                             using PropertyValue v = array.GetElementAt(i);
629                             int intRet;
630                             if (v.Get(out intRet))
631                             {
632                                 NUILog.Debug($"Got play range of string [{i}]: {intRet}");
633                                 if (i == 0)
634                                 {
635                                     item1 = intRet;
636                                 }
637                                 else if (i == 1)
638                                 {
639                                     item2 = intRet;
640                                 }
641                             }
642                             else
643                             {
644                                 Tizen.Log.Error("NUI", $"[ERR] fail to get play range from dali! case#1");
645                             }
646                         }
647                         NUILog.Debug($"  [{GetId()}] GetMinMaxFrame(min:{item1}, max:{item2})! >");
648                         return new Tuple<int, int>(item1, item2);
649                     }
650                 }
651             }
652             Tizen.Log.Error("NUI", $"[ERR] fail to get play range from dali! case#2");
653             return new Tuple<int, int>(-1, -1);
654         }
655
656         // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
657         [EditorBrowsable(EditorBrowsableState.Never)]
658         public void DoActionExtension(LottieAnimationViewDynamicProperty info)
659         {
660             dynamicPropertyCallbackId++;
661
662             weakReferencesOfLottie?.Add(dynamicPropertyCallbackId, new WeakReference<LottieAnimationView>(this));
663             InternalSavedDynamicPropertyCallbacks?.Add(dynamicPropertyCallbackId, info.Callback);
664
665             Interop.View.DoActionExtension(SwigCPtr, ImageView.Property.IMAGE, SetDynamicProperty, dynamicPropertyCallbackId, info.KeyPath, (int)info.Property, Marshal.GetFunctionPointerForDelegate<System.Delegate>(rootCallback));
666
667             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
668         }
669
670         private void CleanCallbackDictionaries()
671         {
672             if (weakReferencesOfLottie?.Count > 0 && InternalSavedDynamicPropertyCallbacks != null)
673             {
674                 foreach (var key in InternalSavedDynamicPropertyCallbacks?.Keys)
675                 {
676                     if (weakReferencesOfLottie.ContainsKey(key))
677                     {
678                         weakReferencesOfLottie.Remove(key);
679                     }
680                 }
681             }
682             InternalSavedDynamicPropertyCallbacks?.Clear();
683             InternalSavedDynamicPropertyCallbacks = null;
684         }
685
686         /// <summary>
687         /// Update lottie-image-relative properties synchronously.
688         /// After call this API, All image properties updated.
689         /// </summary>
690         [EditorBrowsable(EditorBrowsableState.Never)]
691         protected override void UpdateImage()
692         {
693             if (!imagePropertyUpdatedFlag) return;
694
695             // Update currentStates properties to cachedImagePropertyMap
696             if(currentStates.changed)
697             {
698                 UpdateImage(ImageVisualProperty.LoopCount, new PropertyValue(currentStates.loopCount), false);
699                 UpdateImage(ImageVisualProperty.StopBehavior, new PropertyValue((int)currentStates.stopEndAction), false);
700                 UpdateImage(ImageVisualProperty.LoopingMode, new PropertyValue((int)currentStates.loopMode), false);
701                 UpdateImage(ImageVisualProperty.RedrawInScalingDown, new PropertyValue(currentStates.redrawInScalingDown), false);
702
703                 // Do not cache PlayRange and TotalFrameNumber into cachedImagePropertyMap.
704                 // (To keep legacy implements behaviour)
705                 currentStates.changed = false;
706             }
707
708             base.UpdateImage();
709         }
710
711         /// <summary>
712         /// Update NUI cached animated image visual property map by inputed property map.
713         /// And call base.MergeCachedImageVisualProperty()
714         /// </summary>
715         /// <remarks>
716         /// For performance issue, we will collect only "cachedLottieAnimationPropertyKeyList" hold in this class.
717         /// </remarks>
718         [EditorBrowsable(EditorBrowsableState.Never)]
719         protected override void MergeCachedImageVisualProperty(PropertyMap map)
720         {
721             if (map == null) return;
722             if (cachedImagePropertyMap == null)
723             {
724                 cachedImagePropertyMap = new PropertyMap();
725             }
726             foreach (var key in cachedLottieAnimationPropertyKeyList)
727             {
728                 PropertyValue value = map.Find(key);
729                 if (value != null)
730                 {
731                     // Update-or-Insert new value
732                     cachedImagePropertyMap[key] = value;
733                 }
734             }
735             base.MergeCachedImageVisualProperty(map);
736         }
737         #endregion Method
738
739
740         #region Event, Enum, Struct, ETC
741         /// <summary>
742         /// Animation finished event.
743         /// </summary>
744         /// <since_tizen> 7 </since_tizen>
745         public event EventHandler Finished
746         {
747             add
748             {
749                 if (finishedEventHandler == null)
750                 {
751                     NUILog.Debug($"<[{GetId()}] Finished eventhandler added>");
752                     visualEventSignalCallback = onVisualEventSignal;
753                     using VisualEventSignal visualEvent = VisualEventSignal();
754                     visualEvent.Connect(visualEventSignalCallback);
755                 }
756                 finishedEventHandler += value;
757             }
758             remove
759             {
760                 NUILog.Debug($"<[{GetId()}] Finished eventhandler removed>");
761                 finishedEventHandler -= value;
762                 if (finishedEventHandler == null && visualEventSignalCallback != null)
763                 {
764                     using VisualEventSignal visualEvent = VisualEventSignal();
765                     visualEvent.Disconnect(visualEventSignalCallback);
766                     if (visualEvent?.Empty() == true)
767                     {
768                         visualEventSignalCallback = null;
769                     }
770                 }
771             }
772         }
773
774         /// <summary>
775         /// Enumeration for what state the vector animation is in
776         /// </summary>
777         /// <since_tizen> 7 </since_tizen>
778         public enum PlayStateType
779         {
780             /// <summary>
781             /// Invalid
782             /// </summary>
783             /// <since_tizen> 7 </since_tizen>
784             Invalid = -1,
785             /// <summary>
786             /// Vector Animation has stopped
787             /// </summary>
788             /// <since_tizen> 7 </since_tizen>
789             Stopped = 0,
790             /// <summary>
791             /// The vector animation is playing
792             /// </summary>
793             /// <since_tizen> 7 </since_tizen>
794             Playing = 1,
795             /// <summary>
796             /// The vector animation is paused
797             /// </summary>
798             /// <since_tizen> 7 </since_tizen>
799             Paused = 2
800         }
801
802         /// <summary>
803         /// Enumeration for what to do when the animation is stopped.
804         /// </summary>
805         /// <since_tizen> 7 </since_tizen>
806         public enum StopBehaviorType
807         {
808             /// <summary>
809             /// When the animation is stopped, the current frame is shown.
810             /// </summary>
811             /// <since_tizen> 7 </since_tizen>
812             CurrentFrame,
813             /// <summary>
814             /// When the animation is stopped, the min frame (first frame) is shown.
815             /// </summary>
816             /// <since_tizen> 7 </since_tizen>
817             MinimumFrame,
818             /// <summary>
819             /// When the animation is stopped, the max frame (last frame) is shown.
820             /// </summary>
821             /// <since_tizen> 7 </since_tizen>
822             MaximumFrame
823         }
824
825         /// <summary>
826         /// Enumeration for what looping mode is in.
827         /// </summary>
828         /// <since_tizen> 7 </since_tizen>
829         public enum LoopingModeType
830         {
831             /// <summary>
832             /// When the animation arrives at the end in looping mode, the animation restarts from the beginning.
833             /// </summary>
834             /// <since_tizen> 7 </since_tizen>
835             Restart,
836             /// <summary>
837             /// When the animation arrives at the end in looping mode, the animation reverses direction and runs backwards again.
838             /// </summary>
839             /// <since_tizen> 7 </since_tizen>
840             AutoReverse
841         }
842
843         /// <summary>
844         /// Vector Property
845         /// </summary>
846         // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
847         [EditorBrowsable(EditorBrowsableState.Never)]
848         public enum VectorProperty
849         {
850             /// <summary>
851             /// Fill color of the object, Type of <see cref="Vector3"/>
852             /// </summary>
853             [EditorBrowsable(EditorBrowsableState.Never)]
854             FillColor,
855
856             /// <summary>
857             /// Fill opacity of the object, Type of float
858             /// </summary>
859             [EditorBrowsable(EditorBrowsableState.Never)]
860             FillOpacity,
861
862             /// <summary>
863             /// Stroke color of the object, Type of <see cref="Vector3"/>
864             /// </summary>
865             [EditorBrowsable(EditorBrowsableState.Never)]
866             StrokeColor,
867
868             /// <summary>
869             /// Stroke opacity of the object, Type of float
870             /// </summary>
871             [EditorBrowsable(EditorBrowsableState.Never)]
872             StrokeOpacity,
873
874             /// <summary>
875             /// Stroke width of the object, Type of float
876             /// </summary>
877             [EditorBrowsable(EditorBrowsableState.Never)]
878             StrokeWidth,
879
880             /// <summary>
881             /// Transform anchor of the Layer and Group object, Type of <see cref="Vector2"/>
882             /// </summary>
883             [EditorBrowsable(EditorBrowsableState.Never)]
884             TransformAnchor,
885
886             /// <summary>
887             /// Transform position of the Layer and Group object, Type of <see cref="Vector2"/>
888             /// </summary>
889             [EditorBrowsable(EditorBrowsableState.Never)]
890             TransformPosition,
891
892             /// <summary>
893             /// Transform scale of the Layer and Group object, Type of <see cref="Vector2"/>, Value range of [0..100]
894             /// </summary>
895             [EditorBrowsable(EditorBrowsableState.Never)]
896             TransformScale,
897
898             /// <summary>
899             /// Transform rotation of the Layer and Group object, Type of float, Value range of [0..360] in degrees
900             /// </summary>
901             [EditorBrowsable(EditorBrowsableState.Never)]
902             TransformRotation,
903
904             /// <summary>
905             /// Transform opacity of the Layer and Group object, Type of float
906             /// </summary>
907             [EditorBrowsable(EditorBrowsableState.Never)]
908             TransformOpacity
909         };
910
911         // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
912         [EditorBrowsable(EditorBrowsableState.Never)]
913         [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
914         public delegate PropertyValue DynamicPropertyCallbackType(int returnType, uint frameNumber);
915         #endregion Event, Enum, Struct, ETC
916
917
918         #region Internal
919         internal class VisualEventSignalArgs : EventArgs
920         {
921             public int VisualIndex
922             {
923                 set;
924                 get;
925             }
926             public int SignalId
927             {
928                 set;
929                 get;
930             }
931         }
932
933         internal event EventHandler<VisualEventSignalArgs> VisualEvent
934         {
935             add
936             {
937                 if (visualEventSignalHandler == null)
938                 {
939                     visualEventSignalCallback = onVisualEventSignal;
940                     using VisualEventSignal visualEvent = VisualEventSignal();
941                     visualEvent?.Connect(visualEventSignalCallback);
942                 }
943                 visualEventSignalHandler += value;
944             }
945             remove
946             {
947                 visualEventSignalHandler -= value;
948                 if (visualEventSignalHandler == null && visualEventSignalCallback != null)
949                 {
950                     using VisualEventSignal visualEvent = VisualEventSignal();
951                     visualEvent?.Disconnect(visualEventSignalCallback);
952                     if (visualEvent?.Empty() == true)
953                     {
954                         visualEventSignalCallback = null;
955                     }
956                 }
957             }
958         }
959
960         internal void EmitVisualEventSignal(int visualIndex, int signalId)
961         {
962             using VisualEventSignal visualEvent = VisualEventSignal();
963             visualEvent?.Emit(this, visualIndex, signalId);
964         }
965
966         internal VisualEventSignal VisualEventSignal()
967         {
968             VisualEventSignal ret = new VisualEventSignal(Interop.VisualEventSignal.NewWithView(View.getCPtr(this)), false);
969             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
970             return ret;
971         }
972
973         internal Dictionary<int, DynamicPropertyCallbackType> InternalSavedDynamicPropertyCallbacks = new Dictionary<int, DynamicPropertyCallbackType>();
974
975         [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
976         internal delegate void RootCallbackType(int id, int returnType, uint frameNumber, ref float val1, ref float val2, ref float val3);
977
978         internal RootCallbackType rootCallback = RootCallback;
979
980         static internal void RootCallback(int id, int returnType, uint frameNumber, ref float val1, ref float val2, ref float val3)
981         {
982             WeakReference<LottieAnimationView> current = null;
983             LottieAnimationView currentView = null;
984             DynamicPropertyCallbackType currentCallback = null;
985             PropertyValue ret = null;
986
987             if (weakReferencesOfLottie.TryGetValue(id, out current))
988             {
989                 if (current.TryGetTarget(out currentView))
990                 {
991                     if (currentView != null && currentView.InternalSavedDynamicPropertyCallbacks != null &&
992                         currentView.InternalSavedDynamicPropertyCallbacks.TryGetValue(id, out currentCallback))
993                     {
994                         ret = currentCallback?.Invoke(returnType, frameNumber);
995                     }
996                     else
997                     {
998                         Tizen.Log.Error("NUI", "can't find the callback in LottieAnimationView, just return here!");
999                         return;
1000                     }
1001                 }
1002                 else
1003                 {
1004                     Tizen.Log.Error("NUI", "can't find the callback in LottieAnimationView, just return here!");
1005                     return;
1006                 }
1007             }
1008             else
1009             {
1010                 Tizen.Log.Error("NUI", "can't find LottieAnimationView by id, just return here!");
1011                 return;
1012             }
1013
1014             switch (returnType)
1015             {
1016                 case (int)(VectorProperty.FillColor):
1017                 case (int)(VectorProperty.StrokeColor):
1018                     Vector3 tmpVector3 = new Vector3(-1, -1, -1);
1019                     if ((ret != null) && ret.Get(tmpVector3))
1020                     {
1021                         val1 = tmpVector3.X;
1022                         val2 = tmpVector3.Y;
1023                         val3 = tmpVector3.Z;
1024                     }
1025                     tmpVector3.Dispose();
1026                     break;
1027
1028                 case (int)(VectorProperty.TransformAnchor):
1029                 case (int)(VectorProperty.TransformPosition):
1030                 case (int)(VectorProperty.TransformScale):
1031                     Vector2 tmpVector2 = new Vector2(-1, -1);
1032                     if ((ret != null) && ret.Get(tmpVector2))
1033                     {
1034                         val1 = tmpVector2.X;
1035                         val2 = tmpVector2.Y;
1036                     }
1037                     tmpVector2.Dispose();
1038                     break;
1039
1040                 case (int)(VectorProperty.FillOpacity):
1041                 case (int)(VectorProperty.StrokeOpacity):
1042                 case (int)(VectorProperty.StrokeWidth):
1043                 case (int)(VectorProperty.TransformRotation):
1044                 case (int)(VectorProperty.TransformOpacity):
1045                     float tmpFloat = -1;
1046                     if ((ret != null) && ret.Get(out tmpFloat))
1047                     {
1048                         val1 = tmpFloat;
1049                     }
1050                     break;
1051                 default:
1052                     //do nothing
1053                     break;
1054             }
1055             ret?.Dispose();
1056         }
1057         #endregion Internal
1058
1059
1060         #region Private
1061
1062         // Collection of lottie-image-sensitive properties.
1063         private static readonly List<int> cachedLottieAnimationPropertyKeyList = new List<int> {
1064             ImageVisualProperty.LoopCount,
1065             ImageVisualProperty.StopBehavior,
1066             ImageVisualProperty.LoopingMode,
1067             ImageVisualProperty.RedrawInScalingDown,
1068         };
1069
1070         private struct states
1071         {
1072             internal string url;
1073             internal int loopCount;
1074             internal LoopingModeType loopMode;
1075             internal StopBehaviorType stopEndAction;
1076             internal int framePlayRangeMin;
1077             internal int framePlayRangeMax;
1078             internal int totalFrame;
1079             internal float scale;
1080             internal PlayStateType playState;
1081             internal List<Tuple<string, int, int>> contentInfo;
1082             internal string mark1, mark2;
1083             internal bool redrawInScalingDown;
1084             internal bool changed;
1085         };
1086         private states currentStates;
1087
1088         private const string tag = "NUITEST";
1089         private event EventHandler finishedEventHandler;
1090
1091         private void OnFinished()
1092         {
1093             NUILog.Debug($"<[{GetId()}] OnFinished()>");
1094             finishedEventHandler?.Invoke(this, null);
1095         }
1096
1097         private void onVisualEventSignal(IntPtr targetView, int visualIndex, int signalId)
1098         {
1099             OnFinished();
1100
1101             if (targetView != IntPtr.Zero)
1102             {
1103                 View v = Registry.GetManagedBaseHandleFromNativePtr(targetView) as View;
1104                 if (v != null)
1105                 {
1106                     NUILog.Debug($"targetView is not null! name={v.Name}");
1107                 }
1108                 else
1109                 {
1110                     NUILog.Debug($"target is something created from dali");
1111                 }
1112             }
1113             VisualEventSignalArgs e = new VisualEventSignalArgs();
1114             e.VisualIndex = visualIndex;
1115             e.SignalId = signalId;
1116             visualEventSignalHandler?.Invoke(this, e);
1117
1118             NUILog.Debug($"<[{GetId()}] onVisualEventSignal()! visualIndex={visualIndex}, signalId={signalId}>");
1119         }
1120
1121         [UnmanagedFunctionPointer(CallingConvention.StdCall)]
1122         private delegate void VisualEventSignalCallbackType(IntPtr targetView, int visualIndex, int signalId);
1123
1124         private VisualEventSignalCallbackType visualEventSignalCallback;
1125         private EventHandler<VisualEventSignalArgs> visualEventSignalHandler;
1126
1127         static private int dynamicPropertyCallbackId = 0;
1128         //static private Dictionary<int, DynamicPropertyCallbackType> dynamicPropertyCallbacks = new Dictionary<int, DynamicPropertyCallbackType>();
1129         static private Dictionary<int, WeakReference<LottieAnimationView>> weakReferencesOfLottie = new Dictionary<int, WeakReference<LottieAnimationView>>();
1130
1131         private void debugPrint()
1132         {
1133             NUILog.Debug($"===================================");
1134             NUILog.Debug($"<[{GetId()}] get currentStates : url={currentStates.url}, loopCount={currentStates.loopCount}, \nframePlayRangeMin/Max({currentStates.framePlayRangeMin},{currentStates.framePlayRangeMax}) ");
1135             NUILog.Debug($"  get from Property : StopBehavior={StopBehavior}, LoopMode={LoopingMode}, LoopCount={LoopCount}, PlayState={PlayState}");
1136             NUILog.Debug($"  RedrawInScalingDown={RedrawInScalingDown} >");
1137             NUILog.Debug($"===================================");
1138         }
1139
1140         #endregion Private
1141     }
1142
1143     /// <summary>
1144     /// A class containing frame informations for a LottieAnimationView.
1145     /// </summary>
1146     [EditorBrowsable(EditorBrowsableState.Never)]
1147     public class LottieFrameInfo : ICloneable
1148     {
1149         /// <summary>
1150         /// Creates a new instance with a playing range.
1151         /// </summary>
1152         [EditorBrowsable(EditorBrowsableState.Never)]
1153         public LottieFrameInfo(int startFrame, int endFrame)
1154         {
1155             StartFrame = startFrame;
1156             EndFrame = endFrame;
1157         }
1158
1159         /// <summary>
1160         /// Creates a new instance with a still image frame.
1161         /// </summary>
1162         [EditorBrowsable(EditorBrowsableState.Never)]
1163         public LottieFrameInfo(int stillImageFrame) : this(stillImageFrame, stillImageFrame)
1164         {
1165         }
1166
1167         /// <summary>
1168         /// Create a new instance from a pair notation.
1169         /// </summary>
1170         [EditorBrowsable(EditorBrowsableState.Never)]
1171         public static implicit operator LottieFrameInfo((int, int) pair)
1172         {
1173             return new LottieFrameInfo(pair.Item1, pair.Item2);
1174         }
1175
1176         /// <summary>
1177         /// Create a new instance from an int value.
1178         /// </summary>
1179         [EditorBrowsable(EditorBrowsableState.Never)]
1180         public static implicit operator LottieFrameInfo(int stillImageFrame)
1181         {
1182             return new LottieFrameInfo(stillImageFrame);
1183         }
1184
1185         /// <summary>
1186         /// Create a new instance from string.
1187         /// Possible input : "0, 10", "10"
1188         /// </summary>
1189         [EditorBrowsable(EditorBrowsableState.Never)]
1190         public static implicit operator LottieFrameInfo(string pair)
1191         {
1192             if (pair == null)
1193             {
1194                 return null;
1195             }
1196
1197             string[] parts = pair.Split(',');
1198             if (parts.Length == 1)
1199             {
1200                 return new LottieFrameInfo(Int32.Parse(parts[0].Trim(), CultureInfo.InvariantCulture));
1201             }
1202             else if (parts.Length == 2)
1203             {
1204                 return new LottieFrameInfo(Int32.Parse(parts[0].Trim(), CultureInfo.InvariantCulture), Int32.Parse(parts[1].Trim(), CultureInfo.InvariantCulture));
1205             }
1206
1207             Tizen.Log.Error("NUI", $"Can not convert string {pair} to LottieFrameInfo");
1208             return null;
1209         }
1210
1211         /// <summary>
1212         /// The start frame of the lottie animation.
1213         /// </summary>
1214         [EditorBrowsable(EditorBrowsableState.Never)]
1215         public int StartFrame { get; }
1216
1217         /// <summary>
1218         /// The end frame of the lottie animation.
1219         /// </summary>
1220         [EditorBrowsable(EditorBrowsableState.Never)]
1221         public int EndFrame { get; }
1222
1223         /// <summary>
1224         /// Create LottieFrameInfo struct with animation range information
1225         /// </summary>
1226         [EditorBrowsable(EditorBrowsableState.Never)]
1227         public static LottieFrameInfo CreateAnimationRange(int startFrame, int endFrame)
1228         {
1229             return new LottieFrameInfo(startFrame, endFrame);
1230         }
1231
1232         /// <summary>
1233         /// Create LottieFrameInfo struct with still image information
1234         /// </summary>
1235         [EditorBrowsable(EditorBrowsableState.Never)]
1236         public static LottieFrameInfo CreateStillImage(int stillImageFrame)
1237         {
1238             return new LottieFrameInfo(stillImageFrame, stillImageFrame);
1239         }
1240
1241         /// <summary>
1242         /// Inhouse API.
1243         /// Whether this LottieFrameInfo represents one frame or more.
1244         /// </summary>
1245         [EditorBrowsable(EditorBrowsableState.Never)]
1246         public bool IsStillImage()
1247         {
1248             return StartFrame == EndFrame;
1249         }
1250
1251         /// <summary>
1252         /// Inhouse API.
1253         /// Play specified LottieAnimationView with this frame information.
1254         /// </summary>
1255         /// <param name="lottieView">The target LottieAnimationView to play.</param>
1256         /// <param name="noPlay">Whether go direct to the EndFrame. It is false by default.</param>
1257         [EditorBrowsable(EditorBrowsableState.Never)]
1258         [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062: Validate arguments of public methods", Justification = "The null checking is done by BeReadyToShow()")]
1259         public void Show(LottieAnimationView lottieView, bool noPlay = false)
1260         {
1261             if (!BeReadyToShow(lottieView))
1262             {
1263                 return;
1264             }
1265
1266             lottieView.SetMinMaxFrame(StartFrame, Math.Min(EndFrame, lottieView.TotalFrame - 1));
1267
1268             if (IsStillImage() || noPlay)
1269             {
1270                 lottieView.CurrentFrame = EndFrame;
1271             }
1272             else
1273             {
1274                 lottieView.CurrentFrame = StartFrame;
1275                 lottieView.Play();
1276             }
1277         }
1278
1279         /// <inheritdoc/>
1280         [EditorBrowsable(EditorBrowsableState.Never)]
1281         public object Clone() => new LottieFrameInfo(StartFrame, EndFrame);
1282
1283         private bool BeReadyToShow(LottieAnimationView lottieView)
1284         {
1285             // Validate input lottieView
1286             if (null == lottieView || lottieView.PlayState == LottieAnimationView.PlayStateType.Invalid)
1287             {
1288                 return false;
1289             }
1290
1291             // Stop if it was playing
1292             if (lottieView.PlayState == LottieAnimationView.PlayStateType.Playing)
1293             {
1294                 lottieView.Stop();
1295             }
1296
1297             return true;
1298         }
1299     }
1300
1301     // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
1302     [EditorBrowsable(EditorBrowsableState.Never)]
1303     public struct LottieAnimationViewDynamicProperty : IEquatable<LottieAnimationViewDynamicProperty>
1304     {
1305         [EditorBrowsable(EditorBrowsableState.Never)]
1306         public string KeyPath { get; set; }
1307
1308         [EditorBrowsable(EditorBrowsableState.Never)]
1309         public LottieAnimationView.VectorProperty Property { get; set; }
1310
1311         [EditorBrowsable(EditorBrowsableState.Never)]
1312         public LottieAnimationView.DynamicPropertyCallbackType Callback { get; set; }
1313
1314         public override bool Equals(object obj)
1315         {
1316             if (obj is LottieAnimationViewDynamicProperty target)
1317             {
1318                 if (KeyPath == target.KeyPath && Property == target.Property && Callback == target.Callback)
1319                 {
1320                     return true;
1321                 }
1322             }
1323             return false;
1324         }
1325
1326         public override int GetHashCode()
1327         {
1328             return base.GetHashCode();
1329         }
1330
1331         public static bool operator ==(LottieAnimationViewDynamicProperty left, LottieAnimationViewDynamicProperty right)
1332         {
1333             return left.Equals(right);
1334         }
1335
1336         public static bool operator !=(LottieAnimationViewDynamicProperty left, LottieAnimationViewDynamicProperty right)
1337         {
1338             return !(left == right);
1339         }
1340
1341         public bool Equals(LottieAnimationViewDynamicProperty other)
1342         {
1343             if (other != null)
1344             {
1345                 if (KeyPath == other.KeyPath && Property == other.Property && Callback == other.Callback)
1346                 {
1347                     return true;
1348                 }
1349             }
1350             return false;
1351         }
1352     }
1353
1354 }