/*
* Copyright(c) 2020 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
using global::System;
using global::System.Runtime.InteropServices;
using System.ComponentModel;
using System.Collections.Generic;
using System.Globalization;
namespace Tizen.NUI.BaseComponents
{
///
/// LottieAnimationView renders an animated vector image (Lottie file).
///
/// 7
public partial class LottieAnimationView : ImageView
{
#region Constructor, Destructor, Dispose
///
/// LottieAnimationView constructor
///
/// The factor of scaling image, default : 1.0f
/// false : not displayed (hidden), true : displayed (shown), default : true
///
/// If the shown parameter is false, the animation is not visible even if the LottieAnimationView instance is created.
///
///
///
/// LottieAnimationView myLottie = new LottieAnimationView();
/// LottieAnimationView myLottie2 = new LottieAnimationView(2.0f);
/// LottieAnimationView myLottie3 = new LottieAnimationView(1.0f, false);
///
///
/// 7
public LottieAnimationView(float scale = 1.0f, bool shown = true) : base()
{
NUILog.Debug($"< constructor GetId={GetId()} >");
currentStates.url = "";
currentStates.loopCount = 1;
currentStates.loopMode = LoopingModeType.Restart;
currentStates.stopEndAction = StopBehaviorType.CurrentFrame;
currentStates.framePlayRangeMin = -1;
currentStates.framePlayRangeMax = -1;
currentStates.totalFrame = -1;
currentStates.scale = scale;
currentStates.redrawInScalingDown = true;
// Set changed flag as true when initalized state.
// After some properties change, LottieAnimationView.UpdateImage will apply these inital values.
currentStates.changed = true;
SetVisible(shown);
}
///
/// Dispose(DisposeTypes type)
///
///
// This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
[EditorBrowsable(EditorBrowsableState.Never)]
protected override void Dispose(DisposeTypes type)
{
if (disposed)
{
return;
}
CleanCallbackDictionaries();
//Release your own unmanaged resources here.
//You should not access any managed member here except static instance.
//because the execution order of Finalizes is non-deterministic.
//disconnect event signal
if (finishedEventHandler != null && visualEventSignalCallback != null)
{
using VisualEventSignal visualEvent = VisualEventSignal();
visualEvent.Disconnect(visualEventSignalCallback);
finishedEventHandler = null;
NUILog.Debug($"disconnect event signal");
}
base.Dispose(type);
}
// This is used for internal purpose. hidden API.
[EditorBrowsable(EditorBrowsableState.Never)]
protected override void Dispose(bool disposing)
{
CleanCallbackDictionaries();
base.Dispose(disposing);
}
#endregion Constructor, Destructor, Dispose
#region Property
///
/// Set or Get resource URL of Lottie file.
///
/// 7
public string URL
{
get
{
return GetValue(URLProperty) as string;
}
set
{
SetValue(URLProperty, value);
NotifyPropertyChanged();
}
}
private string InternalURL
{
set
{
// Reset cached infomations.
currentStates.contentInfo = null;
currentStates.mark1 = null;
currentStates.mark2 = null;
currentStates.framePlayRangeMin = -1;
currentStates.framePlayRangeMax = -1;
currentStates.totalFrame = -1;
string ret = (value == null ? "" : value);
currentStates.url = ret;
NUILog.Debug($"<[{GetId()}]SET url={currentStates.url}");
// TODO : Could create new Image without additional creation?
using PropertyMap map = new PropertyMap();
using PropertyValue type = new PropertyValue((int)Visual.Type.AnimatedVectorImage);
using PropertyValue url = new PropertyValue(currentStates.url);
using PropertyValue loopCnt = new PropertyValue(currentStates.loopCount);
using PropertyValue stopAction = new PropertyValue((int)currentStates.stopEndAction);
using PropertyValue loopMode = new PropertyValue((int)currentStates.loopMode);
using PropertyValue redrawInScalingDown = new PropertyValue(currentStates.redrawInScalingDown);
map.Add(Visual.Property.Type, type)
.Add(ImageVisualProperty.URL, url)
.Add(ImageVisualProperty.LoopCount, loopCnt)
.Add(ImageVisualProperty.StopBehavior, stopAction)
.Add(ImageVisualProperty.LoopingMode, loopMode)
.Add(ImageVisualProperty.RedrawInScalingDown, redrawInScalingDown);
Image = map;
// All states applied well.
currentStates.changed = false;
if (currentStates.scale != 1.0f)
{
Scale = new Vector3(currentStates.scale, currentStates.scale, currentStates.scale);
}
NUILog.Debug($"<[{GetId()}]>");
}
get
{
string ret = currentStates.url;
NUILog.Debug($"<[{GetId()}] GET");
NUILog.Debug($"gotten url={ret} >");
return ret;
}
}
///
/// Gets the playing state
///
/// 7
public PlayStateType PlayState
{
get
{
NUILog.Debug($"< Get!");
int ret = 0;
Interop.View.InternalRetrievingVisualPropertyInt(this.SwigCPtr, ImageView.Property.IMAGE, ImageVisualProperty.PlayState, out ret);
currentStates.playState = (PlayStateType)ret;
NUILog.Debug($"gotten play state={currentStates.playState} >");
return currentStates.playState;
}
}
///
/// Get the number of total frames
///
/// 7
public int TotalFrame
{
get
{
int ret = currentStates.totalFrame;
if (ret == -1)
{
Interop.View.InternalRetrievingVisualPropertyInt(this.SwigCPtr, ImageView.Property.IMAGE, ImageVisualProperty.TotalFrameNumber, out ret);
currentStates.totalFrame = ret;
NUILog.Debug($"TotalFrameNumber get! ret={ret}");
}
return ret;
}
}
///
/// Set or get the current frame. When setting a specific frame, it is displayed as a still image.
///
///
/// 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.
///
///
/// We assume that the animation in myLottie.json file has 100 frames originally. If so, its frame index will be 0 - 99.
///
/// LottieAnimationView myLottie = new LottieAnimationView();
/// myLottie.URL = Tizen.Applications.Application.Current.DirectoryInfo.Resource + "myLottie.json"; //myLottie.json's total frame is 100 (frame: 0~99)
/// NUIApplication.GetDefaultWindow().GetDefaultLayer().Add(myLottie);
/// myLottie.CurrentFrame = 200; //display 99 frame
/// myLottie.SetMinMaxFrame(10, 20);
/// myLottie.CurrentFrame = 15; //display 15 frame
/// myLottie.CurrentFrame = 50; //display 20 frame, because the MinMax is set (10,20) above
///
///
/// 7
public int CurrentFrame
{
get
{
return (int)GetValue(CurrentFrameProperty);
}
set
{
SetValue(CurrentFrameProperty, value);
NotifyPropertyChanged();
}
}
private int InternalCurrentFrame
{
set
{
NUILog.Debug($"<[{GetId()}]SET frame={value}>");
Interop.View.DoActionWithSingleIntAttributes(this.SwigCPtr, ImageView.Property.IMAGE, ActionJumpTo, value);
}
get
{
int ret = 0;
Interop.View.InternalRetrievingVisualPropertyInt(this.SwigCPtr, ImageView.Property.IMAGE, ImageVisualProperty.CurrentFrameNumber, out ret);
NUILog.Debug($"CurrentFrameNumber get! val={ret}");
return ret;
}
}
///
/// Sets or gets the looping mode of Lottie animation.
///
/// 7
public LoopingModeType LoopingMode
{
get
{
return (LoopingModeType)GetValue(LoopingModeProperty);
}
set
{
SetValue(LoopingModeProperty, value);
NotifyPropertyChanged();
}
}
private LoopingModeType InternalLoopingMode
{
set
{
if (currentStates.loopMode != (LoopingModeType)value)
{
currentStates.changed = true;
currentStates.loopMode = (LoopingModeType)value;
NUILog.Debug($"<[{GetId()}] SET loopMode={currentStates.loopMode}>");
Interop.View.InternalUpdateVisualPropertyInt(this.SwigCPtr, ImageView.Property.IMAGE, ImageVisualProperty.LoopingMode, (int)currentStates.loopMode);
}
}
get
{
NUILog.Debug($"LoopMode get! {currentStates.loopMode}");
return currentStates.loopMode;
}
}
///
/// Sets or gets the loop count.
///
///
/// The minus value means the infinite loop count.
///
///
///
/// LottieAnimationView myLottie = new LottieAnimationView();
/// myLottie.URL = Tizen.Applications.Application.Current.DirectoryInfo.Resource + "myLottie.json"; //myLottie.json's total frame is 100 (frame: 0~99)
/// NUIApplication.GetDefaultWindow().GetDefaultLayer().Add(myLottie);
/// myLottie.LoopCount = -1; //infinite loop
/// myLottie.Play();
/// myLottie.Stop(); //it plays continuously unless Stop() is called
/// myLottie.LoopCount = 2;
/// myLottie.Play(); //it plays only 2 times and stops automatically
///
///
/// 7
public int LoopCount
{
get
{
return (int)GetValue(LoopCountProperty);
}
set
{
SetValue(LoopCountProperty, value);
NotifyPropertyChanged();
}
}
private int InternalLoopCount
{
set
{
if (currentStates.loopCount != value)
{
currentStates.changed = true;
currentStates.loopCount = value;
NUILog.Debug($"<[{GetId()}]SET currentStates.loopCount={currentStates.loopCount}>");
Interop.View.InternalUpdateVisualPropertyInt(this.SwigCPtr, ImageView.Property.IMAGE, ImageVisualProperty.LoopCount, currentStates.loopCount);
}
}
get
{
NUILog.Debug($"LoopCount get! {currentStates.loopCount}");
return currentStates.loopCount;
}
}
///
/// Sets or gets the stop behavior.
///
/// 7
public StopBehaviorType StopBehavior
{
get
{
return (StopBehaviorType)GetValue(StopBehaviorProperty);
}
set
{
SetValue(StopBehaviorProperty, value);
NotifyPropertyChanged();
}
}
private StopBehaviorType InternalStopBehavior
{
set
{
if (currentStates.stopEndAction != (StopBehaviorType)value)
{
currentStates.changed = true;
currentStates.stopEndAction = (StopBehaviorType)value;
NUILog.Debug($"<[{GetId()}]SET val={currentStates.stopEndAction}>");
Interop.View.InternalUpdateVisualPropertyInt(this.SwigCPtr, ImageView.Property.IMAGE, ImageVisualProperty.StopBehavior, (int)currentStates.stopEndAction);
}
}
get
{
NUILog.Debug($"StopBehavior get! {currentStates.stopEndAction}");
return currentStates.stopEndAction;
}
}
///
/// Whether to redraw the image when the visual is scaled down.
///
///
/// Inhouse API.
/// It is used in the AnimatedVectorImageVisual.The default is true.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public bool RedrawInScalingDown
{
get
{
return (bool)GetValue(RedrawInScalingDownProperty);
}
set
{
SetValue(RedrawInScalingDownProperty, value);
NotifyPropertyChanged();
}
}
private bool InternalRedrawInScalingDown
{
set
{
if (currentStates.redrawInScalingDown != value)
{
currentStates.changed = true;
currentStates.redrawInScalingDown = value;
NUILog.Debug($"<[{GetId()}]SET currentStates.redrawInScalingDown={currentStates.redrawInScalingDown}>");
Interop.View.InternalUpdateVisualPropertyBool(this.SwigCPtr, ImageView.Property.IMAGE, ImageVisualProperty.RedrawInScalingDown, currentStates.redrawInScalingDown);
}
}
get
{
NUILog.Debug($"RedrawInScalingDown get! {currentStates.redrawInScalingDown}");
return currentStates.redrawInScalingDown;
}
}
#endregion Property
#region Method
///
/// Set the minimum and the maximum frame.
///
/// minimum frame
/// maximum frame
/// 7
public void SetMinMaxFrame(int minFrame, int maxFrame)
{
if (currentStates.framePlayRangeMin != minFrame || currentStates.framePlayRangeMax != maxFrame)
{
NUILog.Debug($"< [{GetId()}] SetPlayRange({minFrame}, {maxFrame})");
currentStates.changed = true;
currentStates.framePlayRangeMin = minFrame;
currentStates.framePlayRangeMax = maxFrame;
Interop.View.InternalUpdateVisualPropertyIntPair(this.SwigCPtr, ImageView.Property.IMAGE, ImageVisualProperty.PlayRange, currentStates.framePlayRangeMin, currentStates.framePlayRangeMax);
NUILog.Debug($" [{GetId()}] currentStates.min:({currentStates.framePlayRangeMin}, max:{currentStates.framePlayRangeMax})>");
}
}
///
/// Play Animation.
///
/// 7
public new void Play()
{
NUILog.Debug($"<[{GetId()}] Play()");
debugPrint();
base.Play();
NUILog.Debug($"[{GetId()}]>");
}
///
/// Pause Animation.
///
/// 7
public new void Pause()
{
NUILog.Debug($"<[{GetId()}] Pause()>");
debugPrint();
base.Pause();
NUILog.Debug($"[{GetId()}]>");
}
///
/// Stop Animation.
///
/// 7
public new void Stop()
{
NUILog.Debug($"<[{GetId()}] Stop()");
debugPrint();
base.Stop();
NUILog.Debug($"[{GetId()}]>");
}
///
/// Get the list of layers' information such as the start frame and the end frame in the Lottie file.
///
/// List of Tuple (string of layer name, integer of start frame, integer of end frame)
/// 7
public List> GetContentInfo()
{
if (currentStates.contentInfo != null)
{
return currentStates.contentInfo;
}
NUILog.Debug($"<");
PropertyMap imageMap = base.Image;
if (imageMap != null)
{
PropertyValue val = imageMap.Find(ImageVisualProperty.ContentInfo);
PropertyMap contentMap = new PropertyMap();
if (val?.Get(ref contentMap) == true)
{
currentStates.contentInfo = new List>();
for (uint i = 0; i < contentMap.Count(); i++)
{
using PropertyKey propertyKey = contentMap.GetKeyAt(i);
string key = propertyKey.StringKey;
using PropertyValue arrVal = contentMap.GetValue(i);
using PropertyArray arr = new PropertyArray();
if (arrVal.Get(arr))
{
int startFrame = -1;
using PropertyValue start = arr.GetElementAt(0);
start?.Get(out startFrame);
int endFrame = -1;
using PropertyValue end = arr.GetElementAt(1);
end?.Get(out endFrame);
NUILog.Debug($"[{i}] layer name={key}, startFrame={startFrame}, endFrame={endFrame}");
Tuple item = new Tuple(key, startFrame, endFrame);
currentStates.contentInfo?.Add(item);
}
}
}
contentMap.Dispose();
val?.Dispose();
}
NUILog.Debug($">");
return currentStates.contentInfo;
}
///
/// A marker has its start frame and end frame.
/// Animation will play between the start frame and the end frame of the marker if one marker is specified.
/// 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. *
///
/// First marker
/// Second marker
// This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
[EditorBrowsable(EditorBrowsableState.Never)]
public void SetMinMaxFrameByMarker(string marker1, string marker2 = null)
{
if (currentStates.mark1 != marker1 || currentStates.mark2 != marker2)
{
NUILog.Debug($"< [{GetId()}] SetMinMaxFrameByMarker({marker1}, {marker2})");
currentStates.changed = true;
currentStates.mark1 = marker1;
currentStates.mark2 = marker2;
if (string.IsNullOrEmpty(currentStates.mark2))
{
Interop.View.InternalUpdateVisualPropertyString(this.SwigCPtr, ImageView.Property.IMAGE, ImageVisualProperty.PlayRange, currentStates.mark1);
}
else
{
Interop.View.InternalUpdateVisualPropertyStringPair(this.SwigCPtr, ImageView.Property.IMAGE, ImageVisualProperty.PlayRange, currentStates.mark1, currentStates.mark2);
}
NUILog.Debug($" [{GetId()}] currentStates.mark1:{currentStates.mark1}, mark2:{currentStates.mark2} >");
}
}
///
/// Get MinMax Frame
///
/// Tuple of Min and Max frames
// This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
[EditorBrowsable(EditorBrowsableState.Never)]
public Tuple GetMinMaxFrame()
{
NUILog.Debug($"< [{GetId()}] GetMinMaxFrame()! total frame={currentStates.totalFrame}");
using PropertyMap map = Image;
if (map != null)
{
using PropertyValue val = map.Find(ImageVisualProperty.PlayRange);
if (val != null)
{
using PropertyArray array = new PropertyArray();
if (val.Get(array))
{
uint cnt = array.Count();
int item1 = -1, item2 = -1;
for (uint i = 0; i < cnt; i++)
{
using PropertyValue v = array.GetElementAt(i);
int intRet;
if (v.Get(out intRet))
{
NUILog.Debug($"Got play range of string [{i}]: {intRet}");
if (i == 0)
{
item1 = intRet;
}
else if (i == 1)
{
item2 = intRet;
}
}
else
{
Tizen.Log.Error("NUI", $"[ERR] fail to get play range from dali! case#1");
}
}
NUILog.Debug($" [{GetId()}] GetMinMaxFrame(min:{item1}, max:{item2})! >");
return new Tuple(item1, item2);
}
}
}
Tizen.Log.Error("NUI", $"[ERR] fail to get play range from dali! case#2");
return new Tuple(-1, -1);
}
// This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
[EditorBrowsable(EditorBrowsableState.Never)]
public void DoActionExtension(LottieAnimationViewDynamicProperty info)
{
dynamicPropertyCallbackId++;
weakReferencesOfLottie?.Add(dynamicPropertyCallbackId, new WeakReference(this));
InternalSavedDynamicPropertyCallbacks?.Add(dynamicPropertyCallbackId, info.Callback);
Interop.View.DoActionExtension(SwigCPtr, ImageView.Property.IMAGE, ActionSetDynamicProperty, dynamicPropertyCallbackId, info.KeyPath, (int)info.Property, Marshal.GetFunctionPointerForDelegate(rootCallback));
if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
}
private void CleanCallbackDictionaries()
{
if (weakReferencesOfLottie?.Count > 0 && InternalSavedDynamicPropertyCallbacks != null)
{
foreach (var key in InternalSavedDynamicPropertyCallbacks?.Keys)
{
if (weakReferencesOfLottie.ContainsKey(key))
{
weakReferencesOfLottie.Remove(key);
}
}
}
InternalSavedDynamicPropertyCallbacks?.Clear();
InternalSavedDynamicPropertyCallbacks = null;
}
///
/// Update lottie-image-relative properties synchronously.
/// After call this API, All image properties updated.
///
[EditorBrowsable(EditorBrowsableState.Never)]
protected override void UpdateImage()
{
if (!imagePropertyUpdatedFlag) return;
// Update currentStates properties to cachedImagePropertyMap
if(currentStates.changed)
{
UpdateImage(ImageVisualProperty.LoopCount, new PropertyValue(currentStates.loopCount), false);
UpdateImage(ImageVisualProperty.StopBehavior, new PropertyValue((int)currentStates.stopEndAction), false);
UpdateImage(ImageVisualProperty.LoopingMode, new PropertyValue((int)currentStates.loopMode), false);
UpdateImage(ImageVisualProperty.RedrawInScalingDown, new PropertyValue(currentStates.redrawInScalingDown), false);
// Do not cache PlayRange and TotalFrameNumber into cachedImagePropertyMap.
// (To keep legacy implements behaviour)
currentStates.changed = false;
}
base.UpdateImage();
}
///
/// Update NUI cached animated image visual property map by inputed property map.
/// And call base.MergeCachedImageVisualProperty()
///
///
/// For performance issue, we will collect only "cachedLottieAnimationPropertyKeyList" hold in this class.
///
[EditorBrowsable(EditorBrowsableState.Never)]
protected override void MergeCachedImageVisualProperty(PropertyMap map)
{
if (map == null) return;
if (cachedImagePropertyMap == null)
{
cachedImagePropertyMap = new PropertyMap();
}
foreach (var key in cachedLottieAnimationPropertyKeyList)
{
PropertyValue value = map.Find(key);
if (value != null)
{
// Update-or-Insert new value
cachedImagePropertyMap[key] = value;
}
}
base.MergeCachedImageVisualProperty(map);
}
#endregion Method
#region Event, Enum, Struct, ETC
///
/// Animation finished event.
///
/// 7
public event EventHandler Finished
{
add
{
if (finishedEventHandler == null)
{
NUILog.Debug($"<[{GetId()}] Finished eventhandler added>");
visualEventSignalCallback = onVisualEventSignal;
using VisualEventSignal visualEvent = VisualEventSignal();
visualEvent.Connect(visualEventSignalCallback);
}
finishedEventHandler += value;
}
remove
{
NUILog.Debug($"<[{GetId()}] Finished eventhandler removed>");
finishedEventHandler -= value;
if (finishedEventHandler == null && visualEventSignalCallback != null)
{
using VisualEventSignal visualEvent = VisualEventSignal();
visualEvent.Disconnect(visualEventSignalCallback);
if (visualEvent?.Empty() == true)
{
visualEventSignalCallback = null;
}
}
}
}
///
/// Enumeration for what state the vector animation is in
///
/// 7
public enum PlayStateType
{
///
/// Invalid
///
/// 7
Invalid = -1,
///
/// Vector Animation has stopped
///
/// 7
Stopped = 0,
///
/// The vector animation is playing
///
/// 7
Playing = 1,
///
/// The vector animation is paused
///
/// 7
Paused = 2
}
///
/// Enumeration for what to do when the animation is stopped.
///
/// 7
public enum StopBehaviorType
{
///
/// When the animation is stopped, the current frame is shown.
///
/// 7
CurrentFrame,
///
/// When the animation is stopped, the min frame (first frame) is shown.
///
/// 7
MinimumFrame,
///
/// When the animation is stopped, the max frame (last frame) is shown.
///
/// 7
MaximumFrame
}
///
/// Enumeration for what looping mode is in.
///
/// 7
public enum LoopingModeType
{
///
/// When the animation arrives at the end in looping mode, the animation restarts from the beginning.
///
/// 7
Restart,
///
/// When the animation arrives at the end in looping mode, the animation reverses direction and runs backwards again.
///
/// 7
AutoReverse
}
///
/// Vector Property
///
// This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
[EditorBrowsable(EditorBrowsableState.Never)]
public enum VectorProperty
{
///
/// Fill color of the object, Type of
///
[EditorBrowsable(EditorBrowsableState.Never)]
FillColor,
///
/// Fill opacity of the object, Type of float
///
[EditorBrowsable(EditorBrowsableState.Never)]
FillOpacity,
///
/// Stroke color of the object, Type of
///
[EditorBrowsable(EditorBrowsableState.Never)]
StrokeColor,
///
/// Stroke opacity of the object, Type of float
///
[EditorBrowsable(EditorBrowsableState.Never)]
StrokeOpacity,
///
/// Stroke width of the object, Type of float
///
[EditorBrowsable(EditorBrowsableState.Never)]
StrokeWidth,
///
/// Transform anchor of the Layer and Group object, Type of
///
[EditorBrowsable(EditorBrowsableState.Never)]
TransformAnchor,
///
/// Transform position of the Layer and Group object, Type of
///
[EditorBrowsable(EditorBrowsableState.Never)]
TransformPosition,
///
/// Transform scale of the Layer and Group object, Type of , Value range of [0..100]
///
[EditorBrowsable(EditorBrowsableState.Never)]
TransformScale,
///
/// Transform rotation of the Layer and Group object, Type of float, Value range of [0..360] in degrees
///
[EditorBrowsable(EditorBrowsableState.Never)]
TransformRotation,
///
/// Transform opacity of the Layer and Group object, Type of float
///
[EditorBrowsable(EditorBrowsableState.Never)]
TransformOpacity
};
// This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
[EditorBrowsable(EditorBrowsableState.Never)]
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate PropertyValue DynamicPropertyCallbackType(int returnType, uint frameNumber);
#endregion Event, Enum, Struct, ETC
#region Internal
///
/// Actions property value to Jump to the specified frame.
///
internal static readonly int ActionJumpTo = Interop.LottieAnimationView.AnimatedVectorImageVisualActionJumpToGet();
// This is used for internal purpose.
internal static readonly int ActionSetDynamicProperty = Interop.LottieAnimationView.AnimatedVectorImageVisualActionSetDynamicProperty();
internal class VisualEventSignalArgs : EventArgs
{
public int VisualIndex
{
set;
get;
}
public int SignalId
{
set;
get;
}
}
internal event EventHandler VisualEvent
{
add
{
if (visualEventSignalHandler == null)
{
visualEventSignalCallback = onVisualEventSignal;
using VisualEventSignal visualEvent = VisualEventSignal();
visualEvent?.Connect(visualEventSignalCallback);
}
visualEventSignalHandler += value;
}
remove
{
visualEventSignalHandler -= value;
if (visualEventSignalHandler == null && visualEventSignalCallback != null)
{
using VisualEventSignal visualEvent = VisualEventSignal();
visualEvent?.Disconnect(visualEventSignalCallback);
if (visualEvent?.Empty() == true)
{
visualEventSignalCallback = null;
}
}
}
}
internal void EmitVisualEventSignal(int visualIndex, int signalId)
{
using VisualEventSignal visualEvent = VisualEventSignal();
visualEvent?.Emit(this, visualIndex, signalId);
}
internal VisualEventSignal VisualEventSignal()
{
VisualEventSignal ret = new VisualEventSignal(Interop.VisualEventSignal.NewWithView(View.getCPtr(this)), false);
if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
return ret;
}
internal Dictionary InternalSavedDynamicPropertyCallbacks = new Dictionary();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void RootCallbackType(int id, int returnType, uint frameNumber, ref float val1, ref float val2, ref float val3);
internal RootCallbackType rootCallback = RootCallback;
static internal void RootCallback(int id, int returnType, uint frameNumber, ref float val1, ref float val2, ref float val3)
{
WeakReference current = null;
LottieAnimationView currentView = null;
DynamicPropertyCallbackType currentCallback = null;
PropertyValue ret = null;
if (weakReferencesOfLottie.TryGetValue(id, out current))
{
if (current.TryGetTarget(out currentView))
{
if (currentView != null && currentView.InternalSavedDynamicPropertyCallbacks != null &&
currentView.InternalSavedDynamicPropertyCallbacks.TryGetValue(id, out currentCallback))
{
ret = currentCallback?.Invoke(returnType, frameNumber);
}
else
{
Tizen.Log.Error("NUI", "can't find the callback in LottieAnimationView, just return here!");
return;
}
}
else
{
Tizen.Log.Error("NUI", "can't find the callback in LottieAnimationView, just return here!");
return;
}
}
else
{
Tizen.Log.Error("NUI", "can't find LottieAnimationView by id, just return here!");
return;
}
switch (returnType)
{
case (int)(VectorProperty.FillColor):
case (int)(VectorProperty.StrokeColor):
Vector3 tmpVector3 = new Vector3(-1, -1, -1);
if ((ret != null) && ret.Get(tmpVector3))
{
val1 = tmpVector3.X;
val2 = tmpVector3.Y;
val3 = tmpVector3.Z;
}
tmpVector3.Dispose();
break;
case (int)(VectorProperty.TransformAnchor):
case (int)(VectorProperty.TransformPosition):
case (int)(VectorProperty.TransformScale):
Vector2 tmpVector2 = new Vector2(-1, -1);
if ((ret != null) && ret.Get(tmpVector2))
{
val1 = tmpVector2.X;
val2 = tmpVector2.Y;
}
tmpVector2.Dispose();
break;
case (int)(VectorProperty.FillOpacity):
case (int)(VectorProperty.StrokeOpacity):
case (int)(VectorProperty.StrokeWidth):
case (int)(VectorProperty.TransformRotation):
case (int)(VectorProperty.TransformOpacity):
float tmpFloat = -1;
if ((ret != null) && ret.Get(out tmpFloat))
{
val1 = tmpFloat;
}
break;
default:
//do nothing
break;
}
ret?.Dispose();
}
#endregion Internal
#region Private
// Collection of lottie-image-sensitive properties.
private static readonly List cachedLottieAnimationPropertyKeyList = new List {
ImageVisualProperty.LoopCount,
ImageVisualProperty.StopBehavior,
ImageVisualProperty.LoopingMode,
ImageVisualProperty.RedrawInScalingDown,
};
private struct states
{
internal string url;
internal int loopCount;
internal LoopingModeType loopMode;
internal StopBehaviorType stopEndAction;
internal int framePlayRangeMin;
internal int framePlayRangeMax;
internal int totalFrame;
internal float scale;
internal PlayStateType playState;
internal List> contentInfo;
internal string mark1, mark2;
internal bool redrawInScalingDown;
internal bool changed;
};
private states currentStates;
private const string tag = "NUITEST";
private event EventHandler finishedEventHandler;
private void OnFinished()
{
NUILog.Debug($"<[{GetId()}] OnFinished()>");
finishedEventHandler?.Invoke(this, null);
}
private void onVisualEventSignal(IntPtr targetView, int visualIndex, int signalId)
{
OnFinished();
if (targetView != IntPtr.Zero)
{
View v = Registry.GetManagedBaseHandleFromNativePtr(targetView) as View;
if (v != null)
{
NUILog.Debug($"targetView is not null! name={v.Name}");
}
else
{
NUILog.Debug($"target is something created from dali");
}
}
VisualEventSignalArgs e = new VisualEventSignalArgs();
e.VisualIndex = visualIndex;
e.SignalId = signalId;
visualEventSignalHandler?.Invoke(this, e);
NUILog.Debug($"<[{GetId()}] onVisualEventSignal()! visualIndex={visualIndex}, signalId={signalId}>");
}
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate void VisualEventSignalCallbackType(IntPtr targetView, int visualIndex, int signalId);
private VisualEventSignalCallbackType visualEventSignalCallback;
private EventHandler visualEventSignalHandler;
static private int dynamicPropertyCallbackId = 0;
//static private Dictionary dynamicPropertyCallbacks = new Dictionary();
static private Dictionary> weakReferencesOfLottie = new Dictionary>();
private void debugPrint()
{
NUILog.Debug($"===================================");
NUILog.Debug($"<[{GetId()}] get currentStates : url={currentStates.url}, loopCount={currentStates.loopCount}, \nframePlayRangeMin/Max({currentStates.framePlayRangeMin},{currentStates.framePlayRangeMax}) ");
NUILog.Debug($" get from Property : StopBehavior={StopBehavior}, LoopMode={LoopingMode}, LoopCount={LoopCount}, PlayState={PlayState}");
NUILog.Debug($" RedrawInScalingDown={RedrawInScalingDown} >");
NUILog.Debug($"===================================");
}
#endregion Private
}
///
/// A class containing frame informations for a LottieAnimationView.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public class LottieFrameInfo : ICloneable
{
///
/// Creates a new instance with a playing range.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public LottieFrameInfo(int startFrame, int endFrame)
{
StartFrame = startFrame;
EndFrame = endFrame;
}
///
/// Creates a new instance with a still image frame.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public LottieFrameInfo(int stillImageFrame) : this(stillImageFrame, stillImageFrame)
{
}
///
/// Create a new instance from a pair notation.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public static implicit operator LottieFrameInfo((int, int) pair)
{
return new LottieFrameInfo(pair.Item1, pair.Item2);
}
///
/// Create a new instance from an int value.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public static implicit operator LottieFrameInfo(int stillImageFrame)
{
return new LottieFrameInfo(stillImageFrame);
}
///
/// Create a new instance from string.
/// Possible input : "0, 10", "10"
///
[EditorBrowsable(EditorBrowsableState.Never)]
public static implicit operator LottieFrameInfo(string pair)
{
if (pair == null)
{
return null;
}
string[] parts = pair.Split(',');
if (parts.Length == 1)
{
return new LottieFrameInfo(Int32.Parse(parts[0].Trim(), CultureInfo.InvariantCulture));
}
else if (parts.Length == 2)
{
return new LottieFrameInfo(Int32.Parse(parts[0].Trim(), CultureInfo.InvariantCulture), Int32.Parse(parts[1].Trim(), CultureInfo.InvariantCulture));
}
Tizen.Log.Error("NUI", $"Can not convert string {pair} to LottieFrameInfo");
return null;
}
///
/// The start frame of the lottie animation.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public int StartFrame { get; }
///
/// The end frame of the lottie animation.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public int EndFrame { get; }
///
/// Create LottieFrameInfo struct with animation range information
///
[EditorBrowsable(EditorBrowsableState.Never)]
public static LottieFrameInfo CreateAnimationRange(int startFrame, int endFrame)
{
return new LottieFrameInfo(startFrame, endFrame);
}
///
/// Create LottieFrameInfo struct with still image information
///
[EditorBrowsable(EditorBrowsableState.Never)]
public static LottieFrameInfo CreateStillImage(int stillImageFrame)
{
return new LottieFrameInfo(stillImageFrame, stillImageFrame);
}
///
/// Inhouse API.
/// Whether this LottieFrameInfo represents one frame or more.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsStillImage()
{
return StartFrame == EndFrame;
}
///
/// Inhouse API.
/// Play specified LottieAnimationView with this frame information.
///
/// The target LottieAnimationView to play.
/// Whether go direct to the EndFrame. It is false by default.
[EditorBrowsable(EditorBrowsableState.Never)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062: Validate arguments of public methods", Justification = "The null checking is done by BeReadyToShow()")]
public void Show(LottieAnimationView lottieView, bool noPlay = false)
{
if (!BeReadyToShow(lottieView))
{
return;
}
lottieView.SetMinMaxFrame(StartFrame, Math.Min(EndFrame, lottieView.TotalFrame - 1));
if (IsStillImage() || noPlay)
{
lottieView.CurrentFrame = EndFrame;
}
else
{
lottieView.CurrentFrame = StartFrame;
lottieView.Play();
}
}
///
[EditorBrowsable(EditorBrowsableState.Never)]
public object Clone() => new LottieFrameInfo(StartFrame, EndFrame);
private bool BeReadyToShow(LottieAnimationView lottieView)
{
// Validate input lottieView
if (null == lottieView || lottieView.PlayState == LottieAnimationView.PlayStateType.Invalid)
{
return false;
}
// Stop if it was playing
if (lottieView.PlayState == LottieAnimationView.PlayStateType.Playing)
{
lottieView.Stop();
}
return true;
}
}
// This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
[EditorBrowsable(EditorBrowsableState.Never)]
public struct LottieAnimationViewDynamicProperty : IEquatable
{
[EditorBrowsable(EditorBrowsableState.Never)]
public string KeyPath { get; set; }
[EditorBrowsable(EditorBrowsableState.Never)]
public LottieAnimationView.VectorProperty Property { get; set; }
[EditorBrowsable(EditorBrowsableState.Never)]
public LottieAnimationView.DynamicPropertyCallbackType Callback { get; set; }
public override bool Equals(object obj)
{
if (obj is LottieAnimationViewDynamicProperty target)
{
if (KeyPath == target.KeyPath && Property == target.Property && Callback == target.Callback)
{
return true;
}
}
return false;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public static bool operator ==(LottieAnimationViewDynamicProperty left, LottieAnimationViewDynamicProperty right)
{
return left.Equals(right);
}
public static bool operator !=(LottieAnimationViewDynamicProperty left, LottieAnimationViewDynamicProperty right)
{
return !(left == right);
}
public bool Equals(LottieAnimationViewDynamicProperty other)
{
if (other != null)
{
if (KeyPath == other.KeyPath && Property == other.Property && Callback == other.Callback)
{
return true;
}
}
return false;
}
}
}