Browse Source

Merge pull request #3008 from Kermalis/wrappanel-add-itemwidth-itemheight-properties

Add ItemWidth and ItemHeight properties to WrapPanel
pull/3032/head
Steven Kirk 7 years ago
committed by GitHub
parent
commit
f6b4230d2d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 204
      src/Avalonia.Controls/WrapPanel.cs
  2. 24
      tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs

204
src/Avalonia.Controls/WrapPanel.cs

@ -15,7 +15,7 @@ namespace Avalonia.Controls
/// Positions child elements in sequential position from left to right, /// Positions child elements in sequential position from left to right,
/// breaking content to the next line at the edge of the containing box. /// 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, /// 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> /// </summary>
public class WrapPanel : Panel, INavigableContainer public class WrapPanel : Panel, INavigableContainer
{ {
@ -25,6 +25,18 @@ namespace Avalonia.Controls
public static readonly StyledProperty<Orientation> OrientationProperty = public static readonly StyledProperty<Orientation> OrientationProperty =
AvaloniaProperty.Register<WrapPanel, Orientation>(nameof(Orientation), defaultValue: Orientation.Horizontal); 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> /// <summary>
/// Initializes static members of the <see cref="WrapPanel"/> class. /// Initializes static members of the <see cref="WrapPanel"/> class.
/// </summary> /// </summary>
@ -42,6 +54,24 @@ namespace Avalonia.Controls
set { SetValue(OrientationProperty, value); } 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> /// <summary>
/// Gets the next control in the specified direction. /// Gets the next control in the specified direction.
/// </summary> /// </summary>
@ -51,7 +81,9 @@ namespace Avalonia.Controls
/// <returns>The control.</returns> /// <returns>The control.</returns>
IInputElement INavigableContainer.GetControl(NavigationDirection direction, IInputElement from, bool wrap) 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); int index = Children.IndexOf((IControl)from);
switch (direction) switch (direction)
@ -60,7 +92,7 @@ namespace Avalonia.Controls
index = 0; index = 0;
break; break;
case NavigationDirection.Last: case NavigationDirection.Last:
index = Children.Count - 1; index = children.Count - 1;
break; break;
case NavigationDirection.Next: case NavigationDirection.Next:
++index; ++index;
@ -82,9 +114,9 @@ namespace Avalonia.Controls
break; break;
} }
if (index >= 0 && index < Children.Count) if (index >= 0 && index < children.Count)
{ {
return Children[index]; return children[index];
} }
else else
{ {
@ -95,116 +127,140 @@ namespace Avalonia.Controls
/// <inheritdoc/> /// <inheritdoc/>
protected override Size MeasureOverride(Size constraint) protected override Size MeasureOverride(Size constraint)
{ {
var curLineSize = new UVSize(Orientation); double itemWidth = ItemWidth;
var panelSize = new UVSize(Orientation); double itemHeight = ItemHeight;
var uvConstraint = new UVSize(Orientation, constraint.Width, constraint.Height); var orientation = Orientation;
var children = Children;
var childConstraint = new Size(constraint.Width, constraint.Height); var curLineSize = new UVSize(orientation);
var panelSize = new UVSize(orientation);
for (int i = 0, count = Children.Count; i < count; i++) 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]; var child = children[i];
if (child == null) continue; if (child != null)
//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
{ {
panelSize.U = Max(curLineSize.U, panelSize.U); // Flow passes its own constraint to children
panelSize.V += curLineSize.V; child.Measure(childConstraint);
curLineSize = sz;
// 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.U = Max(curLineSize.U, panelSize.U);
panelSize.V += sz.V; panelSize.V += curLineSize.V;
curLineSize = new UVSize(Orientation); curLineSize = sz;
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
{
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);
} }
} }
//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.U = Max(curLineSize.U, panelSize.U);
panelSize.V += curLineSize.V; 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); return new Size(panelSize.Width, panelSize.Height);
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize) protected override Size ArrangeOverride(Size finalSize)
{ {
double itemWidth = ItemWidth;
double itemHeight = ItemHeight;
var orientation = Orientation;
var children = Children;
int firstInLine = 0; int firstInLine = 0;
double accumulatedV = 0; double accumulatedV = 0;
UVSize curLineSize = new UVSize(Orientation); double itemU = orientation == Orientation.Horizontal ? itemWidth : itemHeight;
UVSize uvFinalSize = new UVSize(Orientation, finalSize.Width, finalSize.Height); var curLineSize = new UVSize(orientation);
var uvFinalSize = new UVSize(orientation, finalSize.Width, finalSize.Height);
for (int i = 0; i < Children.Count; i++) 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]; var child = children[i];
if (child == null) continue; 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 accumulatedV += curLineSize.V;
{ curLineSize = sz;
arrangeLine(accumulatedV, curLineSize.V, firstInLine, i);
accumulatedV += curLineSize.V; if (MathUtilities.GreaterThan(sz.U, uvFinalSize.U)) // The element is wider then the constraint - give it a separate line
curLineSize = sz; {
// 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 curLineSize.U += sz.U;
arrangeLine(accumulatedV, sz.V, i, ++i); curLineSize.V = Max(sz.V, curLineSize.V);
accumulatedV += sz.V;
curLineSize = new UVSize(Orientation);
} }
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 // 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; 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; double u = 0;
bool isHorizontal = (Orientation == Orientation.Horizontal); bool isHorizontal = orientation == Orientation.Horizontal;
for (int i = start; i < end; i++) for (int i = start; i < end; i++)
{ {
var child = Children[i]; var child = children[i];
if (child != null) if (child != null)
{ {
UVSize childSize = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height); var childSize = new UVSize(orientation, child.DesiredSize.Width, child.DesiredSize.Height);
double layoutSlotU = childSize.U; double layoutSlotU = useItemU ? itemU : childSize.U;
child.Arrange(new Rect( child.Arrange(new Rect(
(isHorizontal ? u : v), isHorizontal ? u : v,
(isHorizontal ? v : u), isHorizontal ? v : u,
(isHorizontal ? layoutSlotU : lineV), isHorizontal ? layoutSlotU : lineV,
(isHorizontal ? lineV : layoutSlotU))); isHorizontal ? lineV : layoutSlotU));
u += layoutSlotU; u += layoutSlotU;
} }
} }
@ -232,12 +288,12 @@ namespace Avalonia.Controls
internal double Width 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; } set { if (_orientation == Orientation.Horizontal) U = value; else V = value; }
} }
internal double Height 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; } set { if (_orientation == Orientation.Horizontal) V = value; else U = value; }
} }
} }

24
tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs

@ -93,5 +93,29 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Rect(0, 0, 100, 50), target.Children[0].Bounds); Assert.Equal(new Rect(0, 0, 100, 50), target.Children[0].Bounds);
Assert.Equal(new Rect(100, 0, 100, 50), target.Children[1].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()
{
Orientation = Orientation.Horizontal,
Width = 50,
ItemWidth = 20,
ItemHeight = 15,
Children =
{
new Border(),
new Border(),
}
};
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, 0, 20, 15), target.Children[1].Bounds);
}
} }
} }

Loading…
Cancel
Save