|
@@ -38,6 +38,18 @@ namespace Avalonia.Controls
|
|
|
/// </summary>
|
|
|
public class WrapPanel : Panel, INavigableContainer
|
|
|
{
|
|
|
+ /// <summary>
|
|
|
+ /// Defines the <see cref="ItemSpacing"/> dependency property.
|
|
|
+ /// </summary>
|
|
|
+ public static readonly StyledProperty<double> ItemSpacingProperty =
|
|
|
+ AvaloniaProperty.Register<WrapPanel, double>(nameof(ItemSpacing));
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Defines the <see cref="LineSpacing"/> dependency property.
|
|
|
+ /// </summary>
|
|
|
+ public static readonly StyledProperty<double> LineSpacingProperty =
|
|
|
+ AvaloniaProperty.Register<WrapPanel, double>(nameof(LineSpacing));
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Defines the <see cref="Orientation"/> property.
|
|
|
/// </summary>
|
|
@@ -67,12 +79,30 @@ namespace Avalonia.Controls
|
|
|
/// </summary>
|
|
|
static WrapPanel()
|
|
|
{
|
|
|
- AffectsMeasure<WrapPanel>(OrientationProperty, ItemWidthProperty, ItemHeightProperty);
|
|
|
+ AffectsMeasure<WrapPanel>(ItemSpacingProperty, LineSpacingProperty, OrientationProperty, ItemWidthProperty, ItemHeightProperty);
|
|
|
AffectsArrange<WrapPanel>(ItemsAlignmentProperty);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Gets or sets the orientation in which child controls will be layed out.
|
|
|
+ /// Gets or sets the spacing between lines.
|
|
|
+ /// </summary>
|
|
|
+ public double ItemSpacing
|
|
|
+ {
|
|
|
+ get => GetValue(ItemSpacingProperty);
|
|
|
+ set => SetValue(ItemSpacingProperty, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the spacing between items.
|
|
|
+ /// </summary>
|
|
|
+ public double LineSpacing
|
|
|
+ {
|
|
|
+ get => GetValue(LineSpacingProperty);
|
|
|
+ set => SetValue(LineSpacingProperty, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the orientation in which child controls will be laid out.
|
|
|
/// </summary>
|
|
|
public Orientation Orientation
|
|
|
{
|
|
@@ -164,6 +194,8 @@ namespace Avalonia.Controls
|
|
|
{
|
|
|
double itemWidth = ItemWidth;
|
|
|
double itemHeight = ItemHeight;
|
|
|
+ double itemSpacing = ItemSpacing;
|
|
|
+ double lineSpacing = LineSpacing;
|
|
|
var orientation = Orientation;
|
|
|
var children = Children;
|
|
|
var curLineSize = new UVSize(orientation);
|
|
@@ -171,45 +203,46 @@ namespace Avalonia.Controls
|
|
|
var uvConstraint = new UVSize(orientation, constraint.Width, constraint.Height);
|
|
|
bool itemWidthSet = !double.IsNaN(itemWidth);
|
|
|
bool itemHeightSet = !double.IsNaN(itemHeight);
|
|
|
+ bool itemExists = false;
|
|
|
+ bool lineExists = false;
|
|
|
|
|
|
var childConstraint = new Size(
|
|
|
itemWidthSet ? itemWidth : constraint.Width,
|
|
|
itemHeightSet ? itemHeight : constraint.Height);
|
|
|
|
|
|
- for (int i = 0, count = children.Count; i < count; i++)
|
|
|
+ for (int i = 0, count = children.Count; i < count; ++i)
|
|
|
{
|
|
|
var child = children[i];
|
|
|
// Flow passes its own constraint to children
|
|
|
child.Measure(childConstraint);
|
|
|
|
|
|
// This is the size of the child in UV space
|
|
|
- var sz = new UVSize(orientation,
|
|
|
+ UVSize childSize = 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
|
|
|
+ var nextSpacing = itemExists && child.IsVisible ? itemSpacing : 0;
|
|
|
+ if (MathUtilities.GreaterThan(curLineSize.U + childSize.U + nextSpacing, uvConstraint.U)) // Need to switch to another line
|
|
|
{
|
|
|
panelSize.U = Max(curLineSize.U, panelSize.U);
|
|
|
- panelSize.V += curLineSize.V;
|
|
|
- curLineSize = sz;
|
|
|
+ panelSize.V += curLineSize.V + (lineExists ? lineSpacing : 0);
|
|
|
+ curLineSize = childSize;
|
|
|
|
|
|
- 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);
|
|
|
- }
|
|
|
+ itemExists = child.IsVisible;
|
|
|
+ lineExists = true;
|
|
|
}
|
|
|
else // Continue to accumulate a line
|
|
|
{
|
|
|
- curLineSize.U += sz.U;
|
|
|
- curLineSize.V = Max(sz.V, curLineSize.V);
|
|
|
+ curLineSize.U += childSize.U + nextSpacing;
|
|
|
+ curLineSize.V = Max(childSize.V, curLineSize.V);
|
|
|
+
|
|
|
+ itemExists |= child.IsVisible; // keep true
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// The last line size, if any should be added
|
|
|
panelSize.U = Max(curLineSize.U, panelSize.U);
|
|
|
- panelSize.V += curLineSize.V;
|
|
|
+ panelSize.V += curLineSize.V + (lineExists ? lineSpacing : 0);
|
|
|
|
|
|
// Go from UV space to W/H space
|
|
|
return new Size(panelSize.Width, panelSize.Height);
|
|
@@ -220,89 +253,91 @@ namespace Avalonia.Controls
|
|
|
{
|
|
|
double itemWidth = ItemWidth;
|
|
|
double itemHeight = ItemHeight;
|
|
|
+ double itemSpacing = ItemSpacing;
|
|
|
+ double lineSpacing = LineSpacing;
|
|
|
var orientation = Orientation;
|
|
|
+ bool isHorizontal = orientation == Orientation.Horizontal;
|
|
|
var children = Children;
|
|
|
int firstInLine = 0;
|
|
|
double accumulatedV = 0;
|
|
|
- double itemU = orientation == Orientation.Horizontal ? itemWidth : itemHeight;
|
|
|
+ double itemU = isHorizontal ? 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;
|
|
|
+ bool itemExists = false;
|
|
|
+ bool lineExists = false;
|
|
|
|
|
|
- for (int i = 0; i < children.Count; i++)
|
|
|
+ for (int i = 0; i < children.Count; ++i)
|
|
|
{
|
|
|
var child = children[i];
|
|
|
- var sz = new UVSize(orientation,
|
|
|
+ var childSize = new UVSize(orientation,
|
|
|
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
|
|
|
+ var nextSpacing = itemExists && child.IsVisible ? itemSpacing : 0;
|
|
|
+ if (MathUtilities.GreaterThan(curLineSize.U + childSize.U + nextSpacing, uvFinalSize.U)) // Need to switch to another line
|
|
|
{
|
|
|
- ArrangeLine(accumulatedV, curLineSize.V, firstInLine, i, useItemU, itemU, uvFinalSize.U);
|
|
|
-
|
|
|
- accumulatedV += curLineSize.V;
|
|
|
- curLineSize = sz;
|
|
|
+ accumulatedV += lineExists ? lineSpacing : 0; // add spacing to arrange line first
|
|
|
+ ArrangeLine(curLineSize.V, firstInLine, i);
|
|
|
+ accumulatedV += curLineSize.V; // add the height of the line just arranged
|
|
|
+ curLineSize = childSize;
|
|
|
|
|
|
- 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, uvFinalSize.U);
|
|
|
-
|
|
|
- accumulatedV += sz.V;
|
|
|
- curLineSize = new UVSize(orientation);
|
|
|
- }
|
|
|
firstInLine = i;
|
|
|
+
|
|
|
+ itemExists = child.IsVisible;
|
|
|
+ lineExists = true;
|
|
|
}
|
|
|
else // Continue to accumulate a line
|
|
|
{
|
|
|
- curLineSize.U += sz.U;
|
|
|
- curLineSize.V = Max(sz.V, curLineSize.V);
|
|
|
+ curLineSize.U += childSize.U + nextSpacing;
|
|
|
+ curLineSize.V = Max(childSize.V, curLineSize.V);
|
|
|
+
|
|
|
+ itemExists |= child.IsVisible; // keep true
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Arrange the last line, if any
|
|
|
if (firstInLine < children.Count)
|
|
|
{
|
|
|
- ArrangeLine(accumulatedV, curLineSize.V, firstInLine, children.Count, useItemU, itemU, uvFinalSize.U);
|
|
|
+ accumulatedV += lineExists ? lineSpacing : 0; // add spacing to arrange line first
|
|
|
+ ArrangeLine(curLineSize.V, firstInLine, children.Count);
|
|
|
}
|
|
|
|
|
|
return finalSize;
|
|
|
- }
|
|
|
|
|
|
- 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)
|
|
|
+ void ArrangeLine(double lineV, int start, int end)
|
|
|
{
|
|
|
- double totalU = 0;
|
|
|
- for (int i = start; i < end; i++)
|
|
|
+ bool useItemU = isHorizontal ? itemWidthSet : itemHeightSet;
|
|
|
+ double u = 0;
|
|
|
+ if (ItemsAlignment != WrapPanelItemsAlignment.Start)
|
|
|
{
|
|
|
- totalU += GetChildU(i);
|
|
|
+ double totalU = -itemSpacing;
|
|
|
+ for (int i = start; i < end; ++i)
|
|
|
+ {
|
|
|
+ totalU += GetChildU(i) + (!children[i].IsVisible ? 0 : itemSpacing);
|
|
|
+ }
|
|
|
+
|
|
|
+ u = ItemsAlignment switch
|
|
|
+ {
|
|
|
+ WrapPanelItemsAlignment.Center => (uvFinalSize.U - totalU) / 2,
|
|
|
+ WrapPanelItemsAlignment.End => uvFinalSize.U - totalU,
|
|
|
+ WrapPanelItemsAlignment.Start => 0,
|
|
|
+ _ => throw new ArgumentOutOfRangeException(nameof(ItemsAlignment), ItemsAlignment, null),
|
|
|
+ };
|
|
|
}
|
|
|
|
|
|
- u = ItemsAlignment switch
|
|
|
+ for (int i = start; i < end; ++i)
|
|
|
{
|
|
|
- WrapPanelItemsAlignment.Center => (panelU - totalU) / 2,
|
|
|
- WrapPanelItemsAlignment.End => panelU - totalU,
|
|
|
- WrapPanelItemsAlignment.Start => 0,
|
|
|
- _ => throw new NotImplementedException(),
|
|
|
- };
|
|
|
- }
|
|
|
+ double layoutSlotU = GetChildU(i);
|
|
|
+ children[i].Arrange(isHorizontal ? new(u, accumulatedV, layoutSlotU, lineV) : new(accumulatedV, u, lineV, layoutSlotU));
|
|
|
+ u += layoutSlotU + (!children[i].IsVisible ? 0 : itemSpacing);
|
|
|
+ }
|
|
|
|
|
|
- for (int i = start; i < end; i++)
|
|
|
- {
|
|
|
- double layoutSlotU = GetChildU(i);
|
|
|
- children[i].Arrange(isHorizontal ? new(u, v, layoutSlotU, lineV) : new(v, u, lineV, layoutSlotU));
|
|
|
- u += layoutSlotU;
|
|
|
+ return;
|
|
|
+ double GetChildU(int i) => useItemU ? itemU :
|
|
|
+ isHorizontal ? children[i].DesiredSize.Width : children[i].DesiredSize.Height;
|
|
|
}
|
|
|
-
|
|
|
- double GetChildU(int i) => useItemU ? itemU : isHorizontal ? children[i].DesiredSize.Width : children[i].DesiredSize.Height;
|
|
|
}
|
|
|
|
|
|
private struct UVSize
|