[NUI] Apply CornerRadius to View (#1463)
authorJiyun Yang <ji.yang@samsung.com>
Mon, 16 Mar 2020 08:38:06 +0000 (17:38 +0900)
committerGitHub <noreply@github.com>
Mon, 16 Mar 2020 08:38:06 +0000 (17:38 +0900)
Signed-off-by: Jiyun Yang <ji.yang@samsung.com>
src/Tizen.NUI/src/public/BaseComponents/Style/Selector.cs
src/Tizen.NUI/src/public/BaseComponents/Style/ViewStyle.cs
src/Tizen.NUI/src/public/BaseComponents/View.cs
src/Tizen.NUI/src/public/BaseComponents/ViewBindableProperty.cs
src/Tizen.NUI/src/public/BaseComponents/ViewInternal.cs
src/Tizen.NUI/src/public/ViewProperty/ImageShadow.cs
src/Tizen.NUI/src/public/ViewProperty/ShadowBase.cs
src/Tizen.NUI/src/public/VisualConstants.cs
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/ControlSample.cs [new file with mode: 0644]

index 590499c..7638e39 100755 (executable)
@@ -14,6 +14,7 @@
  * limitations under the License.
  *
  */
+using System;
 using System.ComponentModel;
 using Tizen.NUI.Binding;
 using Tizen.NUI.Components;
@@ -272,11 +273,11 @@ namespace Tizen.NUI.BaseComponents
     /// <summary>
     /// A class that helps binding a non-selector property in View to selector property in ViewStyle.
     /// </summary>
-    internal class ViewSelector<T> where T : class, Tizen.NUI.Internal.ICloneable
+    internal class ViewSelector<T>
     {
-        private Selector<T> selector;
-        private View view;
-        private View.ControlStateChangesDelegate controlStateChanged;
+        protected Selector<T> selector;
+        protected View view;
+        protected View.ControlStateChangesDelegate controlStateChanged;
 
         internal ViewSelector(View view, View.ControlStateChangesDelegate controlStateChanged)
         {
@@ -291,23 +292,25 @@ namespace Tizen.NUI.BaseComponents
 
         internal T GetValue()
         {
-            return selector == null ? null : selector.GetValue(view.ControlState);
+            return selector == null ? default(T) : selector.GetValue(view.ControlState);
         }
 
-        internal void Clone(object value)
+        internal void Set(object value)
         {
             bool hadMultiValue = HasMultiValue();
             var type = value?.GetType();
 
             if (type == typeof(T))
             {
-                selector = new Selector<T>();
-                selector.All = (T)((T)value).Clone();
+                CopyValueToSelector((T)value);
             }
             else if (type == typeof(Selector<T>))
             {
-                selector = new Selector<T>();
-                selector.Clone<T>((Selector<T>)value);
+                CopySelectorToSelector((Selector<T>)value);
+            }
+            else if (type == Nullable.GetUnderlyingType(typeof(T)))
+            {
+                CopyValueToSelector((T)value);
             }
             else
             {
@@ -321,6 +324,18 @@ namespace Tizen.NUI.BaseComponents
             }
         }
 
+        protected virtual void CopyValueToSelector(T value)
+        {
+            selector = new Selector<T>();
+            selector.All = value;
+        }
+
+        protected virtual void CopySelectorToSelector(Selector<T> value)
+        {
+            selector = new Selector<T>();
+            selector.Clone(value);
+        }
+
         internal void Clear()
         {
             if (HasMultiValue())
@@ -335,19 +350,41 @@ namespace Tizen.NUI.BaseComponents
             return selector == null;
         }
 
-        private bool HasMultiValue()
+        protected bool HasMultiValue()
         {
             return (selector != null && selector.All == null);
         }
     }
 
-    internal static class SelectorHelper<T> where T : class, Tizen.NUI.Internal.ICloneable
+    /// <summary>
+    /// ViewSelector class for ICloneable type
+    /// </summary>
+    internal class CloneableViewSelector<T> : ViewSelector<T> where T : Tizen.NUI.Internal.ICloneable
+    {
+        internal CloneableViewSelector(View view, View.ControlStateChangesDelegate controlStateChanged) : base(view, controlStateChanged)
+        {
+        }
+
+        protected override void CopyValueToSelector(T value)
+        {
+            selector = new Selector<T>();
+            selector.All = (T)((T)value).Clone();
+        }
+
+        protected override void CopySelectorToSelector(Selector<T> value)
+        {
+            selector = new Selector<T>();
+            selector.Clone<T>((Selector<T>)value);
+        }
+    }
+
+    internal static class SelectorHelper
     {
         /// <summary>
         /// For the object type of T or Selector T, convert it to Selector T and return the cloned one.
         /// Otherwise, return null. <br/>
         /// </summary>
-        static internal Selector<T> Clone(object value)
+        static internal Selector<T> CopyCloneable<T>(object value) where T : class, Tizen.NUI.Internal.ICloneable
         {
             var type = value?.GetType();
 
@@ -367,24 +404,23 @@ namespace Tizen.NUI.BaseComponents
         }
 
         /// <summary>
-        /// For the object type of T or Selector T, convert it to T and return the cloned one.
+        /// For the value type of T or Selector T, convert it to Selector T and return the cloned one.
         /// Otherwise, return null. <br/>
         /// </summary>
-        static internal T Clone(object value, View view)
+        static internal Selector<T> CopyValue<T>(object value)
         {
             var type = value?.GetType();
 
-            if (type == typeof(T))
+            if (type == typeof(Selector<T>))
             {
-                return (T)((T)value).Clone();
+                var result = new Selector<T>();
+                result.Clone((Selector<T>)value);
+                return result;
             }
 
-            if (type == typeof(Selector<T>) && view != null && value != null)
+            if (type == typeof(T))
             {
-                Selector<T> selector = (Selector<T>)value;
-                T valueInState = selector.GetValue(view.ControlState);
-
-                return (T)valueInState?.Clone();
+                return new Selector<T>((T)value);
             }
 
             return null;
index 3c5f4fe..bb05335 100755 (executable)
@@ -749,7 +749,7 @@ namespace Tizen.NUI.BaseComponents
         public static readonly BindableProperty ImageShadowProperty = BindableProperty.Create(nameof(ImageShadow), typeof(Selector<ImageShadow>), typeof(ViewStyle), null, propertyChanged: (bindable, oldValue, newValue) =>
         {
             var viewStyle = (ViewStyle)bindable;
-            viewStyle.imageShadow = SelectorHelper<ImageShadow>.Clone(newValue);
+            viewStyle.imageShadow = SelectorHelper.CopyCloneable<ImageShadow>(newValue);
 
             if (viewStyle.imageShadow != null) viewStyle.boxShadow = null;
         },
@@ -764,7 +764,7 @@ namespace Tizen.NUI.BaseComponents
         public static readonly BindableProperty BoxShadowProperty = BindableProperty.Create(nameof(BoxShadow), typeof(Selector<ImageShadow>), typeof(ViewStyle), null, propertyChanged: (bindable, oldValue, newValue) =>
         {
             var viewStyle = (ViewStyle)bindable;
-            viewStyle.boxShadow = SelectorHelper<Shadow>.Clone(newValue);
+            viewStyle.boxShadow = SelectorHelper.CopyCloneable<Shadow>(newValue);
 
             if (viewStyle.boxShadow != null) viewStyle.imageShadow = null;
         },
@@ -774,6 +774,19 @@ namespace Tizen.NUI.BaseComponents
             return viewStyle.boxShadow;
         });
 
+        /// A BindableProperty for CornerRadius
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly BindableProperty CornerRadiusProperty = BindableProperty.Create(nameof(CornerRadius), typeof(Selector<float?>), typeof(ViewStyle), null, propertyChanged: (bindable, oldValue, newValue) =>
+        {
+            var viewStyle = (ViewStyle)bindable;
+            viewStyle.cornerRadius = SelectorHelper.CopyValue<float?>(newValue);
+        },
+        defaultValueCreator: (bindable) =>
+        {
+            var viewStyle = (ViewStyle)bindable;
+            return viewStyle.cornerRadius;
+        });
+
         private string styleName;
         private string backgroundImage;
         private View.States? state;
@@ -834,6 +847,7 @@ namespace Tizen.NUI.BaseComponents
         private Selector<ImageShadow> imageShadow;
         private Selector<Shadow> boxShadow;
         private Selector<string> backgroundImageSelector;
+        private Selector<float?> cornerRadius;
         private Selector<float?> opacitySelector;
         private Selector<Color> backgroundColorSelector;
         private Selector<Rectangle> backgroundImageBorderSelector;
@@ -1384,6 +1398,16 @@ namespace Tizen.NUI.BaseComponents
             set => SetValue(BoxShadowProperty, value);
         }
 
+        /// <summary>
+        /// The radius for the rounded corners of the View
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public Selector<float?> CornerRadius
+        {
+            get => (Selector<float?>)GetValue(CornerRadiusProperty);
+            set => SetValue(CornerRadiusProperty, value);
+        }
+
         private void OnPaddingChanged(ushort start, ushort end, ushort top, ushort bottom)
         {
             Padding = new Extents(start, end, top, bottom);
index 7010ef8..a4b51ee 100755 (executable)
@@ -61,9 +61,11 @@ namespace Tizen.NUI.BaseComponents
         private string[] transitionNames;
         private Rectangle backgroundImageBorder;
 
-        private ViewSelector<ImageShadow> imageShadow;
+        private CloneableViewSelector<ImageShadow> imageShadow;
 
-        private ViewSelector<Shadow> boxShadow;
+        private CloneableViewSelector<Shadow> boxShadow;
+
+        private ViewSelector<float?> cornerRadius;
 
         internal Size2D sizeSetExplicitly = new Size2D(); // Store size set by API, will be used in place of NaturalSize if not set.
 
@@ -347,6 +349,26 @@ namespace Tizen.NUI.BaseComponents
         }
 
         /// <summary>
+        /// The radius for the rounded corners of the View.
+        /// 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>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public float CornerRadius
+        {
+            get
+            {
+                float? value = (float?)GetValue(CornerRadiusProperty);
+                return value ?? 0;
+            }
+            set
+            {
+                SetValue(CornerRadiusProperty, value);
+                NotifyPropertyChanged();
+            }
+        }
+
+        /// <summary>
         /// The current state of the view.
         /// </summary>
         /// <since_tizen> 3 </since_tizen>
index 2b9e4e5..cbc1eb6 100755 (executable)
@@ -56,6 +56,12 @@ namespace Tizen.NUI.BaseComponents
             if (newValue != null)
             {
                 Tizen.NUI.Object.SetProperty(view.swigCPtr, View.Property.BACKGROUND, new Tizen.NUI.PropertyValue((Color)newValue));
+
+                // Apply CornerRadius if needs
+                if (view.cornerRadius != null && view.cornerRadius.GetValue() != 0)
+                {
+                    view.ApplyCornerRadius();
+                }
             }
         },
         defaultValueCreator: (bindable) =>
@@ -86,6 +92,12 @@ namespace Tizen.NUI.BaseComponents
                 if (Rectangle.IsNullOrZero(view.backgroundImageBorder))
                 {
                     Tizen.NUI.Object.SetProperty(view.swigCPtr, View.Property.BACKGROUND, string.IsNullOrEmpty(url) ? new PropertyValue() : new PropertyValue(url));
+
+                    // Apply CornerRadius if needs
+                    if (view.cornerRadius != null && view.cornerRadius.GetValue() != 0)
+                    {
+                        view.ApplyCornerRadius();
+                    }
                 }
                 else
                 {
@@ -129,6 +141,12 @@ namespace Tizen.NUI.BaseComponents
             if (Rectangle.IsNullOrZero(view.backgroundImageBorder))
             {
                 Tizen.NUI.Object.SetProperty(view.swigCPtr, View.Property.BACKGROUND, new PropertyValue(url));
+
+                // Apply CornerRadius if needs
+                if (view.cornerRadius != null && view.cornerRadius.GetValue() != 0)
+                {
+                    view.ApplyCornerRadius();
+                }
             }
             else
             {
@@ -1385,7 +1403,7 @@ namespace Tizen.NUI.BaseComponents
             var view = (View)bindable;
             bool hadShadowExtents = view.HasShadowExtents();
 
-            (view.imageShadow ?? (view.imageShadow = new ViewSelector<ImageShadow>(view, view.OnControlStateChangedForShadow))).Clone(newValue);
+            (view.imageShadow ?? (view.imageShadow = new CloneableViewSelector<ImageShadow>(view, view.OnControlStateChangedForShadow))).Set(newValue);
             Tizen.NUI.Object.SetProperty(view.swigCPtr, Interop.ViewProperty.View_Property_SHADOW_get(), ImageShadow.ToPropertyValue(view.imageShadow.GetValue(), view));
 
             view.boxShadow?.Clear();
@@ -1406,7 +1424,7 @@ namespace Tizen.NUI.BaseComponents
             var view = (View)bindable;
             bool hadShadowExtents = view.HasShadowExtents();
 
-            (view.boxShadow ?? (view.boxShadow = new ViewSelector<Shadow>(view, view.OnControlStateChangedForShadow))).Clone(newValue);
+            (view.boxShadow ?? (view.boxShadow = new CloneableViewSelector<Shadow>(view, view.OnControlStateChangedForShadow))).Set(newValue);
             Tizen.NUI.Object.SetProperty(view.swigCPtr, Interop.ViewProperty.View_Property_SHADOW_get(), Shadow.ToPropertyValue(view.boxShadow.GetValue(), view));
 
             view.imageShadow?.Clear();
@@ -1419,6 +1437,27 @@ namespace Tizen.NUI.BaseComponents
         });
 
         /// <summary>
+        /// CornerRadius Property
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly BindableProperty CornerRadiusProperty = BindableProperty.Create(nameof(CornerRadius), typeof(float), typeof(View), default(float), propertyChanged: (bindable, oldValue, newValue) =>
+        {
+            var view = (View)bindable;
+
+            (view.cornerRadius ?? (view.cornerRadius = new ViewSelector<float?>(view, view.OnControlStateChangedForCornerRadius))).Set(newValue);
+
+            view.ApplyCornerRadius();
+
+            // Update shadow visual
+            view.ApplyShadow();
+        },
+        defaultValueCreator: (bindable) =>
+        {
+            var view = (View)bindable;
+            return view.cornerRadius?.GetValue();
+        });
+
+        /// <summary>
         /// XamlStyleProperty
         /// </summary>
         [EditorBrowsable(EditorBrowsableState.Never)]
index be7fbd4..d308481 100755 (executable)
@@ -1282,18 +1282,37 @@ namespace Tizen.NUI.BaseComponents
 
         private void OnRelayoutForShadow(object sender, global::System.EventArgs e)
         {
-            UpdateShadowVisual();
+            ApplyShadow();
         }
 
         private void OnControlStateChangedForShadow(View obj, NUI.Components.ControlStates state)
         {
-            UpdateShadowVisual();
+            ApplyShadow();
         }
 
-        private void UpdateShadowVisual()
+        private void ApplyShadow()
         {
             ShadowBase shadow = (boxShadow != null && !boxShadow.IsEmpty()) ? (ShadowBase)boxShadow.GetValue() : (ShadowBase)imageShadow?.GetValue();
             Tizen.NUI.Object.SetProperty(swigCPtr, Interop.ViewProperty.View_Property_SHADOW_get(), ShadowBase.ToPropertyValue(shadow, this));
         }
+
+        private void OnControlStateChangedForCornerRadius(View obj, NUI.Components.ControlStates state)
+        {
+            ApplyCornerRadius();
+            ApplyShadow();
+        }
+
+        private void ApplyCornerRadius()
+        {
+            Tizen.NUI.PropertyMap backgroundMap = new Tizen.NUI.PropertyMap();
+            Tizen.NUI.Object.GetProperty(swigCPtr, View.Property.BACKGROUND).Get(backgroundMap);
+
+            if (!backgroundMap.Empty())
+            {
+                // TODO (NDalic.VISUAL_PROPERTY_MIX_COLOR + 3) to CornerRadius
+                backgroundMap[Visual.Property.CornerRadius] = new PropertyValue(cornerRadius.GetValue() ?? 0);
+                Tizen.NUI.Object.SetProperty(swigCPtr, View.Property.BACKGROUND, new Tizen.NUI.PropertyValue(backgroundMap));
+            }
+        }
     }
 }
