From 1cf93ad393540cd81383708fbc4e16781765f932 Mon Sep 17 00:00:00 2001 From: Kermalis <29823718+Kermalis@users.noreply.github.com> Date: Tue, 17 Sep 2019 23:04:12 -0400 Subject: [PATCH 1/4] Add ItemWidth and ItemHeight properties to WrapPanel (with unit tests) --- src/Avalonia.Controls/WrapPanel.cs | 198 +++++++++++------- .../WrapPanelTests.cs | 23 ++ 2 files changed, 150 insertions(+), 71 deletions(-) diff --git a/src/Avalonia.Controls/WrapPanel.cs b/src/Avalonia.Controls/WrapPanel.cs index 3acf341c35..418a59b64e 100644 --- a/src/Avalonia.Controls/WrapPanel.cs +++ b/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 property. /// public class WrapPanel : Panel, INavigableContainer { @@ -25,6 +25,18 @@ namespace Avalonia.Controls public static readonly StyledProperty OrientationProperty = AvaloniaProperty.Register(nameof(Orientation), defaultValue: Orientation.Horizontal); + /// + /// Defines the property. + /// + public static readonly StyledProperty ItemWidthProperty = + AvaloniaProperty.Register(nameof(ItemWidth), double.NaN); + + /// + /// Defines the property. + /// + public static readonly StyledProperty ItemHeightProperty = + AvaloniaProperty.Register(nameof(ItemHeight), double.NaN); + /// /// Initializes static members of the class. /// @@ -42,6 +54,24 @@ namespace Avalonia.Controls set { SetValue(OrientationProperty, value); } } + /// + /// Gets or sets the width of all items in the WrapPanel. + /// + public double ItemWidth + { + get { return GetValue(ItemWidthProperty); } + set { SetValue(ItemWidthProperty, value); } + } + + /// + /// Gets or sets the height of all items in the WrapPanel. + /// + public double ItemHeight + { + get { return GetValue(ItemHeightProperty); } + set { SetValue(ItemHeightProperty, value); } + } + /// /// Gets the next control in the specified direction. /// @@ -51,7 +81,9 @@ namespace Avalonia.Controls /// The control. 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 /// 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 /// 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; } } } diff --git a/tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs b/tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs index a0511761e4..e0fa02814a 100644 --- a/tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs +++ b/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); + } } } From 9cc2d777addb1d46593d87e25baf4056b6e6658a Mon Sep 17 00:00:00 2001 From: Kermalis <29823718+Kermalis@users.noreply.github.com> Date: Tue, 17 Sep 2019 23:56:47 -0400 Subject: [PATCH 2/4] Update WrapPanelTests.cs --- .../WrapPanelTests.cs | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs b/tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs index e0fa02814a..149f723458 100644 --- a/tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs @@ -97,24 +97,25 @@ namespace Avalonia.Controls.UnitTests [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 }, - } - }; + var target = new WrapPanel() + { + Orientation = Orientation.Horizontal, + 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); + Assert.Equal(new Rect(20, 0, 20, 15), target.Children[1].Bounds); } } } From 1566c1a0689b6697149f229aee19dccde5794954 Mon Sep 17 00:00:00 2001 From: Kermalis <29823718+Kermalis@users.noreply.github.com> Date: Wed, 18 Sep 2019 11:12:37 -0400 Subject: [PATCH 3/4] Update comments --- src/Avalonia.Controls/WrapPanel.cs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Controls/WrapPanel.cs b/src/Avalonia.Controls/WrapPanel.cs index 418a59b64e..7c88401615 100644 --- a/src/Avalonia.Controls/WrapPanel.cs +++ b/src/Avalonia.Controls/WrapPanel.cs @@ -146,28 +146,28 @@ namespace Avalonia.Controls var child = children[i]; if (child != null) { - //Flow passes its own constrint to children + // Flow passes its own constraint to children child.Measure(childConstraint); - //this is the size of the child in UV space + // 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(curLineSize.U + sz.U, uvConstraint.U)) //need to switch to another line + if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvConstraint.U)) // Need to switch to another line { 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 + if (MathUtilities.GreaterThan(sz.U, uvConstraint.U)) // The element is wider then the constraint - 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 + else // Continue to accumulate a line { curLineSize.U += sz.U; curLineSize.V = Max(sz.V, curLineSize.V); @@ -175,11 +175,11 @@ namespace Avalonia.Controls } } - //the last line size, if any should be added + // The last line size, if any should be added panelSize.U = Max(curLineSize.U, panelSize.U); panelSize.V += curLineSize.V; - //go from UV space to W/H space + // Go from UV space to W/H space return new Size(panelSize.Width, panelSize.Height); } @@ -208,16 +208,16 @@ namespace Avalonia.Controls itemWidthSet ? itemWidth : child.DesiredSize.Width, itemHeightSet ? itemHeight : child.DesiredSize.Height); - if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvFinalSize.U)) //need to switch to another line + if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvFinalSize.U)) // Need to switch to another line { ArrangeLine(accumulatedV, curLineSize.V, firstInLine, i, useItemU, itemU); accumulatedV += curLineSize.V; curLineSize = sz; - if (MathUtilities.GreaterThan(sz.U, uvFinalSize.U)) //the element is wider then the constraint - give it a separate line + 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 + // Switch to next line which only contain one element ArrangeLine(accumulatedV, sz.V, i, ++i, useItemU, itemU); accumulatedV += sz.V; @@ -225,7 +225,7 @@ namespace Avalonia.Controls } firstInLine = i; } - else //continue to accumulate a line + else // Continue to accumulate a line { curLineSize.U += sz.U; curLineSize.V = Max(sz.V, curLineSize.V); @@ -233,7 +233,7 @@ namespace Avalonia.Controls } } - //arrange the last line, if any + // Arrange the last line, if any if (firstInLine < children.Count) { ArrangeLine(accumulatedV, curLineSize.V, firstInLine, children.Count, useItemU, itemU); From 2ee5a21dd7366fa98fbf350b07b18d5d164927e1 Mon Sep 17 00:00:00 2001 From: Kermalis <29823718+Kermalis@users.noreply.github.com> Date: Sun, 22 Sep 2019 12:51:59 -0400 Subject: [PATCH 4/4] Remove broken test for now An issue will be made after merging because it's unrelated --- tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs b/tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs index 149f723458..fd93df46b8 100644 --- a/tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs @@ -106,7 +106,7 @@ namespace Avalonia.Controls.UnitTests Children = { new Border(), - new Border { Width = 50, Height = 50 }, + new Border(), } };