[NUI] Implement shadow in View (#1236)
authorJiyun Yang <ji.yang@samsung.com>
Tue, 24 Dec 2019 08:12:25 +0000 (17:12 +0900)
committerGitHub <noreply@github.com>
Tue, 24 Dec 2019 08:12:25 +0000 (17:12 +0900)
Signed-off-by: Jiyun Yang <ji.yang@samsung.com>
14 files changed:
src/Tizen.NUI/src/internal/Interop/Interop.PropertyMap.cs
src/Tizen.NUI/src/internal/Interop/Interop.ViewProperty.cs
src/Tizen.NUI/src/public/BaseComponents/View.cs
src/Tizen.NUI/src/public/BaseComponents/ViewBindableProperty.cs
src/Tizen.NUI/src/public/BaseComponents/ViewEnum.cs
src/Tizen.NUI/src/public/BaseComponents/ViewEvent.cs
src/Tizen.NUI/src/public/PropertyMap.cs
src/Tizen.NUI/src/public/PropertyValue.cs
src/Tizen.NUI/src/public/Rectangle.cs
src/Tizen.NUI/src/public/Size.cs
src/Tizen.NUI/src/public/Vector2.cs
src/Tizen.NUI/src/public/ViewProperty/ImageShadow.cs [new file with mode: 0644]
src/Tizen.NUI/src/public/ViewProperty/Shadow.cs [new file with mode: 0644]
src/Tizen.NUI/src/public/ViewProperty/TransformablePropertyMap.cs [new file with mode: 0644]

index de02849..f4d4824 100755 (executable)
@@ -76,6 +76,12 @@ namespace Tizen.NUI
 
             [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_Property_Map_Assign")]
             public static extern global::System.IntPtr Property_Map_Assign(global::System.Runtime.InteropServices.HandleRef jarg1, global::System.Runtime.InteropServices.HandleRef jarg2);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_Property_Map_SetValue_StringKey")]
+            public static extern void Property_Map_SetValue_StringKey(global::System.Runtime.InteropServices.HandleRef jarg1, string jarg2, global::System.Runtime.InteropServices.HandleRef jarg3);
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_Property_Map_SetValue_IntKey")]
+            public static extern void Property_Map_SetValue_IntKey(global::System.Runtime.InteropServices.HandleRef jarg1, int jarg2, global::System.Runtime.InteropServices.HandleRef jarg3);
         }
     }
 }
\ No newline at end of file
index 5d8205d..e52a6a5 100755 (executable)
@@ -52,6 +52,9 @@ namespace Tizen.NUI
 
             [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_View_Property_UPDATE_SIZE_HINT_get")]
             public static extern int View_Property_UPDATE_SIZE_HINT_get();
+
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_View_Property_SHADOW_get")]
+            public static extern int View_Property_SHADOW_get();
         }
     }
 }
\ No newline at end of file
index 71ba247..62641ab 100755 (executable)
@@ -59,6 +59,10 @@ namespace Tizen.NUI.BaseComponents
         private string[] transitionNames;
         private Rectangle backgroundImageBorder;
 
+        private ImageShadow imageShadow;
+
+        private Shadow boxShadow;
+
         internal Size2D sizeSetExplicitly = new Size2D(); // Store size set by API, will be used in place of NaturalSize if not set.
 
         private ViewStyle viewStyle;
@@ -295,6 +299,52 @@ namespace Tizen.NUI.BaseComponents
         }
 
         /// <summary>
+        /// Describes a shadow as an image for View.
+        /// It is null by default.
+        /// </summary>
+        /// <remarks>
+        /// The mutually exclusive with "BoxShadow".
+        /// If it is not null, the "BoxShadow" property will be ignored.
+        /// </remarks>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ImageShadow ImageShadow
+        {
+            get
+            {
+                return (ImageShadow)GetValue(ImageShadowProperty);
+            }
+            set
+            {
+                value.OnPropertyChanged = OnImageShadowChanged;
+                SetValue(ImageShadowProperty, value);
+                NotifyPropertyChanged();
+            }
+        }
+
+        /// <summary>
+        /// Describes a box shaped shadow drawing for View.
+        /// It is null by default.
+        /// </summary>
+        /// <remarks>
+        /// The mutually exclusive with "ImageShadow".
+        /// If the "ImageShadow" is not null, this property will be ignored.
+        /// </remarks>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public Shadow BoxShadow
+        {
+            get
+            {
+                return (Shadow)GetValue(BoxShadowProperty);
+            }
+            set
+            {
+                value.OnPropertyChanged = OnBoxShadowChanged;
+                SetValue(BoxShadowProperty, value);
+                NotifyPropertyChanged();
+            }
+        }
+
+        /// <summary>
         /// The current state of the view.
         /// </summary>
         /// <since_tizen> 3 </since_tizen>