index bd60a24..f8f6978 100644 (file)
@@ -38,12 +38,12 @@ namespace Tizen.NUI
         [EditorBrowsable(EditorBrowsableState.Never)]
         public ImageShadow() : base()
         {
-            propertyMap[Visual.Property.Type] = new PropertyValue((int)Visual.Type.NPatch);
+            propertyMap[Visual.Property.Type] = new PropertyValue((int)Visual.Type.Image);
         }
 
         internal ImageShadow(ImageShadow other, PropertyChangedCallback callback = null) : base(other)
         {
-            propertyMap[Visual.Property.Type] = new PropertyValue((int)Visual.Type.NPatch);
+            propertyMap[Visual.Property.Type] = new PropertyValue((int)Visual.Type.Image);
 
             Url = other.Url;
             Border = other.Border;
@@ -105,6 +105,15 @@ namespace Tizen.NUI
 
         private void UpdateBorder()
         {
+            if (Rectangle.IsNullOrZero(border))
+            {
+                propertyMap[Visual.Property.Type] = new PropertyValue((int)Visual.Type.Image);
+            }
+            else
+            {
+                propertyMap[Visual.Property.Type] = new PropertyValue((int)Visual.Type.NPatch);
+            }
+
             propertyMap[ImageVisualProperty.Border] = PropertyValue.CreateWithGuard(border);
             OnPropertyChanged?.Invoke(this);
         }
index a2428a7..ecc0918 100644 (file)
@@ -175,6 +175,11 @@ namespace Tizen.NUI
                 return new PropertyValue();
             }
 
