Browse Source

Added WrapItemsAlignment (#17792)

pull/18069/head
Tom Edwards 1 year ago
committed by GitHub
parent
commit
2e6c571fc9
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 74
      src/Avalonia.Controls/WrapPanel.cs
  2. 55
      tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs

74
src/Avalonia.Controls/WrapPanel.cs

@ -3,6 +3,7 @@
//
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Utilities;
@ -11,6 +12,24 @@ using static System.Math;
namespace Avalonia.Controls
{
public enum WrapPanelItemsAlignment
{
/// <summary>
/// Items are laid out so the first one in each column/row touches the top/left of the panel.
/// </summary>
Start,
/// <summary>
/// Items are laid out so that each column/row is centred vertically/horizontally within the panel.
/// </summary>
Center,
/// <summary>
/// Items are laid out so the last one in each column/row touches the bottom/right of the panel.
/// </summary>
End,
}
/// <summary>
/// Positions child elements in sequential position from left to right,
/// breaking content to the next line at the edge of the containing box.
@ -25,6 +44,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<Orientation> OrientationProperty =
AvaloniaProperty.Register<WrapPanel, Orientation>(nameof(Orientation), defaultValue: Orientation.Horizontal);
/// <summary>
/// Defines the <see cref="ItemsAlignment"/> property.
/// </summary>
public static readonly StyledProperty<WrapPanelItemsAlignment> ItemsAlignmentProperty =
AvaloniaProperty.Register<WrapPanel, WrapPanelItemsAlignment>(nameof(ItemsAlignment), defaultValue: WrapPanelItemsAlignment.Start);
/// <summary>
/// Defines the <see cref="ItemWidth"/> property.
/// </summary>
@ -43,6 +68,7 @@ namespace Avalonia.Controls
static WrapPanel()
{
AffectsMeasure<WrapPanel>(OrientationProperty, ItemWidthProperty, ItemHeightProperty);
AffectsArrange<WrapPanel>(ItemsAlignmentProperty);
}
/// <summary>
@ -54,6 +80,15 @@ namespace Avalonia.Controls
set => SetValue(OrientationProperty, value);
}
/// <summary>
/// Gets or sets the alignment of items in the WrapPanel.
/// </summary>
public WrapPanelItemsAlignment ItemsAlignment
{
get => GetValue(ItemsAlignmentProperty);
set => SetValue(ItemsAlignmentProperty, value);
}
/// <summary>
/// Gets or sets the width of all items in the WrapPanel.
/// </summary>
@ -140,7 +175,7 @@ namespace Avalonia.Controls
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];
@ -205,7 +240,7 @@ namespace Avalonia.Controls
if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvFinalSize.U)) // Need to switch to another line
{
ArrangeLine(accumulatedV, curLineSize.V, firstInLine, i, useItemU, itemU);
ArrangeLine(accumulatedV, curLineSize.V, firstInLine, i, useItemU, itemU, uvFinalSize.U);
accumulatedV += curLineSize.V;
curLineSize = sz;
@ -213,7 +248,7 @@ namespace Avalonia.Controls
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);
ArrangeLine(accumulatedV, sz.V, i, ++i, useItemU, itemU, uvFinalSize.U);
accumulatedV += sz.V;
curLineSize = new UVSize(orientation);
@ -230,31 +265,44 @@ namespace Avalonia.Controls
// Arrange the last line, if any
if (firstInLine < children.Count)
{
ArrangeLine(accumulatedV, curLineSize.V, firstInLine, children.Count, useItemU, itemU);
ArrangeLine(accumulatedV, curLineSize.V, firstInLine, children.Count, useItemU, itemU, uvFinalSize.U);
}
return finalSize;
}
private void ArrangeLine(double v, double lineV, int start, int end, bool useItemU, double itemU)
private void ArrangeLine(double v, double lineV, int start, int end, bool useItemU, double itemU, double panelU)
{
var orientation = Orientation;
var children = Children;
double u = 0;
bool isHorizontal = orientation == Orientation.Horizontal;
if (ItemsAlignment != WrapPanelItemsAlignment.Start)
{
double totalU = 0;
for (int i = start; i < end; i++)
{
totalU += GetChildU(i);
}
u = ItemsAlignment switch
{
WrapPanelItemsAlignment.Center => (panelU - totalU) / 2,
WrapPanelItemsAlignment.End => panelU - totalU,
WrapPanelItemsAlignment.Start => 0,
_ => throw new NotImplementedException(),
};
}
for (int i = start; i < end; i++)
{
var child = children[i];
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));
double layoutSlotU = GetChildU(i);
children[i].Arrange(isHorizontal ? new(u, v, layoutSlotU, lineV) : new(v, u, lineV, layoutSlotU));
u += layoutSlotU;
}
double GetChildU(int i) => useItemU ? itemU : isHorizontal ? children[i].DesiredSize.Width : children[i].DesiredSize.Height;
}
private struct UVSize

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

@ -1,3 +1,4 @@
using System;
using Avalonia.Layout;
using Xunit;
@ -47,6 +48,60 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Rect(100, 0, 100, 50), target.Children[1].Bounds);
}
public static TheoryData<Orientation, WrapPanelItemsAlignment> GetItemsAlignmentValues()
{
var data = new TheoryData<Orientation, WrapPanelItemsAlignment>();
foreach (var orientation in Enum.GetValues<Orientation>())
{
foreach (var alignment in Enum.GetValues<WrapPanelItemsAlignment>())
{
data.Add(orientation, alignment);
}
}
return data;
}
[Theory, MemberData(nameof(GetItemsAlignmentValues))]
public void Lays_Out_With_Items_Alignment(Orientation orientation, WrapPanelItemsAlignment itemsAlignment)
{
var target = new WrapPanel()
{
Width = 200,
Height = 200,
Orientation = orientation,
ItemsAlignment = itemsAlignment,
Children =
{
new Border { Height = 50, Width = 50 },
new Border { Height = 50, Width = 50 },
}
};
target.Measure(Size.Infinity);
target.Arrange(new Rect(target.DesiredSize));
Assert.Equal(new Size(200, 200), target.Bounds.Size);
var rowBounds = target.Children[0].Bounds.Union(target.Children[1].Bounds);
Assert.Equal(orientation switch
{
Orientation.Horizontal => new(100, 50),
Orientation.Vertical => new(50, 100),
_ => throw new NotImplementedException()
}, rowBounds.Size);
Assert.Equal((orientation, itemsAlignment) switch
{
(_, WrapPanelItemsAlignment.Start) => new(0, 0),
(Orientation.Horizontal, WrapPanelItemsAlignment.Center) => new(50, 0),
(Orientation.Vertical, WrapPanelItemsAlignment.Center) => new(0, 50),
(Orientation.Horizontal, WrapPanelItemsAlignment.End) => new(100, 0),
(Orientation.Vertical, WrapPanelItemsAlignment.End) => new(0, 100),
_ => throw new NotImplementedException(),
}, rowBounds.Position);
}
[Fact]
public void Lays_Out_Vertically_Children_On_A_Single_Line()
{

Loading…
Cancel
Save