Skip to content

Commit

Permalink
Added WrapItemsAlignment
Browse files Browse the repository at this point in the history
  • Loading branch information
TomEdwardsEnscape committed Jan 21, 2025
1 parent 0efe89e commit e0b3c50
Showing 1 changed file with 61 additions and 13 deletions.
74 changes: 61 additions & 13 deletions src/Avalonia.Controls/WrapPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -11,6 +12,24 @@

namespace Avalonia.Controls
{
public enum WrapItemsAlignment
{
/// <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.
Expand All @@ -25,6 +44,12 @@ public class WrapPanel : Panel, INavigableContainer
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<WrapItemsAlignment> ItemAlignmentProperty =
AvaloniaProperty.Register<WrapPanel, WrapItemsAlignment>(nameof(ItemsAlignment), defaultValue: WrapItemsAlignment.Start);

/// <summary>
/// Defines the <see cref="ItemWidth"/> property.
/// </summary>
Expand All @@ -43,6 +68,7 @@ public class WrapPanel : Panel, INavigableContainer
static WrapPanel()
{
AffectsMeasure<WrapPanel>(OrientationProperty, ItemWidthProperty, ItemHeightProperty);
AffectsArrange<WrapPanel>(ItemAlignmentProperty);
}

/// <summary>
Expand All @@ -54,6 +80,15 @@ public Orientation Orientation
set => SetValue(OrientationProperty, value);
}

/// <summary>
/// Gets or sets the alignment of items in the WrapPanel.
/// </summary>
public WrapItemsAlignment ItemsAlignment
{
get => GetValue(ItemAlignmentProperty);
set => SetValue(ItemAlignmentProperty, value);
}

/// <summary>
/// Gets or sets the width of all items in the WrapPanel.
/// </summary>
Expand Down Expand Up @@ -140,7 +175,7 @@ protected override Size MeasureOverride(Size constraint)
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];
Expand Down Expand Up @@ -205,15 +240,15 @@ protected override Size ArrangeOverride(Size finalSize)

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;

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);
Expand All @@ -230,31 +265,44 @@ protected override Size ArrangeOverride(Size finalSize)
// 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 != WrapItemsAlignment.Start)
{
double totalU = 0;
for (int i = start; i < end; i++)
{
totalU += GetChildU(i);
}

u = ItemsAlignment switch
{
WrapItemsAlignment.Center => (panelU - totalU) / 2,
WrapItemsAlignment.End => panelU - totalU,
WrapItemsAlignment.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
Expand Down

0 comments on commit e0b3c50

Please sign in to comment.