index 55cc478..616e7b6 100755 (executable)
@@ -1349,6 +1349,39 @@ namespace Tizen.NUI.BaseComponents
         });
 
         /// <summary>
+        /// ImageShadow Property
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly BindableProperty ImageShadowProperty = BindableProperty.Create("ImageShadow", typeof(ImageShadow), typeof(View), null, propertyChanged: (bindable, oldValue, newValue) =>
+        {
+            var view = (View)bindable;
+            view.imageShadow = (ImageShadow)newValue;
+            Tizen.NUI.Object.SetProperty(view.swigCPtr, Interop.ViewProperty.View_Property_SHADOW_get(), ImageShadow.ToPropertyValue(view.imageShadow));
+        },
+        defaultValueCreator: (bindable) =>
+        {
+            return ((View)bindable).imageShadow;
+        });
+
+        /// <summary>
+        /// Shadow Property
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly BindableProperty BoxShadowProperty = BindableProperty.Create("BoxShadow", typeof(Shadow), typeof(View), null, propertyChanged: (bindable, oldValue, newValue) =>
+        {
+            var view = (View)bindable;
+            view.boxShadow = (Shadow)newValue;
+            if (view.imageShadow == null)
+            {
+                Tizen.NUI.Object.SetProperty(view.swigCPtr, Interop.ViewProperty.View_Property_SHADOW_get(), Shadow.ToPropertyValue(view.boxShadow));
+            }
+        },
+        defaultValueCreator: (bindable) =>
+        {
+            return ((View)bindable).boxShadow;
+        });
+
+        /// <summary>
         /// XamlStyleProperty
         /// </summary>
         [EditorBrowsable(EditorBrowsableState.Never)]
index 90c996f..5360849 100755 (executable)
@@ -205,6 +205,7 @@ namespace Tizen.NUI.BaseComponents
             internal static readonly int LAYOUT_DIRECTION = Interop.ActorProperty.Actor_Property_LAYOUT_DIRECTION_get();
             internal static readonly int MARGIN = Interop.ViewProperty.View_Property_MARGIN_get();
             internal static readonly int PADDING = Interop.ViewProperty.View_Property_PADDING_get();
+            internal static readonly int SHADOW = Interop.ViewProperty.View_Property_SHADOW_get();
         }
     }
 }
index 4c40811..d44e3d0 100755 (executable)
@@ -604,6 +604,16 @@ namespace Tizen.NUI.BaseComponents
             PivotPoint = new Position(x, y, z);
         }
 
+        private void OnImageShadowChanged(TransformablePropertyMap instance)
+        {
+            ImageShadow = (ImageShadow)instance;
+        }
+
+        private void OnBoxShadowChanged(TransformablePropertyMap instance)
+        {
+            BoxShadow = (Shadow)instance;
+        }
+
         private void OnKeyInputFocusGained(IntPtr view)
         {
             if (_keyInputFocusGainedEventHandler != null)
index 42d6f85..6b469bd 100755 (executable)
@@ -61,6 +61,10 @@ namespace Tizen.NUI
             {
                 return ValueOfIndex(key);
             }
+            internal set
+            {
+                SetValue(key, value);
+            }
         }
 
         /// <summary>
@@ -76,6 +80,10 @@ namespace Tizen.NUI
             {
                 return ValueOfIndex(key);
             }
+            internal set
+            {
+                SetValue(key, value);
+            }
         }
 
         /// <summary>
@@ -286,6 +294,18 @@ namespace Tizen.NUI
             return (obj == null) ? new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero) : obj.swigCPtr;
         }
 
+        internal void SetValue(int key, PropertyValue value)
+        {
+            Interop.PropertyMap.Property_Map_SetValue_IntKey(swigCPtr, key, PropertyValue.getCPtr(value));
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+        }
+
+        internal void SetValue(string key, PropertyValue value)
+        {
+            Interop.PropertyMap.Property_Map_SetValue_StringKey(swigCPtr, key, PropertyValue.getCPtr(value));
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+        }
+
         /// This will not be public opened.
         [EditorBrowsable(EditorBrowsableState.Never)]
         protected override void ReleaseSwigCPtr(System.Runtime.InteropServices.HandleRef swigCPtr)
