Browse Source

Add ItemWidth and ItemHeight properties to WrapPanel (with unit tests)

Kermalis 6 years ago
parent
commit
1cf93ad393
2 changed files with 150 additions and 71 deletions
  1. 127 71
      src/Avalonia.Controls/WrapPanel.cs
  2. 23 0
      tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs

+ 127 - 71
src/Avalonia.Controls/WrapPanel.cs

@@ -15,7 +15,7 @@ namespace Avalonia.Controls
     /// Positions child elements in sequential position from left to right, 
     /// breaking content to the next line at the edge of the containing box. 
     /// Subsequent ordering happens sequentially from top to bottom or from right to left, 
-    /// depending on the value of the Orientation property.
+    /// depending on the value of the <see cref="Orientation"/> property.
     /// </summary>
     public class WrapPanel : Panel, INavigableContainer
     {
@@ -25,6 +25,18 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<Orientation> OrientationProperty =
             AvaloniaProperty.Register<WrapPanel, Orientation>(nameof(Orientation), defaultValue: Orientation.Horizontal);
 
+        /// <summary>
+        /// Defines the <see cref="ItemWidth"/> property.
+        /// </summary>
+        public static readonly StyledProperty<double> ItemWidthProperty =
+            AvaloniaProperty.Register<WrapPanel, double>(nameof(ItemWidth), double.NaN);
+
+        /// <summary>
+        /// Defines the <see cref="ItemHeight"/> property.
+        /// </summary>
+        public static readonly StyledProperty<double> ItemHeightProperty =
+            AvaloniaProperty.Register<WrapPanel, double>(nameof(ItemHeight), double.NaN);
+
         /// <summary>
         /// Initializes static members of the <see cref="WrapPanel"/> class.
         /// </summary>
@@ -42,6 +54,24 @@ namespace Avalonia.Controls
             set { SetValue(OrientationProperty, value); }
         }
 
