diff --git a/components/WrapPanel2/OpenSolution.bat b/components/WrapPanel2/OpenSolution.bat
new file mode 100644
index 000000000..814a56d4b
--- /dev/null
+++ b/components/WrapPanel2/OpenSolution.bat
@@ -0,0 +1,3 @@
+@ECHO OFF
+
+powershell ..\..\tooling\ProjectHeads\GenerateSingleSampleHeads.ps1 -componentPath %CD% %*
\ No newline at end of file
diff --git a/components/WrapPanel2/samples/Assets/icon.png b/components/WrapPanel2/samples/Assets/icon.png
new file mode 100644
index 000000000..8435bcaa9
Binary files /dev/null and b/components/WrapPanel2/samples/Assets/icon.png differ
diff --git a/components/WrapPanel2/samples/Dependencies.props b/components/WrapPanel2/samples/Dependencies.props
new file mode 100644
index 000000000..e622e1df4
--- /dev/null
+++ b/components/WrapPanel2/samples/Dependencies.props
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/WrapPanel2/samples/WrapPanel2.Samples.csproj b/components/WrapPanel2/samples/WrapPanel2.Samples.csproj
new file mode 100644
index 000000000..2f1628cb6
--- /dev/null
+++ b/components/WrapPanel2/samples/WrapPanel2.Samples.csproj
@@ -0,0 +1,30 @@
+
+
+
+
+ WrapPanel2
+
+
+
+
+
+
+ WrapPanel2BasicSample.xaml
+
+
+
+
+ WrapPanel2ProportionalSample.xaml
+
+
+
+
+ WrapPanel2MegaSample.xaml
+
+
+
+
+ Designer
+
+
+
diff --git a/components/WrapPanel2/samples/WrapPanel2.md b/components/WrapPanel2/samples/WrapPanel2.md
new file mode 100644
index 000000000..06d7c3bab
--- /dev/null
+++ b/components/WrapPanel2/samples/WrapPanel2.md
@@ -0,0 +1,92 @@
+---
+title: WrapPanel2
+author: Avid29
+description: A labs-component candidate for a new WrapPanel implementation.
+keywords: WrapPanel, Control, Layout
+dev_langs:
+ - csharp
+category: Layouts
+subcategory: Panel
+discussion-id: 762
+issue-id: 763
+icon: assets/icon.png
+---
+
+# WrapPanel2
+
+The WrapPanel2 is an advanced layout control that uses `GridLength` definitions to manage item sizing within a wrapping flow. It provides granular control over how items occupy space, particularly when using proportional (Star) sizing.
+
+## Proportional Sizing Logic
+
+The behavior of items with **Star** 'LayoutLength' values depends on the panel's justification:
+
+**When Stretched/Distributed:** If `ItemsJustification` is set to a distribution mode (like `SpaceBetween`) or if the panel is stretched along the orientation axis, Star-sized items proportionally occupy the available remaining space.
+
+**When Aligned (Start, Center, End):** Star-sized child elements will collapse to the smallest size possible while maintaining their relative proportions and ensuring all child elements are fully visible.
+
+> [!Sample WrapPanel2BasicSample]
+
+## Properties
+
+### Items Justification
+
+The `ItemsJustification` property determines how items are aligned and distributed along a line.
+
+#### Automatic
+
+Arranges items according to the control's alignment.
+
+#### Start / Center / End
+
+Aligns items to the beginning, middle, or end of the line.
+
+#### SpaceAround
+
+Equal internal padding with half-sized padding at margins.
+
+#### SpaceBetween
+
+Equal spacing between items; no margin padding.
+
+#### SpaceEvenly
+
+Equal spacing between all items and margins.
+
+### Items Stretch
+
+The `ItemsStretch` property defines how the panel fills space on lines that do not contain Star-sized definitions, or when forced to fill a fixed row length.
+
+#### None
+
+No additional stretching is applied to non-star items. Note that Star-sized items will still expand if the `ItemsJustification` mode triggers a stretch.
+
+#### First
+
+The first item in the line is stretched to occupy all remaining space.
+
+#### Last
+
+The last item in the line is stretched to occupy all remaining space.
+
+#### Equal
+
+Every item in the line is stretched to a uniform size to fill the row, regardless of their individual content size.
+
+#### Proportional
+
+Every item in the line is stretched proportionally based on their desired size to fill the remaining space.
+
+## Additional Samples
+
+### Adjusted Sizings Sample
+
+Demonstrates a mix of Auto, Pixel, and Star lengths within a wrapping layout.
+
+> [!Sample WrapPanel2MegaSample]
+
+### Proportional Sizing
+
+Demonstrates how Star-sized items maintain ratios even when the panel is not set to stretch.
+
+> [!Sample WrapPanel2ProportionalSample]
+
diff --git a/components/WrapPanel2/samples/WrapPanel2BasicSample.xaml b/components/WrapPanel2/samples/WrapPanel2BasicSample.xaml
new file mode 100644
index 000000000..87550c529
--- /dev/null
+++ b/components/WrapPanel2/samples/WrapPanel2BasicSample.xaml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/WrapPanel2/samples/WrapPanel2BasicSample.xaml.cs b/components/WrapPanel2/samples/WrapPanel2BasicSample.xaml.cs
new file mode 100644
index 000000000..fda4267d3
--- /dev/null
+++ b/components/WrapPanel2/samples/WrapPanel2BasicSample.xaml.cs
@@ -0,0 +1,118 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Controls;
+
+namespace WrapPanel2Experiment.Samples;
+
+///
+/// An example sample page of a custom control inheriting from Panel.
+///
+[ToolkitSampleMultiChoiceOption("LayoutOrientation", "Horizontal", "Vertical", Title = "Orientation")]
+[ToolkitSampleMultiChoiceOption("LayoutHorizontalAlignment", "Left", "Center", "Right", "Stretch", Title = "Horizontal Alignment")]
+[ToolkitSampleMultiChoiceOption("LayoutVerticalAlignment", "Top", "Center", "Bottom", "Stretch", Title = "Vertical Alignment")]
+[ToolkitSampleNumericOption("ItemSpacing", 8, 0, 16, Title = "Item Spacing")]
+[ToolkitSampleNumericOption("LineSpacing", 2, 0, 16, Title = "Line Spacing")]
+[ToolkitSampleMultiChoiceOption("LayoutItemsJustification", "Automatic", "Start", "Center", "End", "SpaceAround", "SpaceBetween", "SpaceEvenly", Title = "Items Justification")]
+[ToolkitSampleMultiChoiceOption("LayoutItemsStretch", "None", "First", "Last", "Equal", "Proportional", Title = "Items Stretch")]
+
+[ToolkitSample(id: nameof(WrapPanel2BasicSample), $"Basic demo of the {nameof(WrapPanel2)} with auto-sized items.", description: $"A sample showing every property of the {nameof(WrapPanel2)} panel.")]
+public sealed partial class WrapPanel2BasicSample : Page
+{
+ public WrapPanel2BasicSample()
+ {
+ this.InitializeComponent();
+ }
+
+ // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149
+ public static Orientation ConvertStringToOrientation(string orientation) => orientation switch
+ {
+ "Vertical" => Orientation.Vertical,
+ "Horizontal" => Orientation.Horizontal,
+ _ => throw new System.NotImplementedException(),
+ };
+
+ // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149
+ public static HorizontalAlignment ConvertStringToHorizontalAlignment(string alignment) => alignment switch
+ {
+ "Left" => HorizontalAlignment.Left,
+ "Center" => HorizontalAlignment.Center,
+ "Right" => HorizontalAlignment.Right,
+ "Stretch" => HorizontalAlignment.Stretch,
+ _ => throw new System.NotImplementedException(),
+ };
+
+ // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149
+ public static VerticalAlignment ConvertStringToVerticalAlignment(string alignment) => alignment switch
+ {
+ "Top" => VerticalAlignment.Top,
+ "Center" => VerticalAlignment.Center,
+ "Bottom" => VerticalAlignment.Bottom,
+ "Stretch" => VerticalAlignment.Stretch,
+ _ => throw new System.NotImplementedException(),
+ };
+
+ // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149
+ public static WrapPanelItemsJustification ConvertStringToItemsJustification(string itemsJustification) => itemsJustification switch
+ {
+ "Automatic" => WrapPanelItemsJustification.Automatic,
+ "Start" => WrapPanelItemsJustification.Start,
+ "Center" => WrapPanelItemsJustification.Center,
+ "End" => WrapPanelItemsJustification.End,
+ "SpaceAround" => WrapPanelItemsJustification.SpaceAround,
+ "SpaceBetween" => WrapPanelItemsJustification.SpaceBetween,
+ "SpaceEvenly" => WrapPanelItemsJustification.SpaceEvenly,
+ _ => throw new System.NotImplementedException(),
+ };
+
+ // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149
+ public static WrapPanelItemsStretch ConvertStringToItemsStretch(string stretchMethod) => stretchMethod switch
+ {
+ "None" => WrapPanelItemsStretch.None,
+ "First" => WrapPanelItemsStretch.First,
+ "Last" => WrapPanelItemsStretch.Last,
+ "Equal" => WrapPanelItemsStretch.Equal,
+ "Proportional" => WrapPanelItemsStretch.Proportional,
+ _ => throw new System.NotImplementedException(),
+ };
+
+ private int _index;
+
+ private string[] LoremIpsumWords => LoremIpsum.Split(' ');
+
+ private string LoremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam fermentum placerat pretium. Phasellus molestie faucibus purus ut semper. Etiam felis ante, condimentum sed leo in, aliquam pharetra libero. Etiam ante ante, sagittis in semper eu, aliquam non sapien. Donec a pharetra magna. Suspendisse et nulla magna. Cras varius sem dolor, ac faucibus turpis malesuada ac. Maecenas rutrum tortor et faucibus rutrum. Vestibulum in gravida odio, non dapibus dui. Praesent leo tellus, vulputate sed sollicitudin id, fringilla quis ligula. Cras eget ex vitae purus pulvinar mattis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Donec consectetur tellus id augue ultrices, eget congue tellus pharetra.";
+
+ private void AddItemClick(object sender, RoutedEventArgs e)
+ {
+ AddItem();
+ }
+
+ private void Add5ItemsClick(object sender, RoutedEventArgs e)
+ {
+ for (int i = 0; i < 5; i++)
+ AddItem();
+ }
+
+ private void ClearItemsClick(object sender, RoutedEventArgs e)
+ {
+ WrapPanel.Children.Clear();
+ _index = 0;
+ }
+
+ private void AddItem()
+ {
+ _index = _index % LoremIpsumWords.Length;
+
+ var currentWord = LoremIpsumWords[_index++];
+ var border = new Border()
+ {
+ Child = new TextBlock()
+ {
+ Text = currentWord,
+ }
+ };
+
+ WrapPanel.Children.Add(border);
+ }
+}
diff --git a/components/WrapPanel2/samples/WrapPanel2MegaSample.xaml b/components/WrapPanel2/samples/WrapPanel2MegaSample.xaml
new file mode 100644
index 000000000..5f44bf13f
--- /dev/null
+++ b/components/WrapPanel2/samples/WrapPanel2MegaSample.xaml
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/WrapPanel2/samples/WrapPanel2MegaSample.xaml.cs b/components/WrapPanel2/samples/WrapPanel2MegaSample.xaml.cs
new file mode 100644
index 000000000..c11b14a43
--- /dev/null
+++ b/components/WrapPanel2/samples/WrapPanel2MegaSample.xaml.cs
@@ -0,0 +1,79 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Controls;
+
+namespace WrapPanel2Experiment.Samples;
+
+///
+/// An example sample page of a custom control inheriting from Panel.
+///
+[ToolkitSampleMultiChoiceOption("LayoutOrientation", "Horizontal", "Vertical", Title = "Orientation")]
+[ToolkitSampleMultiChoiceOption("LayoutHorizontalAlignment", "Left", "Center", "Right", "Stretch", Title = "Horizontal Alignment")]
+[ToolkitSampleMultiChoiceOption("LayoutVerticalAlignment", "Top", "Center", "Bottom", "Stretch", Title = "Vertical Alignment")]
+[ToolkitSampleNumericOption("ItemSpacing", 8, 0, 16, Title = "Item Spacing")]
+[ToolkitSampleNumericOption("LineSpacing", 2, 0, 16, Title = "Line Spacing")]
+[ToolkitSampleMultiChoiceOption("LayoutItemsJustification", "Automatic", "Start", "Center", "End", "SpaceAround", "SpaceBetween", "SpaceEvenly", Title = "Items Justification")]
+[ToolkitSampleMultiChoiceOption("LayoutItemsStretch", "None", "First", "Last", "Equal", "Proportional", Title = "Items Stretch")]
+
+[ToolkitSample(id: nameof(WrapPanel2MegaSample), "Demo of all WrapPanel2 feature", description: $"A sample showing every property of the {nameof(WrapPanel2)} panel.")]
+public sealed partial class WrapPanel2MegaSample : Page
+{
+ public WrapPanel2MegaSample()
+ {
+ this.InitializeComponent();
+ }
+
+ // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149
+ public static Orientation ConvertStringToOrientation(string orientation) => orientation switch
+ {
+ "Vertical" => Orientation.Vertical,
+ "Horizontal" => Orientation.Horizontal,
+ _ => throw new System.NotImplementedException(),
+ };
+
+ // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149
+ public static HorizontalAlignment ConvertStringToHorizontalAlignment(string alignment) => alignment switch
+ {
+ "Left" => HorizontalAlignment.Left,
+ "Center" => HorizontalAlignment.Center,
+ "Right" => HorizontalAlignment.Right,
+ "Stretch" => HorizontalAlignment.Stretch,
+ _ => throw new System.NotImplementedException(),
+ };
+
+ // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149
+ public static VerticalAlignment ConvertStringToVerticalAlignment(string alignment) => alignment switch
+ {
+ "Top" => VerticalAlignment.Top,
+ "Center" => VerticalAlignment.Center,
+ "Bottom" => VerticalAlignment.Bottom,
+ "Stretch" => VerticalAlignment.Stretch,
+ _ => throw new System.NotImplementedException(),
+ };
+
+ // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149
+ public static WrapPanelItemsJustification ConvertStringToItemsJustification(string itemsJustification) => itemsJustification switch
+ {
+ "Automatic" => WrapPanelItemsJustification.Automatic,
+ "Start" => WrapPanelItemsJustification.Start,
+ "Center" => WrapPanelItemsJustification.Center,
+ "End" => WrapPanelItemsJustification.End,
+ "SpaceAround" => WrapPanelItemsJustification.SpaceAround,
+ "SpaceBetween" => WrapPanelItemsJustification.SpaceBetween,
+ "SpaceEvenly" => WrapPanelItemsJustification.SpaceEvenly,
+ _ => throw new System.NotImplementedException(),
+ };
+
+ // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149
+ public static WrapPanelItemsStretch ConvertStringToItemsStretch(string stretchMethod) => stretchMethod switch
+ {
+ "None" => WrapPanelItemsStretch.None,
+ "First" => WrapPanelItemsStretch.First,
+ "Last" => WrapPanelItemsStretch.Last,
+ "Equal" => WrapPanelItemsStretch.Equal,
+ "Proportional" => WrapPanelItemsStretch.Proportional,
+ _ => throw new System.NotImplementedException(),
+ };
+}
diff --git a/components/WrapPanel2/samples/WrapPanel2ProportionalSample.xaml b/components/WrapPanel2/samples/WrapPanel2ProportionalSample.xaml
new file mode 100644
index 000000000..460d8f9d1
--- /dev/null
+++ b/components/WrapPanel2/samples/WrapPanel2ProportionalSample.xaml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/WrapPanel2/samples/WrapPanel2ProportionalSample.xaml.cs b/components/WrapPanel2/samples/WrapPanel2ProportionalSample.xaml.cs
new file mode 100644
index 000000000..cba863bfe
--- /dev/null
+++ b/components/WrapPanel2/samples/WrapPanel2ProportionalSample.xaml.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Controls;
+
+namespace WrapPanel2Experiment.Samples;
+
+///
+/// An example sample page of a custom control inheriting from Panel.
+///
+[ToolkitSample(id: nameof(WrapPanel2ProportionalSample), "Demo of proportional sizing", description: $"A sample showing every property of the {nameof(WrapPanel2)} panel.")]
+public sealed partial class WrapPanel2ProportionalSample : Page
+{
+ public WrapPanel2ProportionalSample()
+ {
+ this.InitializeComponent();
+ }
+}
diff --git a/components/WrapPanel2/src/CommunityToolkit.WinUI.Controls.WrapPanel2.csproj b/components/WrapPanel2/src/CommunityToolkit.WinUI.Controls.WrapPanel2.csproj
new file mode 100644
index 000000000..fef145482
--- /dev/null
+++ b/components/WrapPanel2/src/CommunityToolkit.WinUI.Controls.WrapPanel2.csproj
@@ -0,0 +1,14 @@
+
+
+
+
+ WrapPanel2
+ This package contains WrapPanel2.
+
+
+ CommunityToolkit.WinUI.Controls.WrapPanel2Rns
+
+
+
+
+
diff --git a/components/WrapPanel2/src/Dependencies.props b/components/WrapPanel2/src/Dependencies.props
new file mode 100644
index 000000000..e622e1df4
--- /dev/null
+++ b/components/WrapPanel2/src/Dependencies.props
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/WrapPanel2/src/MultiTarget.props b/components/WrapPanel2/src/MultiTarget.props
new file mode 100644
index 000000000..b11c19426
--- /dev/null
+++ b/components/WrapPanel2/src/MultiTarget.props
@@ -0,0 +1,9 @@
+
+
+
+ uwp;wasdk;wpf;wasm;linuxgtk;macos;ios;android;
+
+
\ No newline at end of file
diff --git a/components/WrapPanel2/src/WrapPanel2.Properties.cs b/components/WrapPanel2/src/WrapPanel2.Properties.cs
new file mode 100644
index 000000000..f1eaa3e95
--- /dev/null
+++ b/components/WrapPanel2/src/WrapPanel2.Properties.cs
@@ -0,0 +1,142 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace CommunityToolkit.WinUI.Controls;
+
+public partial class WrapPanel2
+{
+ ///
+ /// An attached property for identifying the requested layout of a child within the panel.
+ ///
+ public static readonly DependencyProperty LayoutLengthProperty =
+ DependencyProperty.RegisterAttached(
+ "LayoutLength",
+ typeof(GridLength),
+ typeof(WrapPanel2),
+ new PropertyMetadata(GridLength.Auto));
+
+ ///
+ /// Backing for the property.
+ ///
+ public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
+ nameof(Orientation),
+ typeof(Orientation),
+ typeof(WrapPanel2),
+ new PropertyMetadata(Orientation.Horizontal, OnPropertyChanged));
+
+ ///
+ /// Backing for the property.
+ ///
+ public static readonly DependencyProperty ItemSpacingProperty = DependencyProperty.Register(
+ nameof(ItemSpacing),
+ typeof(double),
+ typeof(WrapPanel2),
+ new PropertyMetadata(default(double), OnPropertyChanged));
+
+ ///
+ /// Backing for the property.
+ ///
+ public static readonly DependencyProperty LineSpacingProperty = DependencyProperty.Register(
+ nameof(LineSpacing),
+ typeof(double),
+ typeof(WrapPanel2),
+ new PropertyMetadata(default(double), OnPropertyChanged));
+
+ ///
+ /// Backing for the property.
+ ///
+ public static readonly DependencyProperty ItemsJustificationProperty = DependencyProperty.Register(
+ nameof(ItemsJustification),
+ typeof(WrapPanelItemsJustification),
+ typeof(WrapPanel2),
+ new PropertyMetadata(default(WrapPanelItemsJustification), OnPropertyChanged));
+
+ ///
+ /// Backing for the property.
+ ///
+ public static readonly DependencyProperty ItemsStretchProperty = DependencyProperty.Register(
+ nameof(ItemsStretch),
+ typeof(WrapPanelItemsStretch),
+ typeof(WrapPanel2),
+ new PropertyMetadata(default(WrapPanelItemsStretch), OnPropertyChanged));
+
+ ///
+ /// Gets or sets the direction in which child elements are arranged.
+ ///
+ public Orientation Orientation
+ {
+ get => (Orientation)GetValue(OrientationProperty);
+ set => SetValue(OrientationProperty, value);
+ }
+
+ ///
+ /// Gets or sets the distance between items in the same row or column.
+ ///
+ ///
+ /// When is set to a spacing mode (e.g., SpaceBetween) and is ,
+ /// this value acts as the minimum spacing between items.
+ ///
+ public double ItemSpacing
+ {
+ get => (double)GetValue(ItemSpacingProperty);
+ set => SetValue(ItemSpacingProperty, value);
+ }
+
+ ///
+ /// Gets or sets the distance between consecutive rows or columns.
+ ///
+ public double LineSpacing
+ {
+ get => (double)GetValue(LineSpacingProperty);
+ set => SetValue(LineSpacingProperty, value);
+ }
+
+ ///
+ /// Gets or sets how items are aligned and distributed within a single line.
+ ///
+ ///
+ /// If a non-stretching justification is used, items with a Star-Sized
+ /// will collapse to their minimum size while maintaining their relative proportions.
+ ///
+ public WrapPanelItemsJustification ItemsJustification
+ {
+ get => (WrapPanelItemsJustification)GetValue(ItemsJustificationProperty);
+ set => SetValue(ItemsJustificationProperty, value);
+ }
+
+ ///
+ /// Gets or sets the stretching behavior for items on lines that do not contain Star-sized elements.
+ ///
+ public WrapPanelItemsStretch ItemsStretch
+ {
+ get => (WrapPanelItemsStretch)GetValue(ItemsStretchProperty);
+ set => SetValue(ItemsStretchProperty, value);
+ }
+
+ ///
+ /// Gets the attached property for a given element.
+ ///
+ /// The element from which to read the property value.
+ /// The defining the item's sizing logic.
+ public static GridLength GetLayoutLength(DependencyObject obj) => (GridLength)obj.GetValue(LayoutLengthProperty);
+
+ ///
+ /// Sets the attached property for a given element.
+ ///
+ /// The element on which to set the property value.
+ /// The to apply.
+ public static void SetLayoutLength(DependencyObject obj, GridLength value) => obj.SetValue(LayoutLengthProperty, value);
+
+ private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var panel = (WrapPanel2)d;
+ panel.InvalidateMeasure();
+ }
+
+ private static void OnAlignmentPropertyChanged(DependencyObject obj, DependencyProperty prop)
+ {
+ var panel = (WrapPanel2)obj;
+ panel.InvalidateMeasure();
+ }
+}
diff --git a/components/WrapPanel2/src/WrapPanel2.Structs.cs b/components/WrapPanel2/src/WrapPanel2.Structs.cs
new file mode 100644
index 000000000..ccae71377
--- /dev/null
+++ b/components/WrapPanel2/src/WrapPanel2.Structs.cs
@@ -0,0 +1,181 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace CommunityToolkit.WinUI.Controls;
+
+public partial class WrapPanel2
+{
+ ///
+ /// A struct representing the specifications of a row or column in the panel.
+ ///
+ private struct RowSpec
+ {
+ public RowSpec(GridLength layout, UVCoord desiredSize)
+ {
+ switch (layout.GridUnitType)
+ {
+ case GridUnitType.Auto:
+ ReservedSpace = desiredSize.U;
+ break;
+ case GridUnitType.Pixel:
+ ReservedSpace = layout.Value;
+ break;
+ case GridUnitType.Star:
+ PortionsSum = layout.Value;
+ MinPortionSize = desiredSize.U / layout.Value;
+ break;
+ }
+
+ MaxInAxisSize = desiredSize.U;
+ MaxOffAxisSize = desiredSize.V;
+ ItemsCount = 1;
+ }
+
+ ///
+ /// Gets the total reserved space for spacing in the row/column.
+ ///
+ ///
+ /// Items with a fixed size or auto size contribute to this value.
+ ///
+ public double ReservedSpace { get; private set; }
+
+ ///
+ /// Gets the sum of portions in the row/column.
+ ///
+ ///
+ /// Items with a star-sized length contribute to this value.
+ ///
+ public double PortionsSum { get; private set; }
+
+ ///
+ /// Gets the maximum width/height of items in the row/column.
+ ///
+ ///
+ /// Height in vertical orientation, width in horizontal orientation.
+ ///
+ public double MaxInAxisSize { get; private set; }
+
+ ///
+ /// Gets the maximum width/height of items in the row/column.
+ ///
+ ///
+ /// Width in vertical orientation, height in horizontal orientation.
+ ///
+ public double MaxOffAxisSize { get; private set; }
+
+ ///
+ /// Gets the minimum size of a portion in the row/column.
+ ///
+ public double MinPortionSize { get; private set; }
+
+ ///
+ /// Gets the number of items in the row/column.
+ ///
+ public int ItemsCount { get; private set; }
+
+ ///
+ /// New size is only set if the size changes.
+ /// Otherwise it will be 0.
+ ///
+ public bool TryAdd(RowSpec addend, double spacing, double maxSize, WrapPanelItemsStretch stretching, WrapPanelItemsJustification justification)
+ {
+ // Check if adding the new spec would exceed the maximum size
+ var sum = this + addend;
+ if (sum.Measure(spacing, stretching, justification) > maxSize)
+ return false;
+
+ // Update the current spec to include the new spec
+ this = sum;
+ return true;
+ }
+
+ public readonly double Measure(double spacing, WrapPanelItemsStretch stretching, WrapPanelItemsJustification justification)
+ {
+ var totalSpacing = GetTotalSpacing(ItemsCount, spacing, justification);
+ var equalStretching = stretching is WrapPanelItemsStretch.Equal && IsSpacingJustified(justification);
+
+ // Handle equal-sized items child stretching.
+ // Without this check, children might become scrunched in the arrange
+ // step when they are made equal sizes.
+ if (equalStretching && PortionsSum is 0)
+ {
+ return (MaxInAxisSize * ItemsCount) + totalSpacing;
+ }
+
+ // Otherwise, base size is reserved space + spacing
+ var totalSize = ReservedSpace + totalSpacing;
+
+ // Also add star-sized items if applicable
+ if (!double.IsNaN(MinPortionSize) && !double.IsInfinity(MinPortionSize))
+ totalSize += MinPortionSize * PortionsSum;
+
+
+ return totalSize;
+ }
+
+ public static RowSpec operator +(RowSpec a, RowSpec b)
+ {
+ var combined = new RowSpec
+ {
+ ReservedSpace = a.ReservedSpace + b.ReservedSpace,
+ PortionsSum = a.PortionsSum + b.PortionsSum,
+ MinPortionSize = Math.Max(a.MinPortionSize, b.MinPortionSize),
+ MaxInAxisSize = Math.Max(a.MaxInAxisSize, b.MaxInAxisSize),
+ MaxOffAxisSize = Math.Max(a.MaxOffAxisSize, b.MaxOffAxisSize),
+ ItemsCount = a.ItemsCount + b.ItemsCount
+ };
+ return combined;
+ }
+ }
+
+ ///
+ /// A struct for mapping X/Y coordinates to an orientation adjusted U/V coordinate system.
+ ///
+ private struct UVCoord(double x, double y, Orientation orientation)
+ {
+ private readonly bool _horizontal = orientation is Orientation.Horizontal;
+
+ public UVCoord(Size size, Orientation orientation) : this(size.Width, size.Height, orientation)
+ {
+ }
+
+ public double X { get; set; } = x;
+
+ public double Y { get; set; } = y;
+
+ public double U
+ {
+ readonly get => _horizontal ? X : Y;
+ set
+ {
+ if (_horizontal)
+ {
+ X = value;
+ }
+ else
+ {
+ Y = value;
+ }
+ }
+ }
+
+ public double V
+ {
+ readonly get => _horizontal ? Y : X;
+ set
+ {
+ if (_horizontal)
+ {
+ Y = value;
+ }
+ else
+ {
+ X = value;
+ }
+ }
+ }
+
+ public readonly Size Size => new(X, Y);
+ }
+}
diff --git a/components/WrapPanel2/src/WrapPanel2.cs b/components/WrapPanel2/src/WrapPanel2.cs
new file mode 100644
index 000000000..756f449ac
--- /dev/null
+++ b/components/WrapPanel2/src/WrapPanel2.cs
@@ -0,0 +1,416 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace CommunityToolkit.WinUI.Controls;
+
+///
+/// A panel that arranges its children in a grid-like fashion, stretching them to fill available space.
+///
+public partial class WrapPanel2 : Panel
+{
+ private List? _rowSpecs;
+ private double _longestRowSize = 0;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public WrapPanel2()
+ {
+ RegisterPropertyChangedCallback(HorizontalAlignmentProperty, OnAlignmentPropertyChanged);
+ RegisterPropertyChangedCallback(VerticalAlignmentProperty, OnAlignmentPropertyChanged);
+ }
+
+ ///
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ _rowSpecs = [];
+ _longestRowSize = 0;
+
+ // Define XY/UV coordinate variables
+ var uvAvailableSize = new UVCoord(availableSize.Width, availableSize.Height, Orientation);
+
+ RowSpec currentRowSpec = default;
+
+ var elements = Children.Where(static e => e.Visibility is Visibility.Visible);
+
+ // Do nothing if the panel is empty
+ if (!elements.Any())
+ {
+ return new Size(0, 0);
+ }
+
+ foreach (var child in elements)
+ {
+ // Measure the child's desired size and get layout
+ child.Measure(availableSize);
+ var uvDesiredSize = new UVCoord(child.DesiredSize, Orientation);
+ var layoutLength = GetLayoutLength(child);
+
+ // Attempt to add the child to the current row/column
+ var spec = new RowSpec(layoutLength, uvDesiredSize);
+ if (!currentRowSpec.TryAdd(spec, ItemSpacing, uvAvailableSize.U, ItemsStretch, RealJustification))
+ {
+ // Could not add to current row/column
+ // Start a new row/column
+ _rowSpecs.Add(currentRowSpec);
+ var newSize = currentRowSpec.Measure(ItemSpacing, ItemsStretch, RealJustification);
+ _longestRowSize = Math.Max(_longestRowSize, newSize);
+ currentRowSpec = spec;
+ }
+ }
+
+ // Add the final row/column
+ _rowSpecs.Add(currentRowSpec);
+ _longestRowSize = Math.Max(_longestRowSize, currentRowSpec.Measure(ItemSpacing, ItemsStretch, RealJustification));
+
+ // Calculate final desired size
+ var uvSize = new UVCoord(0, 0, Orientation)
+ {
+ U = IsMainAxisStretch(uvAvailableSize.U) ? uvAvailableSize.U : _longestRowSize,
+ V = _rowSpecs.Sum(static rs => rs.MaxOffAxisSize) + (LineSpacing * (_rowSpecs.Count - 1))
+ };
+
+ // Clamp to available size and return
+ uvSize.U = Math.Min(uvSize.U, uvAvailableSize.U);
+ uvSize.V = Math.Min(uvSize.V, uvAvailableSize.V);
+ return uvSize.Size;
+ }
+
+ ///
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ // Do nothing if there are no rows/columns
+ if (_rowSpecs is null || _rowSpecs.Count is 0)
+ return new Size(0, 0);
+
+ // Create XY/UV coordinate variables
+ var pos = new UVCoord(0, 0, Orientation);
+ var uvFinalSize = new UVCoord(finalSize, Orientation);
+
+ // Adjust the starting position based on off-axis alignment
+ var contentHeight = _rowSpecs.Sum(static rs => rs.MaxOffAxisSize) + (LineSpacing * (_rowSpecs.Count - 1));
+ pos.V = GetStartByAlignment(GetOffAlignment(), contentHeight, uvFinalSize.V);
+
+ var childQueue = new Queue(Children.Where(static e => e.Visibility is Visibility.Visible));
+
+ foreach (var row in _rowSpecs)
+ {
+ // Arrange the row/column
+ ArrangeRow(ref pos, row, uvFinalSize, childQueue);
+ }
+
+ // "Arrange" remaning children by rendering them with zero size
+ while (childQueue.TryDequeue(out var child))
+ {
+ // Arrange with zero size
+ child.Arrange(new Rect(0, 0, 0, 0));
+ }
+
+ return finalSize;
+ }
+
+ private void ArrangeRow(ref UVCoord pos, RowSpec row, UVCoord uvFinalSize, Queue childQueue)
+ {
+ var spacingTotalSize = GetTotalSpacing(row.ItemsCount, ItemSpacing, RealJustification);
+ var remainingSpace = uvFinalSize.U - row.ReservedSpace - spacingTotalSize;
+ var portionSize = row.MinPortionSize;
+
+ // Determine if the desired alignment is stretched.
+ // Or if fixed row lengths are in use.
+ bool stretch = IsMainAxisStretch(uvFinalSize.U) || JustifiedSpacing;
+
+ // Calculate portion size if stretching
+ // Same logic applies for matching row lengths, since the size was determined during measure
+ if (stretch)
+ {
+ portionSize = remainingSpace / row.PortionsSum;
+ }
+
+ // Reset the starting U position
+ pos.U = 0;
+
+ // Set a flag for if the row is being forced to stretch
+ // Also declare a variable for the effective items spacing. This will be adjusted if needed for justification.
+ bool forceStretch = JustifiedSpacing && row.PortionsSum is 0 && ItemsStretch is not WrapPanelItemsStretch.None;
+ var itemSpacing = ItemSpacing;
+
+ // Setup portionSize for forced stretching
+ if (forceStretch)
+ {
+ portionSize = ItemsStretch switch
+ {
+ // The first child's size will be overridden to 1*
+ // Change portion size to fill remaining space plus its original size
+ WrapPanelItemsStretch.First =>
+ remainingSpace + GetChildSize(childQueue.Peek()),
+
+ // The last child's size will be overridden to 1*
+ // Change portion size to fill remaining space plus its original size
+ WrapPanelItemsStretch.Last =>
+ remainingSpace + GetChildSize(childQueue.ElementAt(row.ItemsCount - 1)),
+
+ // All children's sizes will be overridden to 1*
+ // Change portion size to evenly distribute remaining space
+ WrapPanelItemsStretch.Equal =>
+ (uvFinalSize.U - spacingTotalSize) / row.ItemsCount,
+
+ // All children's sizes will be overridden to star sizes proportional to their original size
+ // Change portion size to distribute remaining space proportionally
+ WrapPanelItemsStretch.Proportional =>
+ (uvFinalSize.U - spacingTotalSize) / row.ReservedSpace,
+
+ // Default case (should not be hit)
+ _ => row.MinPortionSize,
+ };
+ }
+ else if (JustifiedSpacing && row.PortionsSum is 0)
+ {
+ // If Item Justification is enabled and there's no proportional
+ // Adjust the spacing between items according to the justification mode.
+ var divisbleSpace = remainingSpace + spacingTotalSize;
+ itemSpacing = RealJustification switch
+ {
+ WrapPanelItemsJustification.SpaceBetween => divisbleSpace / (row.ItemsCount - 1),
+ WrapPanelItemsJustification.SpaceAround => divisbleSpace / row.ItemsCount,
+ WrapPanelItemsJustification.SpaceEvenly => divisbleSpace / (row.ItemsCount + 1),
+ _ => divisbleSpace / row.ItemsCount,
+ };
+ }
+
+ // Adjust the starting position
+ var rowSize = row.Measure(ItemSpacing, ItemsStretch, RealJustification);
+ pos.U = RealJustification switch
+ {
+ WrapPanelItemsJustification.SpaceAround => itemSpacing / 2,
+ WrapPanelItemsJustification.SpaceBetween => 0,
+ WrapPanelItemsJustification.SpaceEvenly => itemSpacing,
+
+ WrapPanelItemsJustification.Start or
+ WrapPanelItemsJustification.Center or
+ WrapPanelItemsJustification.End or _ => GetStartByAlignment(GetJustificationAlignment(), rowSize, uvFinalSize.U),
+ };
+
+ // Arrange each child in the row/column
+ for (int i = 0; i < row.ItemsCount; i++)
+ {
+ // Get the next child
+ var child = childQueue.Dequeue();
+
+ // Sanity check
+ if (child is null)
+ return;
+
+ // Determine the child's size
+ var size = GetChildSize(child, i, row, portionSize, forceStretch);
+
+ // NOTE: The arrange method is still in X/Y coordinate system
+ child.Arrange(new Rect(pos.X, pos.Y, size.X, size.Y));
+
+ // Advance the position
+ pos.U += size.U + itemSpacing;
+ }
+
+ // Advance to the next row/column
+ pos.V += row.MaxOffAxisSize + LineSpacing;
+ }
+
+ private UVCoord GetChildSize(UIElement child, int indexInRow, RowSpec row, double portionSize, bool forceStretch)
+ {
+ // Get layout and desired size
+ var layoutLength = GetLayoutLength(child);
+ var uvDesiredSize = new UVCoord(child.DesiredSize, Orientation);
+
+ // Override the layout based on the forced stretch method if necessary
+ if (forceStretch)
+ {
+ var oneStar = new GridLength(1, GridUnitType.Star);
+ layoutLength = ItemsStretch switch
+ {
+ // Override the first item's layout to 1*
+ WrapPanelItemsStretch.First when indexInRow is 0 => oneStar,
+
+ // Override the last item's layout to 1*
+ WrapPanelItemsStretch.Last when indexInRow == (row.ItemsCount - 1) => oneStar,
+
+ // Override all item's layouts to 1*
+ WrapPanelItemsStretch.Equal => oneStar,
+
+ // Override all item's layouts to star sizes proportional to their original size
+ WrapPanelItemsStretch.Proportional => layoutLength.GridUnitType switch
+ {
+ GridUnitType.Auto => new GridLength(uvDesiredSize.U, GridUnitType.Star),
+ GridUnitType.Pixel or _ => new GridLength(layoutLength.Value, GridUnitType.Star),
+ },
+
+ // If the above conditions aren't met, do nothing
+ _ => layoutLength,
+ };
+ }
+
+ // Determine the child's U size
+ double uSize = layoutLength.GridUnitType switch
+ {
+ GridUnitType.Auto => uvDesiredSize.U,
+ GridUnitType.Pixel => layoutLength.Value,
+ GridUnitType.Star => layoutLength.Value * portionSize,
+ _ => uvDesiredSize.U,
+ };
+
+ // Return the final size
+ return new UVCoord(0, 0, Orientation)
+ {
+ U = uSize,
+ V = row.MaxOffAxisSize
+ };
+ }
+
+ private static double GetStartByAlignment(Alignment alignment, double size, double availableSize)
+ {
+ return alignment switch
+ {
+ Alignment.Start => 0,
+ Alignment.Center => (availableSize / 2) - (size / 2),
+ Alignment.End => availableSize - size,
+ _ => 0,
+ };
+ }
+
+ ///
+ /// Gets the the alignment panels horizontal/vertical alignment along the main axis.
+ ///
+ private Alignment GetAlignment()
+ {
+ return Orientation switch
+ {
+ Orientation.Horizontal => HorizontalAlignment switch
+ {
+ HorizontalAlignment.Left => Alignment.Start,
+ HorizontalAlignment.Center => Alignment.Center,
+ HorizontalAlignment.Right => Alignment.End,
+ HorizontalAlignment.Stretch => Alignment.Stretch,
+ _ => Alignment.Start,
+ },
+ Orientation.Vertical => VerticalAlignment switch
+ {
+ VerticalAlignment.Top => Alignment.Start,
+ VerticalAlignment.Center => Alignment.Center,
+ VerticalAlignment.Bottom => Alignment.End,
+ VerticalAlignment.Stretch => Alignment.Stretch,
+ _ => Alignment.Start,
+ },
+ _ => Alignment.Start,
+ };
+ }
+
+ ///
+ /// Gets the the alignment panels horizontal/vertical alignment along the off axis.
+ ///
+ private Alignment GetOffAlignment()
+ {
+ return Orientation switch
+ {
+ Orientation.Horizontal => VerticalAlignment switch
+ {
+ VerticalAlignment.Top => Alignment.Start,
+ VerticalAlignment.Center => Alignment.Center,
+ VerticalAlignment.Bottom => Alignment.End,
+ VerticalAlignment.Stretch => Alignment.Stretch,
+ _ => Alignment.Start,
+ },
+ Orientation.Vertical => HorizontalAlignment switch
+ {
+ HorizontalAlignment.Left => Alignment.Start,
+ HorizontalAlignment.Center => Alignment.Center,
+ HorizontalAlignment.Right => Alignment.End,
+ HorizontalAlignment.Stretch => Alignment.Stretch,
+ _ => Alignment.Start,
+ },
+ _ => Alignment.Start,
+ };
+ }
+
+ private Alignment GetJustificationAlignment()
+ {
+ return RealJustification switch
+ {
+ WrapPanelItemsJustification.Start => Alignment.Start,
+ WrapPanelItemsJustification.Center => Alignment.Center,
+ WrapPanelItemsJustification.End => Alignment.End,
+ _ => Alignment.Stretch,
+ };
+ }
+
+ ///
+ /// Determine if the desired alignment is stretched.
+ /// Don't stretch if infinite space is available though. Attempting to divide infinite space will result in a crash.
+ ///
+ private bool IsMainAxisStretch(double availableSize) => GetAlignment() is Alignment.Stretch && !double.IsInfinity(availableSize);
+
+ ///
+ /// Gets whether or not the is adjusting spacing.
+ ///
+ private bool JustifiedSpacing => IsSpacingJustified(RealJustification);
+
+ ///
+ /// Gets the with the automatic option converted to the actual behavior.
+ ///
+ private WrapPanelItemsJustification RealJustification => ItemsJustification switch
+ {
+ WrapPanelItemsJustification.Automatic => GetAlignment() switch
+ {
+ Alignment.Start => WrapPanelItemsJustification.Start,
+ Alignment.Center => WrapPanelItemsJustification.Center,
+ Alignment.End => WrapPanelItemsJustification.End,
+ _ => WrapPanelItemsJustification.SpaceBetween,
+ },
+ _ => ItemsJustification,
+ };
+
+ private static bool IsSpacingJustified(WrapPanelItemsJustification justification) => justification switch
+ {
+ WrapPanelItemsJustification.Start or
+ WrapPanelItemsJustification.Center or
+ WrapPanelItemsJustification.End => false,
+
+ WrapPanelItemsJustification.SpaceAround or
+ WrapPanelItemsJustification.SpaceBetween or
+ WrapPanelItemsJustification.SpaceEvenly => true,
+
+ _ => false,
+ };
+
+ private static double GetTotalSpacing(int itemCount, double itemsSpacing, WrapPanelItemsJustification justification)
+ {
+ return justification switch
+ {
+ WrapPanelItemsJustification.SpaceAround => itemCount * itemsSpacing,
+ WrapPanelItemsJustification.SpaceEvenly => (itemCount + 1) * itemsSpacing,
+
+ WrapPanelItemsJustification.Start or
+ WrapPanelItemsJustification.Center or
+ WrapPanelItemsJustification.End or
+ WrapPanelItemsJustification.SpaceBetween or _ => (itemCount - 1) * itemsSpacing,
+ };
+ }
+
+ private double GetChildSize(UIElement child)
+ {
+ var childLayout = GetLayoutLength(child);
+
+ return childLayout.GridUnitType switch
+ {
+ GridUnitType.Auto => new UVCoord(child.DesiredSize, Orientation).U,
+ GridUnitType.Pixel => childLayout.Value,
+ _ => 0,
+ };
+ }
+
+ private enum Alignment
+ {
+ Start,
+ Center,
+ End,
+ Stretch
+ }
+}
diff --git a/components/WrapPanel2/src/WrapPanelItemsJustification.cs b/components/WrapPanel2/src/WrapPanelItemsJustification.cs
new file mode 100644
index 000000000..76cf146fe
--- /dev/null
+++ b/components/WrapPanel2/src/WrapPanelItemsJustification.cs
@@ -0,0 +1,46 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace CommunityToolkit.WinUI.Controls;
+
+///
+/// Describes how items are arranged between the margins of the main axis in the .
+///
+public enum WrapPanelItemsJustification
+{
+ ///
+ /// Items are arranged according to the control's alignment.
+ ///
+ Automatic,
+
+ ///
+ /// Items are aligned toward the start of the line (Left for Horizontal, Top for Vertical).
+ ///
+ Start,
+
+ ///
+ /// Items are centered within the line.
+ ///
+ Center,
+
+ ///
+ /// Items are aligned toward the end of the line (Right for Horizontal, Bottom for Vertical).
+ ///
+ End,
+
+ ///
+ /// Items are distributed with equal internal spacing and half-sized spacing at the margins.
+ ///
+ SpaceAround,
+
+ ///
+ /// Items are distributed with equal spacing between them; no spacing is applied at the margins.
+ ///
+ SpaceBetween,
+
+ ///
+ /// Items are distributed so that the spacing between any two items and the spacing to the margins is equal.
+ ///
+ SpaceEvenly,
+}
diff --git a/components/WrapPanel2/src/WrapPanelItemsStretch.cs b/components/WrapPanel2/src/WrapPanelItemsStretch.cs
new file mode 100644
index 000000000..a7044c379
--- /dev/null
+++ b/components/WrapPanel2/src/WrapPanelItemsStretch.cs
@@ -0,0 +1,43 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace CommunityToolkit.WinUI.Controls;
+
+///
+/// Describes the behavior for stretching childrenn in the wrap-panel.
+///
+///
+/// This only effects lines without a star-sized item.
+///
+public enum WrapPanelItemsStretch
+{
+ ///
+ /// No additional stretching is applied to non-star items.
+ ///
+ ///
+ /// Items with a Star-sized will still expand
+ /// if the justification mode allows for stretching to fill the line.
+ ///
+ None,
+
+ ///
+ /// The first item in the line is stretched to occupy all remaining space.
+ ///
+ First,
+
+ ///
+ /// The last item in the line is stretched to occupy all remaining space.
+ ///
+ Last,
+
+ ///
+ /// All items in the line are stretched to an equal size to fill the available space.
+ ///
+ Equal,
+
+ ///
+ /// All items in the line are stretched proportionally based on their desired size to fill the remaining space.
+ ///
+ Proportional,
+}
diff --git a/components/WrapPanel2/tests/ExampleWrapPanel2TestClass.cs b/components/WrapPanel2/tests/ExampleWrapPanel2TestClass.cs
new file mode 100644
index 000000000..5a41388c8
--- /dev/null
+++ b/components/WrapPanel2/tests/ExampleWrapPanel2TestClass.cs
@@ -0,0 +1,134 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.Tooling.TestGen;
+using CommunityToolkit.Tests;
+using CommunityToolkit.WinUI.Controls;
+
+namespace WrapPanel2Tests;
+
+[TestClass]
+public partial class ExampleWrapPanel2TestClass : VisualUITestBase
+{
+ // If you don't need access to UI objects directly or async code, use this pattern.
+ [TestMethod]
+ public void SimpleSynchronousExampleTest()
+ {
+ var assembly = typeof(WrapPanel2).Assembly;
+ var type = assembly.GetType(typeof(WrapPanel2).FullName ?? string.Empty);
+
+ Assert.IsNotNull(type, "Could not find WrapPanel2 type.");
+ Assert.AreEqual(typeof(WrapPanel2), type, "Type of WrapPanel2 does not match expected type.");
+ }
+
+ // If you don't need access to UI objects directly, use this pattern.
+ [TestMethod]
+ public async Task SimpleAsyncExampleTest()
+ {
+ await Task.Delay(250);
+
+ Assert.IsTrue(true);
+ }
+
+ // Example that shows how to check for exception throwing.
+ [TestMethod]
+ public void SimpleExceptionCheckTest()
+ {
+ // If you need to check exceptions occur for invalid inputs, etc...
+ // Use Assert.ThrowsException to limit the scope to where you expect the error to occur.
+ // Otherwise, using the ExpectedException attribute could swallow or
+ // catch other issues in setup code.
+ Assert.ThrowsException(() => throw new NotImplementedException());
+ }
+
+ // The UIThreadTestMethod automatically dispatches to the UI for us to work with UI objects.
+ [UIThreadTestMethod]
+ public void SimpleUIAttributeExampleTest()
+ {
+ var component = new WrapPanel2();
+ Assert.IsNotNull(component);
+ }
+
+ // The UIThreadTestMethod can also easily grab a XAML Page for us by passing its type as a parameter.
+ // This lets us actually test a control as it would behave within an actual application.
+ // The page will already be loaded by the time your test is called.
+ [UIThreadTestMethod]
+ public void SimpleUIExamplePageTest(ExampleWrapPanel2TestPage page)
+ {
+ // You can use the Toolkit Visual Tree helpers here to find the component by type or name:
+ var component = page.FindDescendant();
+
+ Assert.IsNotNull(component);
+
+ var componentByName = page.FindDescendant("WrapPanel2Control");
+
+ Assert.IsNotNull(componentByName);
+ }
+
+ // You can still do async work with a UIThreadTestMethod as well.
+ [UIThreadTestMethod]
+ public async Task SimpleAsyncUIExamplePageTest(ExampleWrapPanel2TestPage page)
+ {
+ // This helper can be used to wait for a rendering pass to complete.
+ // Note, this is already done by loading a Page with the [UIThreadTestMethod] helper.
+ await CompositionTargetHelper.ExecuteAfterCompositionRenderingAsync(() => { });
+
+ var component = page.FindDescendant();
+
+ Assert.IsNotNull(component);
+ }
+
+ //// ----------------------------- ADVANCED TEST SCENARIOS -----------------------------
+
+ // If you need to use DataRow, you can use this pattern with the UI dispatch still.
+ // Otherwise, checkout the UIThreadTestMethod attribute above.
+ // See https://github.com/CommunityToolkit/Labs-Windows/issues/186
+ [TestMethod]
+ public async Task ComplexAsyncUIExampleTest()
+ {
+ await EnqueueAsync(() =>
+ {
+ var component = new WrapPanel2();
+ Assert.IsNotNull(component);
+ });
+ }
+
+ // If you want to load other content not within a XAML page using the UIThreadTestMethod above.
+ // Then you can do that using the Load/UnloadTestContentAsync methods.
+ [TestMethod]
+ public async Task ComplexAsyncLoadUIExampleTest()
+ {
+ await EnqueueAsync(async () =>
+ {
+ var component = new WrapPanel2();
+ Assert.IsNotNull(component);
+ Assert.IsFalse(component.IsLoaded);
+
+ await LoadTestContentAsync(component);
+
+ Assert.IsTrue(component.IsLoaded);
+
+ await UnloadTestContentAsync(component);
+
+ Assert.IsFalse(component.IsLoaded);
+ });
+ }
+
+ // You can still use the UIThreadTestMethod to remove the extra layer for the dispatcher as well:
+ [UIThreadTestMethod]
+ public async Task ComplexAsyncLoadUIExampleWithoutDispatcherTest()
+ {
+ var component = new WrapPanel2();
+ Assert.IsNotNull(component);
+ Assert.IsFalse(component.IsLoaded);
+
+ await LoadTestContentAsync(component);
+
+ Assert.IsTrue(component.IsLoaded);
+
+ await UnloadTestContentAsync(component);
+
+ Assert.IsFalse(component.IsLoaded);
+ }
+}
diff --git a/components/WrapPanel2/tests/ExampleWrapPanel2TestPage.xaml b/components/WrapPanel2/tests/ExampleWrapPanel2TestPage.xaml
new file mode 100644
index 000000000..5e5b69b41
--- /dev/null
+++ b/components/WrapPanel2/tests/ExampleWrapPanel2TestPage.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/components/WrapPanel2/tests/ExampleWrapPanel2TestPage.xaml.cs b/components/WrapPanel2/tests/ExampleWrapPanel2TestPage.xaml.cs
new file mode 100644
index 000000000..0272a9498
--- /dev/null
+++ b/components/WrapPanel2/tests/ExampleWrapPanel2TestPage.xaml.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace WrapPanel2Tests;
+
+///
+/// An empty page that can be used on its own or navigated to within a Frame.
+///
+public sealed partial class ExampleWrapPanel2TestPage : Page
+{
+ public ExampleWrapPanel2TestPage()
+ {
+ this.InitializeComponent();
+ }
+}
diff --git a/components/WrapPanel2/tests/WrapPanel2.Tests.projitems b/components/WrapPanel2/tests/WrapPanel2.Tests.projitems
new file mode 100644
index 000000000..5dd673b98
--- /dev/null
+++ b/components/WrapPanel2/tests/WrapPanel2.Tests.projitems
@@ -0,0 +1,23 @@
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+ true
+ 1EFF9838-CA24-43C3-AA4F-0B321F74861B
+
+
+ WrapPanel2Tests
+
+
+
+
+ ExampleWrapPanel2TestPage.xaml
+
+
+
+
+ Designer
+ MSBuild:Compile
+
+
+
\ No newline at end of file
diff --git a/components/WrapPanel2/tests/WrapPanel2.Tests.shproj b/components/WrapPanel2/tests/WrapPanel2.Tests.shproj
new file mode 100644
index 000000000..8db9f9ff5
--- /dev/null
+++ b/components/WrapPanel2/tests/WrapPanel2.Tests.shproj
@@ -0,0 +1,13 @@
+
+
+
+ 1EFF9838-CA24-43C3-AA4F-0B321F74861B
+ 14.0
+
+
+
+
+
+
+
+