index f48389a..b0ea75d 100755 (executable)
@@ -331,6 +331,10 @@ namespace Tizen.NUI
             {
                 value = new PropertyValue((Extents)obj);
             }
+            else if (type.Equals(typeof(Rectangle)))
+            {
+                value = new PropertyValue((Rectangle)obj);
+            }
             else
             {
                 throw new global::System.InvalidOperationException("Unimplemented type for Property Value :" + type.Name);
@@ -645,5 +649,25 @@ namespace Tizen.NUI
         {
             Interop.PropertyValue.delete_Property_Value(swigCPtr);
         }
+
+        internal static PropertyValue CreateWithGuard(string value)
+        {
+            return value == null ? new PropertyValue() : new PropertyValue(value);
+        }
+
+        internal static PropertyValue CreateWithGuard(Vector2 value)
+        {
+            return value == null ? new PropertyValue() : new PropertyValue(value);
+        }
+
+        internal static PropertyValue CreateWithGuard(Rectangle value)
+        {
+            return value == null ? new PropertyValue() : new PropertyValue(value);
+        }
+
+        internal static PropertyValue CreateWithGuard(Color value)
+        {
+            return value == null ? new PropertyValue() : new PropertyValue(value);
+        }
     }
 }
index cae40b3..903990e 100755 (executable)
@@ -57,6 +57,11 @@ namespace Tizen.NUI
             callback = cb;
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
+
+        internal Rectangle(RectangleChangedCallback cb, Rectangle other) : this(cb, other.x, other.y, other.width, other.height)
+        {
+        }
+
         internal delegate void RectangleChangedCallback(int x, int y, int width, int height);
         private RectangleChangedCallback callback = null;
 
index ce50f96..b2ca5c6 100755 (executable)
@@ -425,6 +425,10 @@ namespace Tizen.NUI
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
 
+        internal Size(SizeChangedCallback cb, Size other) : this(cb, other.Width, other.Height, other.Depth)
+        {
+        }
+
         private SizeChangedCallback callback = null;
     }
 }
index ed895b1..27c75cb 100755 (executable)
@@ -87,6 +87,11 @@ namespace Tizen.NUI
             callback = cb;
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
+
+        internal Vector2(Vector2ChangedCallback cb, Vector2 other) : this(cb, other.X, other.Y)
+        {
+        }
+
         internal delegate void Vector2ChangedCallback(float x, float y);
         private Vector2ChangedCallback callback = null;
 
