[NUI] Enables animation of view visual properties (#2625)
authorJiyun Yang <ji.yang@samsung.com>
Tue, 9 Feb 2021 06:59:35 +0000 (15:59 +0900)
committerlwc0917 <48237284+lwc0917@users.noreply.github.com>
Thu, 18 Feb 2021 03:15:29 +0000 (12:15 +0900)
This enables animation of
* CornerRadius
* BackgroundColor
* BoxShadow.Color
* BoxShadow.BlurRadius
* etc.

Signed-off-by: Jiyun Yang <ji.yang@samsung.com>
src/Tizen.NUI/src/internal/Common/PropertyHelper.cs
src/Tizen.NUI/src/internal/Interop/Interop.View.cs
src/Tizen.NUI/src/public/Animation/Animation.cs
src/Tizen.NUI/src/public/BaseComponents/View.cs

index 25b1455..634b1d1 100755 (executable)
@@ -1,5 +1,5 @@
 /*
- * Copyright(c) 2017 Samsung Electronics Co., Ltd.
+ * Copyright(c) 2021 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.
  * limitations under the License.
  *
  */
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
 using System.Text;
+using Tizen.NUI.BaseComponents;
 
 namespace Tizen.NUI
 {
+    using OOConverter = Converter<object, object>;
+    using PPConverter = Converter<PropertyValue, PropertyValue>;
+
     internal static class PropertyHelper
     {
+        private static readonly Dictionary<string, VisualPropertyData> visualPropertyTable = new Dictionary<string, VisualPropertyData>()
+        {
+            { "backgroundColor",        new VisualPropertyData(View.Property.BACKGROUND, ColorVisualProperty.MixColor, ObjectColorToVector3, PropertyValueColorToVector3,
+                                        new VisualPropertyData(View.Property.BACKGROUND, Visual.Property.Opacity, ObjectColorToAlpha, PropertyValueColorToAlpha)) },
+            { "backgroundOpacity",      new VisualPropertyData(View.Property.BACKGROUND, Visual.Property.Opacity, ObjectIntToFloat) },
+            { "boxShadow.BlurRadius",   new VisualPropertyData(View.Property.SHADOW, ColorVisualProperty.BlurRadius) },
+            { "boxShadow.Color",        new VisualPropertyData(View.Property.SHADOW, ColorVisualProperty.MixColor, ObjectColorToVector3, PropertyValueColorToVector3,
+                                        new VisualPropertyData(View.Property.SHADOW, Visual.Property.Opacity, ObjectColorToAlpha, PropertyValueColorToAlpha)) },
+            { "boxShadow.CornerRadius", new VisualPropertyData(View.Property.SHADOW, Visual.Property.CornerRadius, ObjectIntToFloat) },
+            { "boxShadow.Offset",       new VisualPropertyData(View.Property.SHADOW, (int)VisualTransformPropertyType.Offset) },
+            { "boxShadow.Opacity",      new VisualPropertyData(View.Property.SHADOW, Visual.Property.Opacity, ObjectIntToFloat) },
+            { "cornerRadius",           new VisualPropertyData(View.Property.BACKGROUND, Visual.Property.CornerRadius, ObjectIntToFloat, null,
+                                        new VisualPropertyData(View.Property.SHADOW, Visual.Property.CornerRadius, ObjectIntToFloat)) },
+            { "imageShadow.Offset",     new VisualPropertyData(View.Property.SHADOW, (int)VisualTransformPropertyType.Offset) },
+            { "shadow.CornerRadius",    new VisualPropertyData(View.Property.SHADOW, Visual.Property.CornerRadius, ObjectIntToFloat) },
+        };
+
+        static PropertyHelper() {}
+
         ///<summary>
         /// Returns a Property if stringProperty is a valid index
         ///</summary>
         internal static Property GetPropertyFromString(Animatable handle, string stringProperty)
         {
-            // Convert property string to be lowercase
-            StringBuilder sb = new StringBuilder(stringProperty);
-            sb[0] = (char)(sb[0] | 0x20);
-            string str = sb.ToString();
-
-            Property property = new Property(handle, str);
+            Property property = new Property(handle, LowerFirstLetter(stringProperty));
             if (property.propertyIndex == Property.InvalidIndex)
             {
                 throw new System.ArgumentException("string property is invalid");
@@ -38,5 +59,254 @@ namespace Tizen.NUI
 
             return property;
         }
+
+        ///<summary>
+        /// Returns a Property if stringProperty is a valid index
+        ///</summary>
+        internal static SearchResult Search(View view, string stringProperty)
+        {
+            var propertyName = LowerFirstLetter(stringProperty);
+
+            return SearchProperty(view, propertyName) ?? SearchVisualProperty(view, propertyName);
+        }
+
+        private static SearchResult SearchProperty(View view, string lowercasePropertyString)
+        {
+            Property property = new Property(view, lowercasePropertyString);
+            
+            if (property.propertyIndex == Property.InvalidIndex)
+            {
+                property.Dispose();
+                return null;
+            }
+
+            OOConverter converter = null;
+            if (view.GetPropertyType(property.propertyIndex).Equals(PropertyType.Float))
+            {
+                converter = ObjectIntToFloat;
+            }
+
+            return new SearchResult(property, converter);
+        }
+
+        private static SearchResult SearchVisualProperty(View view, string lowercasePropertyString)
+        {
+            if (visualPropertyTable.TryGetValue(lowercasePropertyString, out var found))
+            {
+                return GenerateVisualPropertySearchResult(view, found);
+            }
+
+            return null;
+        }
+
+        private static SearchResult GenerateVisualPropertySearchResult(View view, VisualPropertyData data)
+        {
+            var propertyIntPtr = Interop.View.GetVisualProperty(view.SwigCPtr, data.ViewPropertyIndex, data.VisualPropertyIndex);
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+
+            var property = new Property(propertyIntPtr, true);
+            if (property.propertyIndex == Property.InvalidIndex)
+            {
+                property.Dispose();
+                return null;
+            }
+
+            SearchResult result = new SearchResult(property, data.ObjectConverter, data.PropertyValueConverter);
+
+            while (data.RelatedData != null)
+            {
+                result.NextResult = GenerateVisualPropertySearchResult(view, data.RelatedData);
+                data = data.RelatedData;
+            }
+
+            return result;
+        }
+
+        private static string LowerFirstLetter(string original)
+        {
+            StringBuilder sb = new StringBuilder(original);
+            sb[0] = (char)(sb[0] | 0x20);
+            return sb.ToString();
+        }
+
+        private static object ObjectColorToVector3(object value)
+        {
+            if (value is Vector4)
+            {
+                var colorValue = value as Vector4;
+                return new Vector3(colorValue.R, colorValue.G, colorValue.B);
+            }
+            
+            if (value is Color)
+            {
+                var colorValue = value as Color;
+                return new Vector3(colorValue.R, colorValue.G, colorValue.B);
+            }
+
+            return null;
+        }
+
+        private static PropertyValue PropertyValueColorToVector3(PropertyValue value)
+        {
+            var valueType = value.GetType();
+
+            if (valueType != PropertyType.Vector4)
+            {
+                return null;
+            }
+
+            var colorValue = new Vector4();
+            value.Get(colorValue);
+            using (var v3 = new Vector3(colorValue.R, colorValue.G, colorValue.B))
+            {
+                colorValue.Dispose();
+                return new PropertyValue(v3);
+            }
+        }
+
+        private static object ObjectColorToAlpha(object value)
+        {
+            if (value is Vector4)
+            {
+                var colorValue = value as Vector4;
+                return colorValue.A;
+            }
+
+            if (value is Color)
+            {
+                var colorValue = value as Color;
+                return colorValue.A;
+            }
+
+            return null;
+        }
+
+        private static PropertyValue PropertyValueColorToAlpha(PropertyValue value)
+        {
+            var valueType = value.GetType();
+
+            if (valueType != PropertyType.Vector4)
+            {
+                return null;
+            }
+
+            using (var colorValue = new Vector4())
+            {
+                value.Get(colorValue);
+                return new PropertyValue(colorValue.A);
+            }
+        }
+
+        private static object ObjectIntToFloat(object value)
+        {
+            Type type = value.GetType();
+            if (type.Equals(typeof(System.Int32)) || type.Equals(typeof(int)))
+            {
+                return (float)((int)value);
+            }
+
+            return value;
+        }
+
+        internal class SearchResult : Disposable
+        {
+            private readonly OOConverter objectConverter;
+            private readonly PPConverter propertyValueConverter;
+
+            internal SearchResult(Property property, OOConverter objectConverter = null, PPConverter propertyValueConverter = null)
+            {
+                this.objectConverter = objectConverter;
+                this.propertyValueConverter = propertyValueConverter;
+                Property = property;
+            }
+
+            internal Property Property { get; }
+
+            internal SearchResult NextResult { get; set; }
+
+            internal PropertyValue RefineValue(object value)
+            {
+                Debug.Assert(Property != null && value != null);
+
+                var refined = value;
+
+                if (objectConverter != null)
+                {
+                    refined = objectConverter(value);
+                }
+
+                if (refined == null)
+                {
+                    return null;
+                }
+
+                return PropertyValue.CreateFromObject(refined);
+            }
+
+            internal KeyFrames RefineKeyFrames(KeyFrames keyFrames)
+            {
+                Debug.Assert(keyFrames != null);
+
+                var refined = keyFrames;
+                if (propertyValueConverter != null)
+                {
+                    // TODO Enable this code when csharp-binder is ready
+                    // refined = new KeyFrames();
+                    // for (uint i = 0; i < keyFrames.Count; i++)
+                    // {
+                    //     var keyFrame = keyFrames.GetKeyFrame(i);
+                    //     var newKeyFrame = propertyValueConverter(keyFrame);
+                    //     if (newKeyFrame == null)
+                    //     {
+                    //         return null;
+                    //     }
+                    //     refined.Add(newKeyFrame);
+                    // }
+                }
+
+                return refined;
+            }
+
+            /// <summary>
+            /// Dispose
+            /// </summary>
+            protected override void Dispose(DisposeTypes type)
+            {
+                if (disposed)
+                {
+                    return;
+                }
+
+                if (type == DisposeTypes.Explicit)
+                {
+                    Property.Dispose();
+                    NextResult?.Dispose();
+                }
+
+                base.Dispose(type);
+            }
+        }
+
+        private class VisualPropertyData
+        {
+            internal VisualPropertyData(int viewPropertyIndex, int visualPropertyIndex, OOConverter objectConverter = null, PPConverter propertyValueConverter = null, VisualPropertyData relatedData = null)
+            {
+                ViewPropertyIndex = viewPropertyIndex;
+                VisualPropertyIndex = visualPropertyIndex;
+                ObjectConverter = objectConverter;
+                PropertyValueConverter = propertyValueConverter;
+                RelatedData = relatedData;
+            }
+
+            internal int ViewPropertyIndex { get; }
+
+            internal int VisualPropertyIndex { get; }
+
+            internal OOConverter ObjectConverter { get; }
+
+            internal PPConverter PropertyValueConverter { get; }
+
+            internal VisualPropertyData RelatedData { get; }
+        }
     }
 }
index d204d01..56a07a3 100755 (executable)
@@ -50,6 +50,9 @@ namespace Tizen.NUI
             [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_View_DoAction")]
             public static extern void DoAction(global::System.Runtime.InteropServices.HandleRef jarg1, int jarg2, int jarg3, global::System.Runtime.InteropServices.HandleRef jarg4);
 
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_View_GetVisualProperty")]
+            public static extern global::System.IntPtr GetVisualProperty(global::System.Runtime.InteropServices.HandleRef jarg1, int jarg2, int jarg3);
+
             [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_View_SWIGUpcast")]
             public static extern global::System.IntPtr Upcast(global::System.IntPtr jarg1);
 
index ba73b32..85dfe92 100755 (executable)
@@ -1,5 +1,5 @@
 /*
- * Copyright(c) 2019 Samsung Electronics Co., Ltd.
+ * Copyright(c) 2021 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.
@@ -632,7 +632,8 @@ namespace Tizen.NUI
         /// <param name="property">The target property to animate.</param>
         /// <param name="relativeValue">The property value will change by this amount.</param>
         /// <param name="alphaFunction">The alpha function to apply.</param>
-        /// <exception cref="ArgumentNullException"> Thrown when target or relativeValue is null. </exception>
+        /// <exception cref="ArgumentNullException"> Thrown when target or property or relativeValue is null. </exception>
+        /// <exception cref="ArgumentException"> Thrown when it failed to find a property from given string or the given relativeValue is invalid format. </exception>
         /// <since_tizen> 3 </since_tizen>
         public void AnimateBy(View target, string property, object relativeValue, AlphaFunction alphaFunction = null)
         {
@@ -640,26 +641,31 @@ namespace Tizen.NUI
             {
                 throw new ArgumentNullException(nameof(target));
             }
-            else if (relativeValue == null)
+            if (property == null)
             {
-                throw new ArgumentNullException(nameof(relativeValue));
+                throw new ArgumentNullException(nameof(property));
             }
-
-            Property _prop = PropertyHelper.GetPropertyFromString(target, property);
-            relativeValue = AvoidFloatPropertyHasIntegerValue(target, _prop, relativeValue);
-            PropertyValue val = PropertyValue.CreateFromObject(relativeValue);
-
-            if (alphaFunction != null)
+            if (relativeValue == null)
             {
-                AnimateBy(_prop, val, alphaFunction);
+                throw new ArgumentNullException(nameof(relativeValue));
             }
-            else
+
+            using (var result = PropertyHelper.Search(target, property))
             {
-                AnimateBy(_prop, val);
-            }
+                if (result == null)
+                {
+                    throw new ArgumentException("string property is invalid");
+                }
 
-            val.Dispose();
-            _prop.Dispose();
+                var current = result;
+                while (current != null)
+                {
+                    var targetValue = current.RefineValue(relativeValue) ?? throw new ArgumentException("Invalid " + nameof(relativeValue));
+                    AnimateBy(current.Property, targetValue, alphaFunction);
+                    targetValue.Dispose();
+                    current = current.NextResult;
+                }
+            }
         }
 
         /// <summary>
@@ -671,7 +677,8 @@ namespace Tizen.NUI
         /// <param name="startTime">The start time of the animation.</param>
         /// <param name="endTime">The end time of the animation.</param>
         /// <param name="alphaFunction">The alpha function to apply.</param>
-        /// <exception cref="ArgumentNullException"> Thrown when target or relativeValue is null. </exception>
+        /// <exception cref="ArgumentNullException"> Thrown when target or property or relativeValue is null. </exception>
+        /// <exception cref="ArgumentException"> Thrown when it failed to find a property from given string or the given relativeValue is invalid format. </exception>
         /// <since_tizen> 3 </since_tizen>
         public void AnimateBy(View target, string property, object relativeValue, int startTime, int endTime, AlphaFunction alphaFunction = null)
         {
@@ -679,30 +686,32 @@ namespace Tizen.NUI
             {
                 throw new ArgumentNullException(nameof(target));
             }
-            else if (relativeValue == null)
+            if (property == null)
             {
-                throw new ArgumentNullException(nameof(relativeValue));
+                throw new ArgumentNullException(nameof(property));
             }
-
-            Property _prop = PropertyHelper.GetPropertyFromString(target, property);
-            relativeValue = AvoidFloatPropertyHasIntegerValue(target, _prop, relativeValue);
-            PropertyValue val = PropertyValue.CreateFromObject(relativeValue);
-
-            if (alphaFunction != null)
+            if (relativeValue == null)
             {
-                Tizen.NUI.TimePeriod time = new Tizen.NUI.TimePeriod(MilliSecondsToSeconds(startTime), MilliSecondsToSeconds(endTime - startTime));
-                AnimateBy(_prop, val, alphaFunction, time);
-                time.Dispose();
+                throw new ArgumentNullException(nameof(relativeValue));
             }
-            else
+
+            using (var result = PropertyHelper.Search(target, property))
             {
-                Tizen.NUI.TimePeriod time = new Tizen.NUI.TimePeriod(MilliSecondsToSeconds(startTime), MilliSecondsToSeconds(endTime - startTime));
-                AnimateBy(_prop, val, time);
-                time.Dispose();
-            }
+                if (result == null)
+                {
+                    throw new ArgumentException("string property is invalid");
+                }
 
-            val.Dispose();
-            _prop.Dispose();
+                var current = result;
+                using (var time = new TimePeriod(MilliSecondsToSeconds(startTime), MilliSecondsToSeconds(endTime - startTime)))
+                while (current != null)
+                {
+                    var targetValue = current.RefineValue(relativeValue) ?? throw new ArgumentException("Invalid " + nameof(relativeValue));
+                    AnimateBy(current.Property, targetValue, alphaFunction, time);
+                    targetValue.Dispose();
+                    current = current.NextResult;
+                }
+            }
         }
 
         /// <summary>
@@ -712,7 +721,8 @@ namespace Tizen.NUI
         /// <param name="property">The target property to animate.</param>
         /// <param name="destinationValue">The destination value.</param>
         /// <param name="alphaFunction">The alpha function to apply.</param>
-        /// <exception cref="ArgumentNullException"> Thrown when target or destinationValue is null. </exception>
+        /// <exception cref="ArgumentNullException"> Thrown when target or property or destinationValue is null. </exception>
+        /// <exception cref="ArgumentException"> Thrown when it failed to find a property from given string or the given destinationValue is invalid format. </exception>
         /// <since_tizen> 3 </since_tizen>
         public void AnimateTo(View target, string property, object destinationValue, AlphaFunction alphaFunction = null)
         {
@@ -720,26 +730,31 @@ namespace Tizen.NUI
             {
                 throw new ArgumentNullException(nameof(target));
             }
-            else if (destinationValue == null)
+            if (property == null)
             {
-                throw new ArgumentNullException(nameof(destinationValue));
+                throw new ArgumentNullException(nameof(property));
             }
-
-            Property _prop = PropertyHelper.GetPropertyFromString(target, property);
-            destinationValue = AvoidFloatPropertyHasIntegerValue(target, _prop, destinationValue);
-            PropertyValue val = PropertyValue.CreateFromObject(destinationValue);
-
-            if (alphaFunction != null)
+            if (destinationValue == null)
             {
-                AnimateTo(_prop, val, alphaFunction);
+                throw new ArgumentNullException(nameof(destinationValue));
             }
-            else
+
+            using (var result = PropertyHelper.Search(target, property))
             {
-                AnimateTo(_prop, val);
-            }
+                if (result == null)
+                {
+                    throw new ArgumentException("string property is invalid");
+                }
 
-            val.Dispose();
-            _prop.Dispose();
+                var current = result;
+                while (current != null)
+                {
+                    var targetValue = current.RefineValue(destinationValue) ?? throw new ArgumentException("Invalid " + nameof(destinationValue));
+                    AnimateTo(current.Property, targetValue, alphaFunction);
+                    targetValue.Dispose();
+                    current = current.NextResult;
+                }
+            }
         }
 
         /// <summary>
@@ -818,7 +833,8 @@ namespace Tizen.NUI
         /// <param name="startTime">The start time of the animation.</param>
         /// <param name="endTime">The end time of the animation.</param>
         /// <param name="alphaFunction">The alpha function to apply.</param>
-        /// <exception cref="ArgumentNullException"> Thrown when target or destinationValue is null. </exception>
+        /// <exception cref="ArgumentNullException"> Thrown when target or property or destinationValue is null. </exception>
+        /// <exception cref="ArgumentException"> Thrown when it failed to find a property from given string or the given destinationValue is invalid format. </exception>
         /// <since_tizen> 3 </since_tizen>
         public void AnimateTo(View target, string property, object destinationValue, int startTime, int endTime, AlphaFunction alphaFunction = null)
         {
@@ -826,30 +842,32 @@ namespace Tizen.NUI
             {
                 throw new ArgumentNullException(nameof(target));
             }
-            else if (destinationValue == null)
+            if (property == null)
             {
-                throw new ArgumentNullException(nameof(destinationValue));
+                throw new ArgumentNullException(nameof(property));
             }
-
-            Property _prop = PropertyHelper.GetPropertyFromString(target, property);
-            destinationValue = AvoidFloatPropertyHasIntegerValue(target, _prop, destinationValue);
-            PropertyValue val = PropertyValue.CreateFromObject(destinationValue);
-
-            if (alphaFunction != null)
+            if (destinationValue == null)
             {
-                Tizen.NUI.TimePeriod time = new Tizen.NUI.TimePeriod(MilliSecondsToSeconds(startTime), MilliSecondsToSeconds(endTime - startTime));
-                AnimateTo(_prop, val, alphaFunction, time);
-                time.Dispose();
+                throw new ArgumentNullException(nameof(destinationValue));
             }
-            else
+
+            using (var result = PropertyHelper.Search(target, property))
             {
-                Tizen.NUI.TimePeriod time = new Tizen.NUI.TimePeriod(MilliSecondsToSeconds(startTime), MilliSecondsToSeconds(endTime - startTime));
-                AnimateTo(_prop, val, time);
-                time.Dispose();
-            }
+                if (result == null)
+                {
+                    throw new ArgumentException("string property is invalid");
+                }
 
-            val.Dispose();
-            _prop.Dispose();
+                var current = result;
+                using (var time = new TimePeriod(MilliSecondsToSeconds(startTime), MilliSecondsToSeconds(endTime - startTime)))
+                while (current != null)
+                {
+                    var targetValue = current.RefineValue(destinationValue) ?? throw new ArgumentException("Invalid " + nameof(destinationValue));
+                    AnimateTo(current.Property, targetValue, alphaFunction, time);
+                    targetValue.Dispose();
+                    current = current.NextResult;
+                }
+            }
         }
 
         /// <summary>
@@ -860,26 +878,39 @@ namespace Tizen.NUI
         /// <param name="keyFrames">The set of time or value pairs between which to animate.</param>
         /// <param name="interpolation">The method used to interpolate between values.</param>
         /// <param name="alphaFunction">The alpha function to apply.</param>
+        /// <exception cref="ArgumentNullException"> Thrown when target or property or keyFrames is null. </exception>
+        /// <exception cref="ArgumentException"> Thrown when it failed to find a property from given string or the given keyFrames has invalid value. </exception>
         /// <since_tizen> 3 </since_tizen>
         public void AnimateBetween(View target, string property, KeyFrames keyFrames, Interpolation interpolation = Interpolation.Linear, AlphaFunction alphaFunction = null)
         {
-            Property _prop = PropertyHelper.GetPropertyFromString(target, property);
-
-            if (_prop.propertyIndex == Property.InvalidIndex)
+            if (target == null)
             {
-                throw new System.ArgumentException("second argument string property is invalid parameter!");
+                throw new ArgumentNullException(nameof(target));
             }
-
-            if (alphaFunction != null)
+            if (property == null)
             {
-                AnimateBetween(_prop, keyFrames, alphaFunction, interpolation);
+                throw new ArgumentNullException(nameof(property));
             }
-            else
+            if (keyFrames == null)
             {
-                AnimateBetween(_prop, keyFrames, interpolation);
+                throw new ArgumentNullException(nameof(keyFrames));
             }
 
-            _prop.Dispose();
+            using (var result = PropertyHelper.Search(target, property))
+            {
+                if (result == null)
+                {
+                    throw new ArgumentException("string property is invalid");
+                }
+
+                var current = result;
+                while (current != null)
+                {
+                    // NOTE Do not dispose keyFrames object returned by GetRefinedKeyFrames() here.
+                    AnimateBetween(current.Property, current.RefineKeyFrames(keyFrames) ?? throw new ArgumentException("Invalid " + nameof(keyFrames)), alphaFunction, interpolation);   
+                    current = current.NextResult;
+                }
+            }
         }
 
         /// <summary>
@@ -892,23 +923,40 @@ namespace Tizen.NUI
         /// <param name="endTime">The end time of animation in milliseconds.</param>
         /// <param name="interpolation">The method used to interpolate between values.</param>
         /// <param name="alphaFunction">The alpha function to apply.</param>
+        /// <exception cref="ArgumentNullException"> Thrown when target or property or keyFrames is null. </exception>
+        /// <exception cref="ArgumentException"> Thrown when it failed to find a property from given string or the given keyFrames has invalid value. </exception>
         /// <since_tizen> 3 </since_tizen>
         public void AnimateBetween(View target, string property, KeyFrames keyFrames, int startTime, int endTime, Interpolation interpolation = Interpolation.Linear, AlphaFunction alphaFunction = null)
         {
-            Property _prop = PropertyHelper.GetPropertyFromString(target, property);
-
-            Tizen.NUI.TimePeriod time = new Tizen.NUI.TimePeriod(MilliSecondsToSeconds(startTime), MilliSecondsToSeconds(endTime - startTime));
-            if (alphaFunction != null)
+            if (target == null)
             {
-                AnimateBetween(_prop, keyFrames, alphaFunction, time, interpolation);
+                throw new ArgumentNullException(nameof(target));
             }
-            else
+            if (property == null)
             {
-                AnimateBetween(_prop, keyFrames, time, interpolation);
+                throw new ArgumentNullException(nameof(property));
+            }
+            if (keyFrames == null)
+            {
+                throw new ArgumentNullException(nameof(keyFrames));
             }
 
-            time.Dispose();
-            _prop.Dispose();
+            using (var result = PropertyHelper.Search(target, property))
+            {
+                if (result == null)
+                {
+                    throw new ArgumentException("string property is invalid");
+                }
+
+                var current = result;
+                using (var time = new TimePeriod(MilliSecondsToSeconds(startTime), MilliSecondsToSeconds(endTime - startTime)))
+                while (current != null)
+                {
+                    // NOTE Do not dispose keyFrames object returned by GetRefinedKeyFrames() here.
+                    AnimateBetween(current.Property, current.RefineKeyFrames(keyFrames) ?? throw new ArgumentException("Invalid " + nameof(keyFrames)), alphaFunction, time, interpolation);   
+                    current = current.NextResult;
+                }
+            }
         }
 
         /// <summary>
@@ -1341,51 +1389,55 @@ namespace Tizen.NUI
             return ret;
         }
 
-        internal void AnimateBy(Property target, PropertyValue relativeValue)
-        {
-            Interop.Animation.AnimateBy(SwigCPtr, Property.getCPtr(target), PropertyValue.getCPtr(relativeValue));
-            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
-        }
-
         internal void AnimateBy(Property target, PropertyValue relativeValue, AlphaFunction alpha)
         {
-            Interop.Animation.AnimateByAlphaFunction(SwigCPtr, Property.getCPtr(target), PropertyValue.getCPtr(relativeValue), AlphaFunction.getCPtr(alpha));
-            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
-        }
-
-        internal void AnimateBy(Property target, PropertyValue relativeValue, TimePeriod period)
-        {
-            Interop.Animation.AnimateByTimePeriod(SwigCPtr, Property.getCPtr(target), PropertyValue.getCPtr(relativeValue), TimePeriod.getCPtr(period));
+            if (alpha == null)
+            {
+                Interop.Animation.AnimateBy(SwigCPtr, Property.getCPtr(target), PropertyValue.getCPtr(relativeValue));
+            }
+            else
+            {
+                Interop.Animation.AnimateByAlphaFunction(SwigCPtr, Property.getCPtr(target), PropertyValue.getCPtr(relativeValue), AlphaFunction.getCPtr(alpha));
+            }
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
 
         internal void AnimateBy(Property target, PropertyValue relativeValue, AlphaFunction alpha, TimePeriod period)
         {
-            Interop.Animation.AnimateBy(SwigCPtr, Property.getCPtr(target), PropertyValue.getCPtr(relativeValue), AlphaFunction.getCPtr(alpha), TimePeriod.getCPtr(period));
-            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
-        }
-
-        internal void AnimateTo(Property target, PropertyValue destinationValue)
-        {
-            Interop.Animation.AnimateTo(SwigCPtr, Property.getCPtr(target), PropertyValue.getCPtr(destinationValue));
+            if (alpha == null)
+            {
+                Interop.Animation.AnimateByTimePeriod(SwigCPtr, Property.getCPtr(target), PropertyValue.getCPtr(relativeValue), TimePeriod.getCPtr(period));
+            }
+            else
+            {
+                Interop.Animation.AnimateBy(SwigCPtr, Property.getCPtr(target), PropertyValue.getCPtr(relativeValue), AlphaFunction.getCPtr(alpha), TimePeriod.getCPtr(period));
+            }
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
 
         internal void AnimateTo(Property target, PropertyValue destinationValue, AlphaFunction alpha)
         {
-            Interop.Animation.AnimateToAlphaFunction(SwigCPtr, Property.getCPtr(target), PropertyValue.getCPtr(destinationValue), AlphaFunction.getCPtr(alpha));
-            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
-        }
-
-        internal void AnimateTo(Property target, PropertyValue destinationValue, TimePeriod period)
-        {
-            Interop.Animation.AnimateToTimePeriod(SwigCPtr, Property.getCPtr(target), PropertyValue.getCPtr(destinationValue), TimePeriod.getCPtr(period));
+            if (alpha == null)
+            {
+                Interop.Animation.AnimateTo(SwigCPtr, Property.getCPtr(target), PropertyValue.getCPtr(destinationValue));
+            }
+            else
+            {
+                Interop.Animation.AnimateToAlphaFunction(SwigCPtr, Property.getCPtr(target), PropertyValue.getCPtr(destinationValue), AlphaFunction.getCPtr(alpha));
+            }
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
 
         internal void AnimateTo(Property target, PropertyValue destinationValue, AlphaFunction alpha, TimePeriod period)
         {
-            Interop.Animation.AnimateTo(SwigCPtr, Property.getCPtr(target), PropertyValue.getCPtr(destinationValue), AlphaFunction.getCPtr(alpha), TimePeriod.getCPtr(period));
+            if (alpha == null)
+            {
+                Interop.Animation.AnimateToTimePeriod(SwigCPtr, Property.getCPtr(target), PropertyValue.getCPtr(destinationValue), TimePeriod.getCPtr(period));
+            }
+            else
+            {
+                Interop.Animation.AnimateTo(SwigCPtr, Property.getCPtr(target), PropertyValue.getCPtr(destinationValue), AlphaFunction.getCPtr(alpha), TimePeriod.getCPtr(period));
+            }
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
 
@@ -1395,12 +1447,6 @@ namespace Tizen.NUI
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
 
-        internal void AnimateBetween(Property target, KeyFrames keyFrames, Animation.Interpolation interpolation)
-        {
-            Interop.Animation.AnimateBetween(SwigCPtr, Property.getCPtr(target), KeyFrames.getCPtr(keyFrames), (int)interpolation);
-            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
-        }
-
         internal void AnimateBetween(Property target, KeyFrames keyFrames, AlphaFunction alpha)
         {
             Interop.Animation.AnimateBetweenAlphaFunction(SwigCPtr, Property.getCPtr(target), KeyFrames.getCPtr(keyFrames), AlphaFunction.getCPtr(alpha));
@@ -1409,7 +1455,14 @@ namespace Tizen.NUI
 
         internal void AnimateBetween(Property target, KeyFrames keyFrames, AlphaFunction alpha, Animation.Interpolation interpolation)
         {
-            Interop.Animation.AnimateBetweenAlphaFunctionInterpolation(SwigCPtr, Property.getCPtr(target), KeyFrames.getCPtr(keyFrames), AlphaFunction.getCPtr(alpha), (int)interpolation);
+            if (alpha == null)
+            {
+                Interop.Animation.AnimateBetween(SwigCPtr, Property.getCPtr(target), KeyFrames.getCPtr(keyFrames), (int)interpolation);
+            }
+            else
+            {
+                Interop.Animation.AnimateBetweenAlphaFunctionInterpolation(SwigCPtr, Property.getCPtr(target), KeyFrames.getCPtr(keyFrames), AlphaFunction.getCPtr(alpha), (int)interpolation);
+            }
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
 
@@ -1419,12 +1472,6 @@ namespace Tizen.NUI
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
 
-        internal void AnimateBetween(Property target, KeyFrames keyFrames, TimePeriod period, Animation.Interpolation interpolation)
-        {
-            Interop.Animation.AnimateBetweenTimePeriodInterpolation(SwigCPtr, Property.getCPtr(target), KeyFrames.getCPtr(keyFrames), TimePeriod.getCPtr(period), (int)interpolation);
-            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
-        }
-
         internal void AnimateBetween(Property target, KeyFrames keyFrames, AlphaFunction alpha, TimePeriod period)
         {
             Interop.Animation.AnimateBetween(SwigCPtr, Property.getCPtr(target), KeyFrames.getCPtr(keyFrames), AlphaFunction.getCPtr(alpha), TimePeriod.getCPtr(period));
@@ -1433,7 +1480,14 @@ namespace Tizen.NUI
 
         internal void AnimateBetween(Property target, KeyFrames keyFrames, AlphaFunction alpha, TimePeriod period, Animation.Interpolation interpolation)
         {
-            Interop.Animation.AnimateBetween(SwigCPtr, Property.getCPtr(target), KeyFrames.getCPtr(keyFrames), AlphaFunction.getCPtr(alpha), TimePeriod.getCPtr(period), (int)interpolation);
+            if (alpha == null)
+            {
+                Interop.Animation.AnimateBetweenTimePeriodInterpolation(SwigCPtr, Property.getCPtr(target), KeyFrames.getCPtr(keyFrames), TimePeriod.getCPtr(period), (int)interpolation);
+            }
+            else
+            {
+                Interop.Animation.AnimateBetween(SwigCPtr, Property.getCPtr(target), KeyFrames.getCPtr(keyFrames), AlphaFunction.getCPtr(alpha), TimePeriod.getCPtr(period), (int)interpolation);
+            }
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
 
@@ -1542,20 +1596,5 @@ namespace Tizen.NUI
         {
             return (int)(sec * 1000);
         }
-
-        private object AvoidFloatPropertyHasIntegerValue(View target, Property property, object value)
-        {
-            PropertyType propertyType = target.GetPropertyType(property.propertyIndex);
-            if (propertyType.Equals(PropertyType.Float))
-            {
-                System.Type type = value.GetType();
-                if (type.Equals(typeof(System.Int32)) || type.Equals(typeof(int)))
-                {
-                    int num = (int)value;
-                    value = (float)num;
-                }
-            }
-            return value;
-        }
     }
 }
index ac055fb..3917635 100755 (executable)
@@ -289,7 +289,15 @@ namespace Tizen.NUI.BaseComponents
         /// The mutually exclusive with "backgroundImage" and "background" type Vector4.
         /// </summary>
         /// <remarks>
+        /// <para>
         /// The property cascade chaining set is possible. For example, this (view.BackgroundColor.X = 0.1f;) is possible.
+        /// </para>
+        /// <para>
+        /// Animatable - This property can be animated using <c>Animation</c> class.
+        /// <code>
+        /// animation.AnimateTo(view, "BackgroundColor", new Color(r, g, b, a));
+        /// </code>
+        /// </para>
         /// </remarks>
         /// <since_tizen> 3 </since_tizen>
         public Color BackgroundColor
@@ -379,6 +387,16 @@ namespace Tizen.NUI.BaseComponents
         /// <remarks>
         /// The mutually exclusive with "BoxShadow".
         /// </remarks>
+        /// <remarks>
+        /// <para>
+        /// Animatable - This property can be animated using <c>Animation</c> class.
+        /// To animate this property, specify a sub-property with separator ".", for example, "ImageShadow.Offset".
+        /// <code>
+        /// animation.AnimateTo(view, "ImageShadow.Offset", new Vector2(10, 10));
+        /// </code>
+        /// Animatable sub-property : Offset.
+        /// </para>
+        /// </remarks>
         [EditorBrowsable(EditorBrowsableState.Never)]
         public ImageShadow ImageShadow
         {
@@ -408,6 +426,16 @@ namespace Tizen.NUI.BaseComponents
         /// <remarks>
         /// The mutually exclusive with "ImageShadow".
         /// </remarks>
+        /// <remarks>
+        /// <para>
+        /// Animatable - This property can be animated using <c>Animation</c> class.
+        /// To animate this property, specify a sub-property with separator ".", for example, "BoxShadow.BlurRadius".
+        /// <code>
+        /// animation.AnimateTo(view, "BoxShadow.BlurRadius", 10.0f);
+        /// </code>
+        /// Animatable sub-property : Offset, Color, BlurRadius.
+        /// </para>
+        /// </remarks>
         [EditorBrowsable(EditorBrowsableState.Never)]
         public Shadow BoxShadow
         {
@@ -432,6 +460,11 @@ namespace Tizen.NUI.BaseComponents
         /// This will rounds background and shadow edges.
         /// Note that, an image background (or shadow) may not have rounded corners if it uses a Border property.
         /// </summary>
+        /// <remarks>
+        /// <para>
+        /// Animatable - This property can be animated using <c>Animation</c> class.
+        /// </para>
+        /// </remarks>
         [EditorBrowsable(EditorBrowsableState.Never)]
         public float CornerRadius
         {