+            if (attachedView.CornerRadius > 0)
+            {
+                instance.propertyMap[Visual.Property.CornerRadius] = new PropertyValue(attachedView.CornerRadius);
+            }
+
             instance.propertyMap[Visual.Property.Transform] = instance.GetTransformMap(attachedView);
 
             return new PropertyValue(instance.propertyMap);
index eb9dd18..c03663b 100755 (executable)
@@ -448,6 +448,11 @@ namespace Tizen.NUI
             /// </summary>
             /// <since_tizen> 5 </since_tizen>
             public static readonly int VisualFittingMode = NDalic.VISUAL_PROPERTY_MIX_COLOR + 2;
+            /// <summary>
+            /// The fitting mode of the visual.
+            /// </summary>
+            [EditorBrowsable(EditorBrowsableState.Never)]
+            public static readonly int CornerRadius = NDalic.VISUAL_PROPERTY_MIX_COLOR + 3;
         }
 
         /// <summary>
diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/ControlSample.cs b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/ControlSample.cs
new file mode 100644 (file)
index 0000000..76c96c7
--- /dev/null
@@ -0,0 +1,53 @@
+using Tizen.NUI;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+
+namespace Tizen.NUI.Samples
+{
+    public class ControlSample : IExample
+    {
+        private View root;
+        private Control control;
+
+        public void Activate()
+        {
+            Window window = Window.Instance;
+
+            root = new View()
+            {
+                Size = window.Size,
+                BackgroundColor = new Color(0.8f, 0.8f, 0.8f, 0.6f),
+                ParentOrigin = ParentOrigin.Center,
+                PivotPoint = PivotPoint.Center,
+                PositionUsesPivotPoint = true,
+            };
+            window.Add(root);
+
+            control = new Control()
+            {
+                Size = new Size(100, 100),
+                BackgroundColor = Color.Blue,
+                ParentOrigin = ParentOrigin.Center,
+                PivotPoint = PivotPoint.Center,
+                PositionUsesPivotPoint = true,
+                BoxShadow = new Shadow()
+                {
+                    Color = new Color(0.2f, 0.2f, 0.2f, 0.3f),
+                    Offset = new Vector2(5, 5),
+                },
+                CornerRadius = 26f,
+            };
+
+            root.Add(control);
+        }
+
+        public void Deactivate()
+        {
+            if (root != null)
+            {
+                Window.Instance.Remove(root);
+                root.Dispose();
+            }
+        }
+    }
+}