Skip to content

Conversation

KaranocaVe
Copy link

@KaranocaVe KaranocaVe commented Aug 7, 2025

What does the pull request do?

This pull request introduces a new SpaceBetween Justify alignment option to the WrapPanel control. This alignment mode arranges child elements with equal spacing between them within each row, providing a new way to distribute content evenly.

What is the current behavior?

The current WrapPanel offers Start, Center, and End alignments. The Center alignment centers the entire group of children as a single block within each line. This can lead to uneven visual gaps, especially when the items don't fill the entire width of the line.

What is the updated/expected behavior with this PR?

With this PR, users can set ItemsAlignment="Justify" to achieve a justified layout. In this mode, the WrapPanel will calculate the remaining horizontal space in each line and distribute it evenly as gaps between the child elements.

image

The expected behavior for Justify is:

  • ItemsAlignment="Justify": Child items are arranged from the start of the line, with equal spacing injected between each item to fill the remaining space. This creates a visually balanced, "justified" look for each row.

To test this, create a WrapPanel with ItemsAlignment="Justify" and add several child controls. Observe how the spacing between the children changes as you resize the window.

How was the solution implemented (if it's not obvious)?

The solution was implemented by extending the WrapPanelItemsAlignment enum with a new Justify value. The core layout logic was modified within the ArrangeLine local function in ArrangeOverride. A new case was added to the ItemsAlignment switch statement that calculates the spacing required for the Justify behavior and arranges the children accordingly.

Checklist

  • Added unit tests?
    • A new case has been added to the Lays_Out_With_Items_Alignment test to verify the behavior of Justify.
  • Added XML documentation to any related classes?
    • XML documentation has been added to the WrapPanelItemsAlignment.Justify enum value.
  • Consider submitting a PR to https://github.com/AvaloniaUI/avalonia-docs with user documentation

Breaking changes

No breaking changes. This is an additive change to an existing public API.

Obsoletions / Deprecations

No APIs have been obsoleted or deprecated.

Fixed issues

@MrJul MrJul added feature needs-api-review The PR adds new public APIs that should be reviewed. labels Aug 7, 2025
@KaranocaVe
Copy link
Author

image

I just fixed an issue where the last row, if it couldn't be filled completely, wouldn't use equal spacing (which is very weird) but instead maintain the same spacing as the previous rows.

@cla-avalonia
Copy link
Collaborator

cla-avalonia commented Aug 7, 2025

  • All contributors have signed the CLA.

@rabbitism
Copy link
Contributor

I would prefer the word Justify which is used in TextAlignment.

@KaranocaVe
Copy link
Author

I would prefer the word Justify which is used in TextAlignment.

Agree

@KaranocaVe KaranocaVe changed the title feat: Add SpaceBetween alignment to WrapPanel feat: Add Justify alignment to WrapPanel Aug 7, 2025
@avaloniaui-bot
Copy link

You can test this PR using the following package version. 12.0.999-cibuild0058104-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@timunie
Copy link
Contributor

timunie commented Aug 7, 2025

There is a PR for a VirtualizingWrapPanel in Avalonia.Labs. in the best case both controls can share the same spacing enum. I won't argue here which names are better. But we should at least compare them and check naming and functionallity.

@timunie
Copy link
Contributor

timunie commented Aug 7, 2025

src/Avalonia.Labs.Controls/VirtualizingWrapPanel/SpacingMode.cs

@KaranocaVe
Copy link
Author

There is a PR for a VirtualizingWrapPanel in Avalonia.Labs. in the best case both controls can share the same spacing enum. I won't argue here which names are better. But we should at least compare them and check naming and functionallity.

It looks like you've implemented more precise spacing control. I'll try to implement them if I have time.
But I might not have the time. 😇

@KaranocaVe
Copy link
Author

src/Avalonia.Labs.Controls/VirtualizingWrapPanel/SpacingMode.cs

Perhaps we can keep the Justify enum since it belongs to the ItemsAlignment property. Later, we could implement a SpacingMode property (only effective when Justify is enabled) for more granular control.

@KaranocaVe
Copy link
Author

@cla-avalonia agree

@stevemonaco
Copy link
Contributor

This is another step for WrapPanel to overlap with the Avalonia.Labs FlexPanel which supports:
https://github.com/AvaloniaUI/Avalonia.Labs/blob/main/src/Avalonia.Labs.Panels/JustifyContent.cs

@robloo
Copy link
Contributor

robloo commented Aug 9, 2025

I have to link to the original discussion when adding alignment to the WrapPanel starting here: #17792 (comment)

There are a few things we could talk about based on that discussion but right of the bat: Why not use a UniformGrid here instead?

@avaloniaui-bot
Copy link

You can test this PR using the following package version. 12.0.999-cibuild0058128-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@MrJul
Copy link
Member

MrJul commented Aug 11, 2025

Public API for review:

 namespace Avalonia.Controls
 {
     public enum WrapPanelItemsAlignment
     {
         Start,
         Center,
         End,
+        Justify
     }
 }

@avaloniaui-bot
Copy link

You can test this PR using the following package version. 12.0.999-cibuild0058402-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@avaloniaui-bot
Copy link

You can test this PR using the following package version. 12.0.999-cibuild0058581-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@MrJul MrJul added api-approved The new public APIs have been approved. and removed needs-api-review The PR adds new public APIs that should be reviewed. labels Aug 28, 2025
@avaloniaui-bot
Copy link

You can test this PR using the following package version. 12.0.999-cibuild0058649-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

Copy link
Member

@MrJul MrJul left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! The feature has a major issue where the justification isn't currently computed per line (see the screenshot below).

bool itemWidthSet = !double.IsNaN(itemWidth);
bool itemHeightSet = !double.IsNaN(itemHeight);

double GetChildU(Control child) => (isHorizontal ? itemWidthSet : itemHeightSet)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider extracting isHorizontal as the main condition for readability, to avoid repeating it three times (and saving a branch).

e.g.

double GetChildU(Control child) => isHorizontal
    ? (itemWidthSet ? itemWidth : child.DesiredSize.Width)
    : (itemHeightSet ? itemHeight : child.DesiredSize.Height);

? (isHorizontal ? itemWidth : itemHeight)
: (isHorizontal ? child.DesiredSize.Width : child.DesiredSize.Height);

double GetChildV(Control child) => (isHorizontal ? itemHeightSet : itemWidthSet)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing as above.

{
var layoutSlotU = GetChildU(children[i]);
children[i].Arrange(isHorizontal ? new Rect(u, accumulatedV, layoutSlotU, lineV) : new Rect(accumulatedV, u, lineV, layoutSlotU));
u += layoutSlotU + spacing;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should take the item's visibility into account, like for other alignments.
When that's done, this if branch can be removed and merged with the otthers below.

{
var layoutSlotU = GetChildU(children[i]);
children[i].Arrange(isHorizontal ? new Rect(u, accumulatedV, layoutSlotU, lineV) : new Rect(accumulatedV, u, lineV, layoutSlotU));
u += layoutSlotU + (!children[i].IsVisible ? 0 : itemSpacing);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spacing should be used here; I know that's is effectively equal to itemSpacing at this point, but this will make things clearer (and allow both branches to be merged into one).

/// <summary>
/// Items are laid out with equal spacing between them within each column/row.
/// </summary>
Justify
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to adjust the documentation of ItemSpacing and LineSpacing to specify that they become minimums in Justify mode.

}

var lineBreaks = new List<int>();
var childrenTotalUByLine = new List<double>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This list has items added to it, but it's never read in any way. Leftover or bug?

}
}

