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