diff --git a/src/Tizen.NUI/src/public/ViewProperty/ImageShadow.cs b/src/Tizen.NUI/src/public/ViewProperty/ImageShadow.cs
new file mode 100644 (file)
index 0000000..21edf54
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * Copyright(c) 2019 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 System.ComponentModel;
+
+namespace Tizen.NUI
+{
+
+    /// <summary>
+    /// The Shadow composed of image for View
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class ImageShadow : TransformablePropertyMap
+    {
+        private string url;
+
+        private Rectangle border;
+
+        /// <summary>
+        /// Constructor
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ImageShadow() : base()
+        {
+        }
+
+        private void OnBorderChanged(int x, int y, int width, int height)
+        {
+            UpdateBorder();
+        }
+
+        private void UpdateUrl()
+        {
+            propertyMap[ImageVisualProperty.URL] = PropertyValue.CreateWithGuard(url);
+            OnPropertyChanged?.Invoke(this);
+        }
+
+        private void UpdateBorder()
+        {
+            propertyMap[ImageVisualProperty.Border] = PropertyValue.CreateWithGuard(border);
+            OnPropertyChanged?.Invoke(this);
+        }
+
+        /// <summary>
+        /// The url for the shadow image to load.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public string Url
+        {
+            get
+            {
+                return url;
+            }
+            set
+            {
+                url = value;
+                UpdateUrl();
+            }
+        }
+
+        /// <summary>
+        /// Optional.<br />
+        /// The border area of the n-patch image.
+        /// Set left, right, bottom, top length of the border you don't want to stretch in the image.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public Rectangle Border
+        {
+            get
+            {
+                return border;
+            }
+            set
+            {
+                border = value == null? null : new Rectangle(OnBorderChanged, value);
+                UpdateBorder();
+            }
+        }
+
+        override internal string ToDebugString()
+        {
+            string result = "";
+            // TODO
+            return result;
+        }
+
+        override internal bool IsValid()
+        {
+            return url != null;
+        }
+
+        static internal new PropertyValue ToPropertyValue(TransformablePropertyMap instance)
+        {
+            if (instance == null || !instance.IsValid())
+            {
+                return new PropertyValue();
+            }
+
+            instance.propertyMap.Insert(Visual.Property.Type, new PropertyValue((int)Visual.Type.NPatch));
+
+            return new PropertyValue(instance.propertyMap);
+        }
+    }
+}
+
+
diff --git a/src/Tizen.NUI/src/public/ViewProperty/Shadow.cs b/src/Tizen.NUI/src/public/ViewProperty/Shadow.cs
new file mode 100644 (file)
index 0000000..44ee106
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * Copyright(c) 2019 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 System.ComponentModel;
+
+namespace Tizen.NUI
+{
+
+    /// <summary>
+    /// The platform provided shadow drawing for View
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class Shadow : TransformablePropertyMap
+    {
+        private static readonly Color noColor = new Color(0, 0, 0, 0);
+
+        private static readonly Color defaultColor = new Color(0, 0, 0, 0.5f);
+
+        private Color color;
+
+        private uint blurRadius;
+
+        /// <summary>
+        /// Constructor
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public Shadow() : base()
+        {
+            Color = defaultColor;
+        }
+
+        /// <summary>
+        /// The boolean conversion
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static implicit operator Shadow(bool value)
+        {
+            Shadow shadow = new Shadow()
+            {
+                Color = value ? defaultColor : noColor,
+            };
+            return shadow;
+        }
+
+        private void OnColorChanged(float r, float g, float b, float a)
+        {
+            UpdateColor();
+        }
+
+        private void UpdateColor()
+        {
+            propertyMap[ColorVisualProperty.MixColor] = PropertyValue.CreateWithGuard(color);
+            OnPropertyChanged?.Invoke(this);
+        }
+
+        private void UpdateBlurRadius()
+        {
+            // TODO update blur radius value in the property map
+            OnPropertyChanged?.Invoke(this);
+        }
+
+        /// <summary>
+        /// The color for the shadow.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public Color Color
+        {
+            get
+            {
+                return color;
+            }
+            set
+            {
+                color = value;
+                UpdateColor();
+            }
+        }
+
+        /// <summary>
+        /// The blur radius value for the shadow. Bigger value, much blurry.
+        /// </summary>
+        private uint BlurRadius
+        {
+            get
+            {
+                return blurRadius;
+            }
+            set
+            {
+                blurRadius = value;
+                UpdateBlurRadius();
+            }
+        }
+
+        override internal string ToDebugString()
+        {
+            string result = "";
+            // TODO
+            return result;
+        }
+
+        override internal bool IsValid()
+        {
+            return color != null && color.A != 0;
+        }
+
+        static internal new PropertyValue ToPropertyValue(TransformablePropertyMap instance)
+        {
+            if (instance == null || !instance.IsValid())
+            {
+                return new PropertyValue();
+            }
+
+            // TODO to be other blurable visual in the future
+            instance.propertyMap.Insert(Visual.Property.Type, new PropertyValue((int)Visual.Type.Color));
+
+            return new PropertyValue(instance.propertyMap);
+        }
+    }
+}
+
+
diff --git a/src/Tizen.NUI/src/public/ViewProperty/TransformablePropertyMap.cs b/src/Tizen.NUI/src/public/ViewProperty/TransformablePropertyMap.cs
new file mode 100644 (file)
index 0000000..d2d2927
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * Copyright(c) 2019 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 System.ComponentModel;
+
+namespace Tizen.NUI
+{
+
+    /// <summary>
+    /// The property map class that has transform property for one of its items.
+    /// This class can be used to convert visual properties to map.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    abstract public class TransformablePropertyMap
+    {
+        internal delegate void PropertyChangedCallback(TransformablePropertyMap instance);
+
+        internal PropertyChangedCallback OnPropertyChanged = null;
+
+        private static readonly Vector2 noOffset = new Vector2(0, 0);
+
+        private static readonly Vector2 noScale = new Vector2(1, 1);
+
+        private static readonly Vector2 defaultOffset = new Vector2(0, 0);
+
+        private static readonly Vector2 defaultOffsetPolicy = new Vector2((int)VisualTransformPolicyType.Absolute, (int)VisualTransformPolicyType.Absolute);
+
+
+        /// <summary>
+        /// The offset value that tansform should have in common
+        /// </summary>
+        protected internal Vector2 offset = defaultOffset;
+
+        /// <summary>
+        /// The size value in scale that tansform should have in common
+        /// </summary>
+        protected internal Vector2 scale = noScale;
+
+        /// <summary>
+        /// The output property map
+        /// </summary>
+        protected internal PropertyMap propertyMap;
+
+        /// <summary>
+        /// The transform property map
+        /// </summary>
+        protected internal PropertyMap transformMap;
+
+        /// <summary>
+        /// Constructor
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public TransformablePropertyMap()
+        {
+            // Initialize maps
+            propertyMap = new PropertyMap();
+            transformMap = new PropertyMap();
+
+            // Offet has default value, so need to update map
+            transformMap[(int)VisualTransformPropertyType.OffsetPolicy] = new PropertyValue(defaultOffsetPolicy);
+            transformMap[(int)VisualTransformPropertyType.Offset] = PropertyValue.CreateWithGuard(offset);
+            propertyMap[Visual.Property.Transform] = new PropertyValue(transformMap);
+        }
+
+        private void OnOffsetChanged(float x, float y)
+        {
+            UpdateOffset();
+        }
+
+        private void OnScaleChanged(float widht, float height)
+        {
+            UpdateScale();
+        }
+
+        private void UpdateOffset()
+        {
+            if (!ClearTransformMapIfNeeds())
+            {
+                transformMap[(int)VisualTransformPropertyType.Offset] = PropertyValue.CreateWithGuard(offset);
+                propertyMap[Visual.Property.Transform] = new PropertyValue(transformMap);
+            }
+            OnPropertyChanged?.Invoke(this);
+        }
+
+        private void UpdateScale()
+        {
+            if (!ClearTransformMapIfNeeds())
+            {
+                transformMap[(int)VisualTransformPropertyType.Size] = PropertyValue.CreateWithGuard(scale);
+                propertyMap[Visual.Property.Transform] = new PropertyValue(transformMap);
+            }
+            OnPropertyChanged?.Invoke(this);
+        }
+
+        /// <summary>
+        /// Indicates whether the transform map is needed or not.
+        /// This checks offset and scale values are valid.
+        /// It can be overwritten in the derived class.
+        /// </summary>
+        virtual protected internal bool NeedTransformMap()
+        {
+            return (offset != null && !offset.Equals(noOffset)) || (scale != null && !scale.Equals(noScale));
+        }
+
+        /// <summary>
+        /// If this map does not need to have transform property(= no offset and no size),
+        /// clear existing transform map and return true.
+        /// Return false when it needs to transform.
+        /// </summary>
+        protected internal bool ClearTransformMapIfNeeds()
+        {
+            if (!NeedTransformMap())
+            {
+                transformMap.Clear();
+                transformMap[(int)VisualTransformPropertyType.OffsetPolicy] = new PropertyValue(defaultOffsetPolicy);
+                propertyMap[Visual.Property.Transform] = new PropertyValue();
+                return true;
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// The position offset value (x, y) from the top left corner.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public Vector2 Offset
+        {
+            get
+            {
+                return offset;
+            }
+            set
+            {
+                offset = value == null? null : new Vector2(OnOffsetChanged, value);
+                UpdateOffset();
+            }
+        }
+
+        /// <summary>
+        /// The value indicates percentage of the container size. <br />
+        /// e.g. (0.5f, 1.0f) means 50% of the container's width and 100% of container's height.
+        /// </summary>
+        public Vector2 Scale
+        {
+            get
+            {
+                return scale;
+            }
+            set
+            {
+                scale = value == null? null : new Vector2(OnScaleChanged, value);
+                UpdateScale();
+            }
+        }
+
+        abstract internal string ToDebugString();
+
+        abstract internal bool IsValid();
+
+        static internal PropertyValue ToPropertyValue(TransformablePropertyMap instance)
+        {
+            return (instance != null && instance.IsValid()) ? new PropertyValue(instance.propertyMap) : new PropertyValue();
+        }
+    }
+}
+
+