+        /// <summary>
+        /// Gets or sets the width of all items in the WrapPanel.
+        /// </summary>
+        public double ItemWidth
+        {
+            get { return GetValue(ItemWidthProperty); }
+            set { SetValue(ItemWidthProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the height of all items in the WrapPanel.
+        /// </summary>
+        public double ItemHeight
+        {
+            get { return GetValue(ItemHeightProperty); }
+            set { SetValue(ItemHeightProperty, value); }
+        }
+
         /// <summary>
         /// Gets the next control in the specified direction.
         /// </summary>
@@ -51,7 +81,9 @@ namespace Avalonia.Controls
         /// <returns>The control.</returns>
         IInputElement INavigableContainer.GetControl(NavigationDirection direction, IInputElement from, bool wrap)
         {
-            var horiz = Orientation == Orientation.Horizontal;
+            var orientation = Orientation;
+            var children = Children;
+            bool horiz = orientation == Orientation.Horizontal;
             int index = Children.IndexOf((IControl)from);
 
             switch (direction)
@@ -60,7 +92,7 @@ namespace Avalonia.Controls
                     index = 0;
                     break;
                 case NavigationDirection.Last:
-                    index = Children.Count - 1;
+                    index = children.Count - 1;
                     break;
                 case NavigationDirection.Next:
                     ++index;
@@ -82,9 +114,9 @@ namespace Avalonia.Controls
                     break;
             }
 
-            if (index >= 0 && index < Children.Count)
+            if (index >= 0 && index < children.Count)
             {
-                return Children[index];
+                return children[index];
             }
             else
             {
@@ -95,40 +127,51 @@ namespace Avalonia.Controls
         /// <inheritdoc/>
         protected override Size MeasureOverride(Size constraint)
         {
-            var curLineSize = new UVSize(Orientation);
-            var panelSize = new UVSize(Orientation);
-            var uvConstraint = new UVSize(Orientation, constraint.Width, constraint.Height);
-
-            var childConstraint = new Size(constraint.Width, constraint.Height);
-
-            for (int i = 0, count = Children.Count; i < count; i++)
+            double itemWidth = ItemWidth;
+            double itemHeight = ItemHeight;
+            var orientation = Orientation;
+            var children = Children;
+            var curLineSize = new UVSize(orientation);
+            var panelSize = new UVSize(orientation);
+            var uvConstraint = new UVSize(orientation, constraint.Width, constraint.Height);
+            bool itemWidthSet = !double.IsNaN(itemWidth);
+            bool itemHeightSet = !double.IsNaN(itemHeight);
+
+            var childConstraint = new Size(
+                itemWidthSet ? itemWidth : constraint.Width,
+                itemHeightSet ? itemHeight : constraint.Height);
+            
+            for (int i = 0, count = children.Count; i < count; i++)
             {
-                var child = Children[i];
-                if (child == null) continue;
-
-                //Flow passes its own constrint to children
-                child.Measure(childConstraint);
-
-                //this is the size of the child in UV space
-                var sz = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
-
-                if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvConstraint.U)) //need to switch to another line
+                var child = children[i];
+                if (child != null)
                 {
-                    panelSize.U = Max(curLineSize.U, panelSize.U);
-                    panelSize.V += curLineSize.V;
-                    curLineSize = sz;
+                    //Flow passes its own constrint to children
+                    child.Measure(childConstraint);
+
+                    //this is the size of the child in UV space
+                    var sz = new UVSize(orientation,
+                        itemWidthSet ? itemWidth : child.DesiredSize.Width,
+                        itemHeightSet ? itemHeight : child.DesiredSize.Height);
 
-                    if (MathUtilities.GreaterThan(sz.U, uvConstraint.U)) //the element is wider then the constrint - give it a separate line                    
+                    if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvConstraint.U)) //need to switch to another line
                     {
-                        panelSize.U = Max(sz.U, panelSize.U);
-                        panelSize.V += sz.V;
-                        curLineSize = new UVSize(Orientation);
+                        panelSize.U = Max(curLineSize.U, panelSize.U);
+                        panelSize.V += curLineSize.V;
+                        curLineSize = sz;
+
+                        if (MathUtilities.GreaterThan(sz.U, uvConstraint.U)) //the element is wider then the constrint - give it a separate line                    
+                        {
+                            panelSize.U = Max(sz.U, panelSize.U);
+                            panelSize.V += sz.V;
+                            curLineSize = new UVSize(orientation);
+                        }
+                    }
+                    else //continue to accumulate a line
+                    {
+                        curLineSize.U += sz.U;
+                        curLineSize.V = Max(sz.V, curLineSize.V);
                     }
-                }
-                else //continue to accumulate a line
-                {
-                    curLineSize.U += sz.U;
-                    curLineSize.V = Max(sz.V, curLineSize.V);
                 }
             }
 
@@ -143,68 +186,81 @@ namespace Avalonia.Controls
         /// <inheritdoc/>
         protected override Size ArrangeOverride(Size finalSize)
         {
+            double itemWidth = ItemWidth;
+            double itemHeight = ItemHeight;
+            var orientation = Orientation;
+            var children = Children;
             int firstInLine = 0;
             double accumulatedV = 0;
-            UVSize curLineSize = new UVSize(Orientation);
-            UVSize uvFinalSize = new UVSize(Orientation, finalSize.Width, finalSize.Height);
-
-            for (int i = 0; i < Children.Count; i++)
+            double itemU = orientation == Orientation.Horizontal ? itemWidth : itemHeight;
+            var curLineSize = new UVSize(orientation);
+            var uvFinalSize = new UVSize(orientation, finalSize.Width, finalSize.Height);
+            bool itemWidthSet = !double.IsNaN(itemWidth);
+            bool itemHeightSet = !double.IsNaN(itemHeight);
+            bool useItemU = orientation == Orientation.Horizontal ? itemWidthSet : itemHeightSet;
+
+            for (int i = 0; i < children.Count; i++)
             {
-                var child = Children[i];
-                if (child == null) continue;
+                var child = children[i];
+                if (child != null)
+                {
+                    var sz = new UVSize(orientation,
+                        itemWidthSet ? itemWidth : child.DesiredSize.Width,
+                        itemHeightSet ? itemHeight : child.DesiredSize.Height);
 
-                var sz = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
+                    if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvFinalSize.U)) //need to switch to another line
+                    {
+                        ArrangeLine(accumulatedV, curLineSize.V, firstInLine, i, useItemU, itemU);
 
-                if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvFinalSize.U)) //need to switch to another line
-                {
-                    arrangeLine(accumulatedV, curLineSize.V, firstInLine, i);
+                        accumulatedV += curLineSize.V;
+                        curLineSize = sz;
 
-                    accumulatedV += curLineSize.V;
-                    curLineSize = sz;
+                        if (MathUtilities.GreaterThan(sz.U, uvFinalSize.U)) //the element is wider then the constraint - give it a separate line                    
+                        {
+                            //switch to next line which only contain one element
+                            ArrangeLine(accumulatedV, sz.V, i, ++i, useItemU, itemU);
 
-                    if (MathUtilities.GreaterThan(sz.U, uvFinalSize.U)) //the element is wider then the constraint - give it a separate line                    
+                            accumulatedV += sz.V;
+                            curLineSize = new UVSize(orientation);
+                        }
+                        firstInLine = i;
+                    }
+                    else //continue to accumulate a line
                     {
-                        //switch to next line which only contain one element
-                        arrangeLine(accumulatedV, sz.V, i, ++i);
-
-                        accumulatedV += sz.V;
-                        curLineSize = new UVSize(Orientation);
+                        curLineSize.U += sz.U;
+                        curLineSize.V = Max(sz.V, curLineSize.V);
                     }
-                    firstInLine = i;
-                }
-                else //continue to accumulate a line
-                {
-                    curLineSize.U += sz.U;
-                    curLineSize.V = Max(sz.V, curLineSize.V);
                 }
             }
 
             //arrange the last line, if any