var lineBreaks = new List<int>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We try to avoid allocations when possible during the Measure and Arrange pass. Consider keeping the list in a field once instantiated.

WrapPanelItemsAlignment.Start => 0,
_ => throw new ArgumentOutOfRangeException(nameof(ItemsAlignment), ItemsAlignment, null),
};
gridSpacing = (uvFinalSize.U - firstRowTotalWidth) / (firstRowChildrenCount - 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't correct: the spacing can't be computed once from the first row. Each line needs to be justified indepentely since items may have different sizes.

@MrJul
Copy link
Member

MrJul commented Sep 15, 2025

Screenshot of the spacing from the first row being incorrectly applied to all rows:

image

Reproduction:

<Border Width="400" Height="400" Background="SlateGray">
  <WrapPanel ItemsAlignment="Justify"
             ItemSpacing="8"
             LineSpacing="8"
             Width="320"
             Height="320"
             Background="Wheat"
             Margin="8"
             Orientation="Horizontal">
    <Border Width="120" Height="100" Background="Red" />
    <Border Width="150" Height="100" Background="Blue" />
    <Border Width="100" Height="100" Background="Green" />
    <Border Width="100" Height="100" Background="Yellow" />
    <Border Width="100" Height="100" Background="Magenta" />
  </WrapPanel>
</Border>

@KaranocaVe
Copy link
Author

KaranocaVe commented Sep 17, 2025

Screenshot of the spacing from the first row being incorrectly applied to all rows:

image Reproduction:
<Border Width="400" Height="400" Background="SlateGray">
  <WrapPanel ItemsAlignment="Justify"
             ItemSpacing="8"
             LineSpacing="8"
             Width="320"
             Height="320"
             Background="Wheat"
             Margin="8"
             Orientation="Horizontal">
    <Border Width="120" Height="100" Background="Red" />
    <Border Width="150" Height="100" Background="Blue" />
    <Border Width="100" Height="100" Background="Green" />
    <Border Width="100" Height="100" Background="Yellow" />
    <Border Width="100" Height="100" Background="Magenta" />
  </WrapPanel>
</Border>

Thanks for your feedback! I'll try to implement a more robust Justify when I have time.
Tests should also be updated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api-approved The new public APIs have been approved. feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants