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