-            if (firstInLine < Children.Count)
+            if (firstInLine < children.Count)
             {
-                arrangeLine(accumulatedV, curLineSize.V, firstInLine, Children.Count);
+                ArrangeLine(accumulatedV, curLineSize.V, firstInLine, children.Count, useItemU, itemU);
             }
 
             return finalSize;
         }
 
-        private void arrangeLine(double v, double lineV, int start, int end)
+        private void ArrangeLine(double v, double lineV, int start, int end, bool useItemU, double itemU)
         {
+            var orientation = Orientation;
+            var children = Children;
             double u = 0;
-            bool isHorizontal = (Orientation == Orientation.Horizontal);
+            bool isHorizontal = orientation == Orientation.Horizontal;
 
             for (int i = start; i < end; i++)
             {
-                var child = Children[i];
+                var child = children[i];
                 if (child != null)
                 {
-                    UVSize childSize = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
-                    double layoutSlotU = childSize.U;
+                    var childSize = new UVSize(orientation, child.DesiredSize.Width, child.DesiredSize.Height);
+                    double layoutSlotU = useItemU ? itemU : childSize.U;
                     child.Arrange(new Rect(
-                        (isHorizontal ? u : v),
-                        (isHorizontal ? v : u),
-                        (isHorizontal ? layoutSlotU : lineV),
-                        (isHorizontal ? lineV : layoutSlotU)));
+                        isHorizontal ? u : v,
+                        isHorizontal ? v : u,
+                        isHorizontal ? layoutSlotU : lineV,
+                        isHorizontal ? lineV : layoutSlotU));
                     u += layoutSlotU;
                 }
             }
@@ -232,12 +288,12 @@ namespace Avalonia.Controls
 
             internal double Width
             {
-                get { return (_orientation == Orientation.Horizontal ? U : V); }
+                get { return _orientation == Orientation.Horizontal ? U : V; }
                 set { if (_orientation == Orientation.Horizontal) U = value; else V = value; }
             }
             internal double Height
             {
-                get { return (_orientation == Orientation.Horizontal ? V : U); }
+                get { return _orientation == Orientation.Horizontal ? V : U; }
                 set { if (_orientation == Orientation.Horizontal) V = value; else U = value; }
             }
         }

+ 23 - 0
tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs

@@ -93,5 +93,28 @@ namespace Avalonia.Controls.UnitTests
             Assert.Equal(new Rect(0, 0, 100, 50), target.Children[0].Bounds);
             Assert.Equal(new Rect(100, 0, 100, 50), target.Children[1].Bounds);
         }
+
+        [Fact]
+        public void Applies_ItemWidth_And_ItemHeight_Properties()
+        {
+            var target = new WrapPanel
+            {
+                Width = 50,
+                ItemWidth = 20,
+                ItemHeight = 15,
+                Children =
+                {
+                    new Border(),
+                    new Border { Width = 50, Height = 50 },
+                }
+            };
+
+            target.Measure(Size.Infinity);
+            target.Arrange(new Rect(target.DesiredSize));
+
+            Assert.Equal(new Size(50, 15), target.Bounds.Size);
+            Assert.Equal(new Rect(0, 0, 20, 15), target.Children[0].Bounds);
+            Assert.Equal(new Rect(20, 15, 20, 15), target.Children[1].Bounds);
+        }
     }
 }