diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj index 1dc21a42558..cff34175f46 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj +++ b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj @@ -115,7 +115,7 @@ 6.1.0-build.6 - 2.2.190917002 + 2.3.200213001 0.7.0-alpha @@ -273,6 +273,7 @@ + @@ -367,6 +368,7 @@ + @@ -531,10 +533,16 @@ ImageCropperPage.xaml + + StaggeredLayoutPage.xaml + TokenizingTextBoxPage.xaml + + WrapLayoutPage.xaml + @@ -545,6 +553,8 @@ + + @@ -959,6 +969,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + MSBuild:Compile Designer @@ -1335,6 +1349,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + MSBuild:Compile Designer @@ -1408,6 +1426,10 @@ {daeb9cec-c817-33b2-74b2-bc379380db72} Microsoft.Toolkit.Uwp.UI.Controls.DataGrid + + {cb444381-18ba-4a51-bb32-3a498bcc1e99} + Microsoft.Toolkit.Uwp.UI.Controls.Layout + {e9faabfb-d726-42c1-83c1-cb46a29fea81} Microsoft.Toolkit.Uwp.UI.Controls diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/StaggeredLayout/StaggeredLayout.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/StaggeredLayout/StaggeredLayout.bind new file mode 100644 index 00000000000..089b5918bb1 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/StaggeredLayout/StaggeredLayout.bind @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/StaggeredLayout/StaggeredLayout.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/StaggeredLayout/StaggeredLayout.png new file mode 100644 index 00000000000..08ab2b77ce7 Binary files /dev/null and b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/StaggeredLayout/StaggeredLayout.png differ diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/StaggeredLayout/StaggeredLayoutPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/StaggeredLayout/StaggeredLayoutPage.xaml new file mode 100644 index 00000000000..2ab42395d48 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/StaggeredLayout/StaggeredLayoutPage.xaml @@ -0,0 +1,14 @@ + + + + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/StaggeredLayout/StaggeredLayoutPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/StaggeredLayout/StaggeredLayoutPage.xaml.cs new file mode 100644 index 00000000000..7b62a3b8e2a --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/StaggeredLayout/StaggeredLayoutPage.xaml.cs @@ -0,0 +1,56 @@ +// 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 System; +using System.Collections.ObjectModel; +using Microsoft.Toolkit.Uwp.UI.Extensions; +using Microsoft.UI.Xaml.Controls; +using Windows.UI; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages +{ + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + public sealed partial class StaggeredLayoutPage : Page, IXamlRenderListener + { + private ObservableCollection _items = new ObservableCollection(); + private Random _random; + + public StaggeredLayoutPage() + { + this.InitializeComponent(); + + _random = new Random(DateTime.Now.Millisecond); + for (int i = 0; i < _random.Next(1000, 5000); i++) + { + var item = new Item { Index = i, Width = _random.Next(50, 250), Height = _random.Next(50, 250), Color = Color.FromArgb(255, (byte)_random.Next(0, 255), (byte)_random.Next(0, 255), (byte)_random.Next(0, 255)) }; + _items.Add(item); + } + } + + public void OnXamlRendered(FrameworkElement control) + { + var repeater = control.FindChildByName("StaggeredRepeater") as ItemsRepeater; + + if (repeater != null) + { + repeater.ItemsSource = _items; + } + } + + private class Item + { + public int Index { get; internal set; } + + public int Width { get; internal set; } + + public int Height { get; internal set; } + + public Color Color { get; internal set; } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/WrapLayout/WrapLayout.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/WrapLayout/WrapLayout.bind new file mode 100644 index 00000000000..18549c16f82 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/WrapLayout/WrapLayout.bind @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/WrapLayout/WrapLayout.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/WrapLayout/WrapLayout.png new file mode 100644 index 00000000000..5e7ae5ab2d1 Binary files /dev/null and b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/WrapLayout/WrapLayout.png differ diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/WrapLayout/WrapLayoutPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/WrapLayout/WrapLayoutPage.xaml new file mode 100644 index 00000000000..fcfebb4223b --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/WrapLayout/WrapLayoutPage.xaml @@ -0,0 +1,15 @@ + + + + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/WrapLayout/WrapLayoutPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/WrapLayout/WrapLayoutPage.xaml.cs new file mode 100644 index 00000000000..97b27f8d55b --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/WrapLayout/WrapLayoutPage.xaml.cs @@ -0,0 +1,56 @@ +// 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 System; +using System.Collections.ObjectModel; +using Microsoft.Toolkit.Uwp.UI.Extensions; +using Microsoft.UI.Xaml.Controls; +using Windows.UI; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages +{ + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + public sealed partial class WrapLayoutPage : Page, IXamlRenderListener + { + private ObservableCollection _items = new ObservableCollection(); + private Random _random; + + public WrapLayoutPage() + { + this.InitializeComponent(); + + _random = new Random(DateTime.Now.Millisecond); + for (int i = 0; i < _random.Next(1000, 5000); i++) + { + var item = new Item { Index = i, Width = _random.Next(50, 250), Height = _random.Next(50, 250), Color = Color.FromArgb(255, (byte)_random.Next(0, 255), (byte)_random.Next(0, 255), (byte)_random.Next(0, 255)) }; + _items.Add(item); + } + } + + public void OnXamlRendered(FrameworkElement control) + { + var repeater = control.FindChildByName("WrapRepeater") as ItemsRepeater; + + if (repeater != null) + { + repeater.ItemsSource = _items; + } + } + + private class Item + { + public int Index { get; internal set; } + + public int Width { get; internal set; } + + public int Height { get; internal set; } + + public Color Color { get; internal set; } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json index e40eb165412..d440632a6d2 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json @@ -229,6 +229,16 @@ "Icon": "/SamplePages/WrapPanel/WrapPanel.png", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/WrapPanel.md" }, + { + "Name": "WrapLayout", + "Type": "WrapLayoutPage", + "Subcategory": "Layout - ItemsRepeater", + "About": "The WrapLayout virtualizes child elements in sequential position from left to right, breaking content to the next line at the edge of the containing box.", + "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Layout/WrapLayout", + "XamlCodeFile": "WrapLayout.bind", + "Icon": "/SamplePages/WrapLayout/WrapLayout.png", + "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/layout/WrapLayout.md" + }, { "Name": "OrbitView", "Type": "OrbitViewPage", @@ -302,6 +312,16 @@ "Icon": "/SamplePages/StaggeredPanel/StaggeredPanel.png", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/StaggeredPanel.md" }, + { + "Name": "StaggeredLayout", + "Type": "StaggeredLayoutPage", + "Subcategory": "Layout - ItemsRepeater", + "About": "The StaggeredLayout virtualizes items in a column approach where an item will be added to whichever column has used the least amount of space.", + "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Layout/StaggeredLayout", + "XamlCodeFile": "StaggeredLayout.bind", + "Icon": "/SamplePages/StaggeredLayout/StaggeredLayout.png", + "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/layout/StaggeredLayout.md" + }, { "Name": "LayoutTransformControl", "Type": "LayoutTransformControlPage", @@ -424,7 +444,7 @@ "XamlCodeFile": "FocusTrackerXaml.bind", "Icon": "/SamplePages/FocusTracker/FocusTracker.png", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/developer-tools/FocusTracker.md" - }//, + } //, //{ // "Name": "TokenizingTextBox", // "Type": "TokenizingTextBoxPage", diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Microsoft.Toolkit.Uwp.UI.Controls.Layout.csproj b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Microsoft.Toolkit.Uwp.UI.Controls.Layout.csproj new file mode 100644 index 00000000000..b1f67ff8750 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Microsoft.Toolkit.Uwp.UI.Controls.Layout.csproj @@ -0,0 +1,31 @@ + + + + uap10.0.17134 + 10.0.18362.0 + Windows Community Toolkit Layout + + This library provides XAML layout controls. It is part of the Windows Community Toolkit. + + Microsoft.Toolkit.Uwp.UI.Controls + + + + + + + + + + + + + + + + + + + + + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Microsoft.Toolkit.Uwp.UI.Controls.Layout.csproj.DotSettings b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Microsoft.Toolkit.Uwp.UI.Controls.Layout.csproj.DotSettings new file mode 100644 index 00000000000..65848b5a192 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Microsoft.Toolkit.Uwp.UI.Controls.Layout.csproj.DotSettings @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Properties/AssemblyInfo.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..64a4e43ebc0 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Properties/AssemblyInfo.cs @@ -0,0 +1,12 @@ +// 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 System.Resources; +using System.Runtime.CompilerServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: InternalsVisibleTo("UnitTests")] +[assembly: NeutralResourcesLanguage("en-US")] \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Properties/Microsoft.Windows.Toolkit.UI.Controls.Layout.rd.xml b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Properties/Microsoft.Windows.Toolkit.UI.Controls.Layout.rd.xml new file mode 100644 index 00000000000..342912dbe1c --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Properties/Microsoft.Windows.Toolkit.UI.Controls.Layout.rd.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/StaggeredLayout/StaggeredColumnLayout.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/StaggeredLayout/StaggeredColumnLayout.cs new file mode 100644 index 00000000000..5d3ce1f9b58 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/StaggeredLayout/StaggeredColumnLayout.cs @@ -0,0 +1,26 @@ +// 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 System.Collections.Generic; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + [System.Diagnostics.DebuggerDisplay("Count = {Count}, Height = {Height}")] + internal class StaggeredColumnLayout : List + { + public double Height { get; private set; } + + public new void Add(StaggeredItem item) + { + Height = item.Top + item.Height; + base.Add(item); + } + + public new void Clear() + { + Height = 0; + base.Clear(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/StaggeredLayout/StaggeredItem.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/StaggeredLayout/StaggeredItem.cs new file mode 100644 index 00000000000..c1bcac34815 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/StaggeredLayout/StaggeredItem.cs @@ -0,0 +1,27 @@ +// 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 System; +using Microsoft.UI.Xaml.Controls; +using Windows.Foundation; +using Windows.UI.Xaml; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + internal class StaggeredItem + { + public StaggeredItem(int index) + { + this.Index = index; + } + + public double Top { get; internal set; } + + public double Height { get; internal set; } + + public int Index { get; } + + public UIElement Element { get; internal set; } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/StaggeredLayout/StaggeredLayout.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/StaggeredLayout/StaggeredLayout.cs new file mode 100644 index 00000000000..350c14be778 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/StaggeredLayout/StaggeredLayout.cs @@ -0,0 +1,328 @@ +// 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 System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using Microsoft.UI.Xaml.Controls; +using Windows.Foundation; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Arranges child elements into a staggered grid pattern where items are added to the column that has used least amount of space. + /// + public class StaggeredLayout : VirtualizingLayout + { + /// + /// Initializes a new instance of the class. + /// + public StaggeredLayout() + { + } + + /// + /// Gets or sets the desired width for each column. + /// + /// + /// The width of columns can exceed the DesiredColumnWidth if the HorizontalAlignment is set to Stretch. + /// + public double DesiredColumnWidth + { + get { return (double)GetValue(DesiredColumnWidthProperty); } + set { SetValue(DesiredColumnWidthProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + /// The identifier for the dependency property. + public static readonly DependencyProperty DesiredColumnWidthProperty = DependencyProperty.Register( + nameof(DesiredColumnWidth), + typeof(double), + typeof(StaggeredLayout), + new PropertyMetadata(250d, OnDesiredColumnWidthChanged)); + + /// + /// Gets or sets the spacing between columns of items. + /// + public double ColumnSpacing + { + get { return (double)GetValue(ColumnSpacingProperty); } + set { SetValue(ColumnSpacingProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ColumnSpacingProperty = DependencyProperty.Register( + nameof(ColumnSpacing), + typeof(double), + typeof(StaggeredLayout), + new PropertyMetadata(0d, OnSpacingChanged)); + + /// + /// Gets or sets the spacing between rows of items. + /// + public double RowSpacing + { + get { return (double)GetValue(RowSpacingProperty); } + set { SetValue(RowSpacingProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty RowSpacingProperty = DependencyProperty.Register( + nameof(RowSpacing), + typeof(double), + typeof(StaggeredLayout), + new PropertyMetadata(0d, OnSpacingChanged)); + + /// + protected override void InitializeForContextCore(VirtualizingLayoutContext context) + { + context.LayoutState = new StaggeredLayoutState(context); + base.InitializeForContextCore(context); + } + + /// + protected override void UninitializeForContextCore(VirtualizingLayoutContext context) + { + context.LayoutState = null; + base.UninitializeForContextCore(context); + } + + /// + protected override void OnItemsChangedCore(VirtualizingLayoutContext context, object source, NotifyCollectionChangedEventArgs args) + { + var state = (StaggeredLayoutState)context.LayoutState; + + switch (args.Action) + { + case NotifyCollectionChangedAction.Add: + state.RemoveFromIndex(args.NewStartingIndex); + break; + case NotifyCollectionChangedAction.Replace: + state.RemoveFromIndex(args.NewStartingIndex); + + // We must recycle the element to ensure that it gets the correct context + state.RecycleElementAt(args.NewStartingIndex); + break; + case NotifyCollectionChangedAction.Move: + int minIndex = Math.Min(args.NewStartingIndex, args.OldStartingIndex); + int maxIndex = Math.Max(args.NewStartingIndex, args.OldStartingIndex); + state.RemoveRange(minIndex, maxIndex); + break; + case NotifyCollectionChangedAction.Remove: + state.RemoveFromIndex(args.OldStartingIndex); + break; + case NotifyCollectionChangedAction.Reset: + state.Clear(); + break; + } + + base.OnItemsChangedCore(context, source, args); + } + + /// + protected override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize) + { + if (context.ItemCount == 0) + { + return new Size(availableSize.Width, 0); + } + + if ((context.RealizationRect.Width == 0) && (context.RealizationRect.Height == 0)) + { + return new Size(availableSize.Width, 0.0); + } + + var state = (StaggeredLayoutState)context.LayoutState; + + double availableWidth = availableSize.Width; + double availableHeight = availableSize.Height; + + double columnWidth = Math.Min(DesiredColumnWidth, availableWidth); + if (columnWidth != state.ColumnWidth) + { + // The items will need to be remeasured + state.Clear(); + } + + state.ColumnWidth = Math.Min(DesiredColumnWidth, availableWidth); + int numColumns = Math.Max(1, (int)Math.Floor(availableWidth / state.ColumnWidth)); + + // adjust for column spacing on all columns expect the first + double totalWidth = state.ColumnWidth + ((numColumns - 1) * (state.ColumnWidth + ColumnSpacing)); + if (totalWidth > availableWidth) + { + numColumns--; + } + else if (double.IsInfinity(availableWidth)) + { + availableWidth = totalWidth; + } + + if (numColumns != state.NumberOfColumns) + { + // The items will not need to be remeasured, but they will need to go into new columns + state.ClearColumns(); + } + + if (RowSpacing != state.RowSpacing) + { + // If the RowSpacing changes the height of the rows will be different. + // The columns stores the height so we'll want to clear them out to + // get the proper height + state.ClearColumns(); + state.RowSpacing = RowSpacing; + } + + var columnHeights = new double[numColumns]; + var itemsPerColumn = new int[numColumns]; + var deadColumns = new HashSet(); + + for (int i = 0; i < context.ItemCount; i++) + { + var columnIndex = GetColumnIndex(columnHeights); + + bool measured = false; + StaggeredItem item = state.GetItemAt(i); + if (item.Height == 0) + { + // Item has not been measured yet. Get the element and store the values + item.Element = context.GetOrCreateElementAt(i); + item.Element.Measure(new Size(state.ColumnWidth, availableHeight)); + item.Height = item.Element.DesiredSize.Height; + measured = true; + } + + double spacing = itemsPerColumn[columnIndex] > 0 ? RowSpacing : 0; + item.Top = columnHeights[columnIndex] + spacing; + double bottom = item.Top + item.Height; + columnHeights[columnIndex] = bottom; + itemsPerColumn[columnIndex]++; + state.AddItemToColumn(item, columnIndex); + + if (bottom < context.RealizationRect.Top) + { + // The bottom of the element is above the realization area + if (item.Element != null) + { + context.RecycleElement(item.Element); + item.Element = null; + } + } + else if (item.Top > context.RealizationRect.Bottom) + { + // The top of the element is below the realization area + if (item.Element != null) + { + context.RecycleElement(item.Element); + item.Element = null; + } + + deadColumns.Add(columnIndex); + } + else if (measured == false) + { + // We ALWAYS want to measure an item that will be in the bounds + item.Element = context.GetOrCreateElementAt(i); + item.Element.Measure(new Size(state.ColumnWidth, availableHeight)); + if (item.Height != item.Element.DesiredSize.Height) + { + // this item changed size; we need to recalculate layout for everything after this + state.RemoveFromIndex(i + 1); + item.Height = item.Element.DesiredSize.Height; + columnHeights[columnIndex] = item.Top + item.Height; + } + } + + if (deadColumns.Count == numColumns) + { + break; + } + } + + double desiredHeight = state.GetHeight(); + + return new Size(availableWidth, desiredHeight); + } + + /// + protected override Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize) + { + if ((context.RealizationRect.Width == 0) && (context.RealizationRect.Height == 0)) + { + return finalSize; + } + + var state = (StaggeredLayoutState)context.LayoutState; + + // Cycle through each column and arrange the items that are within the realization bounds + for (int columnIndex = 0; columnIndex < state.NumberOfColumns; columnIndex++) + { + StaggeredColumnLayout layout = state.GetColumnLayout(columnIndex); + for (int i = 0; i < layout.Count; i++) + { + StaggeredItem item = layout[i]; + + double bottom = item.Top + item.Height; + if (bottom < context.RealizationRect.Top) + { + // element is above the realization bounds + continue; + } + + if (item.Top <= context.RealizationRect.Bottom) + { + double itemHorizontalOffset = (state.ColumnWidth * columnIndex) + (ColumnSpacing * columnIndex); + + Rect bounds = new Rect(itemHorizontalOffset, item.Top, state.ColumnWidth, item.Height); + UIElement element = context.GetOrCreateElementAt(item.Index); + element.Arrange(bounds); + } + else + { + break; + } + } + } + + return finalSize; + } + + private static void OnDesiredColumnWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var panel = (StaggeredLayout)d; + panel.InvalidateMeasure(); + } + + private static void OnSpacingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var panel = (StaggeredLayout)d; + panel.InvalidateMeasure(); + } + + private static int GetColumnIndex(double[] columnHeights) + { + int columnIndex = 0; + double height = columnHeights[0]; + for (int j = 1; j < columnHeights.Length; j++) + { + if (columnHeights[j] < height) + { + columnIndex = j; + height = columnHeights[j]; + } + } + + return columnIndex; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/StaggeredLayout/StaggeredLayoutState.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/StaggeredLayout/StaggeredLayoutState.cs new file mode 100644 index 00000000000..15ce0c0d0ce --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/StaggeredLayout/StaggeredLayoutState.cs @@ -0,0 +1,197 @@ +// 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 System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.UI.Xaml.Controls; +using Windows.UI.Xaml; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + internal class StaggeredLayoutState + { + private List _items = new List(); + private VirtualizingLayoutContext _context; + private Dictionary _columnLayout = new Dictionary(); + private double _lastAverageHeight; + + public StaggeredLayoutState(VirtualizingLayoutContext context) + { + _context = context; + } + + public double ColumnWidth { get; internal set; } + + public int NumberOfColumns + { + get + { + return _columnLayout.Count; + } + } + + public double RowSpacing { get; internal set; } + + internal void AddItemToColumn(StaggeredItem item, int columnIndex) + { + if (_columnLayout.TryGetValue(columnIndex, out StaggeredColumnLayout columnLayout) == false) + { + columnLayout = new StaggeredColumnLayout(); + _columnLayout[columnIndex] = columnLayout; + } + + if (columnLayout.Contains(item) == false) + { + columnLayout.Add(item); + } + } + + internal StaggeredItem GetItemAt(int index) + { + if (index < 0) + { + throw new IndexOutOfRangeException(); + } + + if (index <= (_items.Count - 1)) + { + return _items[index]; + } + else + { + StaggeredItem item = new StaggeredItem(index); + _items.Add(item); + return item; + } + } + + internal StaggeredColumnLayout GetColumnLayout(int columnIndex) + { + _columnLayout.TryGetValue(columnIndex, out StaggeredColumnLayout columnLayout); + return columnLayout; + } + + /// + /// Clear everything that has been calculated. + /// + internal void Clear() + { + _columnLayout.Clear(); + _items.Clear(); + } + + /// + /// Clear the layout columns so they will be recalculated. + /// + internal void ClearColumns() + { + _columnLayout.Clear(); + } + + /// + /// Gets the estimated height of the layout. + /// + /// The estimated height of the layout. + /// + /// If all of the items have been calculated then the actual height will be returned. + /// If all of the items have not been calculated then an estimated height will be calculated based on the average height of the items. + /// + internal double GetHeight() + { + double desiredHeight = Enumerable.Max(_columnLayout.Values, c => c.Height); + + var itemCount = Enumerable.Sum(_columnLayout.Values, c => c.Count); + if (itemCount == _context.ItemCount) + { + return desiredHeight; + } + + double averageHeight = 0; + foreach (var kvp in _columnLayout) + { + averageHeight += kvp.Value.Height / kvp.Value.Count; + } + + averageHeight /= _columnLayout.Count; + double estimatedHeight = (averageHeight * _context.ItemCount) / _columnLayout.Count; + if (estimatedHeight > desiredHeight) + { + desiredHeight = estimatedHeight; + } + + if (Math.Abs(desiredHeight - _lastAverageHeight) < 5) + { + return _lastAverageHeight; + } + + _lastAverageHeight = desiredHeight; + return desiredHeight; + } + + internal void RecycleElementAt(int index) + { + UIElement element = _context.GetOrCreateElementAt(index); + _context.RecycleElement(element); + } + + internal void RemoveFromIndex(int index) + { + if (index >= _items.Count) + { + // Item was added/removed but we haven't realized that far yet + return; + } + + int numToRemove = _items.Count - index; + _items.RemoveRange(index, numToRemove); + + foreach (var kvp in _columnLayout) + { + StaggeredColumnLayout layout = kvp.Value; + for (int i = 0; i < layout.Count; i++) + { + if (layout[i].Index >= index) + { + numToRemove = layout.Count - i; + layout.RemoveRange(i, numToRemove); + break; + } + } + } + } + + internal void RemoveRange(int startIndex, int endIndex) + { + for (int i = startIndex; i <= endIndex; i++) + { + if (i > _items.Count) + { + break; + } + + StaggeredItem item = _items[i]; + item.Height = 0; + item.Top = 0; + + // We must recycle all elements to ensure that it gets the correct context + RecycleElementAt(i); + } + + foreach (var kvp in _columnLayout) + { + StaggeredColumnLayout layout = kvp.Value; + for (int i = 0; i < layout.Count; i++) + { + if ((startIndex <= layout[i].Index) && (layout[i].Index <= endIndex)) + { + int numToRemove = layout.Count - i; + layout.RemoveRange(i, numToRemove); + break; + } + } + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/VisualStudioToolsManifest.xml b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/VisualStudioToolsManifest.xml new file mode 100644 index 00000000000..8d2ad7fe8bf --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/VisualStudioToolsManifest.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/WrapLayout/UvBounds.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/WrapLayout/UvBounds.cs new file mode 100644 index 00000000000..616275c4d99 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/WrapLayout/UvBounds.cs @@ -0,0 +1,38 @@ +// 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 Windows.Foundation; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + internal struct UvBounds + { + public UvBounds(Orientation orientation, Rect rect) + { + if (orientation == Orientation.Horizontal) + { + UMin = rect.Left; + UMax = rect.Right; + VMin = rect.Top; + VMax = rect.Bottom; + } + else + { + UMin = rect.Top; + UMax = rect.Bottom; + VMin = rect.Left; + VMax = rect.Right; + } + } + + public double UMin { get; } + + public double UMax { get; } + + public double VMin { get; } + + public double VMax { get; } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/WrapLayout/UvMeasure.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/WrapLayout/UvMeasure.cs new file mode 100644 index 00000000000..9aca6edf8c5 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/WrapLayout/UvMeasure.cs @@ -0,0 +1,47 @@ +// 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 Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + [System.Diagnostics.DebuggerDisplay("U = {U} V = {V}")] + internal struct UvMeasure + { + internal static readonly UvMeasure Zero = default(UvMeasure); + + internal double U { get; set; } + + internal double V { get; set; } + + public UvMeasure(Orientation orientation, double width, double height) + { + if (orientation == Orientation.Horizontal) + { + U = width; + V = height; + } + else + { + U = height; + V = width; + } + } + + public override bool Equals(object obj) + { + if (obj is UvMeasure measure) + { + return (measure.U == U) && (measure.V == V); + } + + return false; + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/WrapLayout/WrapItem.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/WrapLayout/WrapItem.cs new file mode 100644 index 00000000000..ea5ac2f90f3 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/WrapLayout/WrapItem.cs @@ -0,0 +1,24 @@ +// 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 Windows.UI.Xaml; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + internal class WrapItem + { + public WrapItem(int index) + { + this.Index = index; + } + + public int Index { get; } + + public UvMeasure? Measure { get; internal set; } + + public UvMeasure? Position { get; internal set; } + + public UIElement Element { get; internal set; } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/WrapLayout/WrapLayout.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/WrapLayout/WrapLayout.cs new file mode 100644 index 00000000000..7895892fe32 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/WrapLayout/WrapLayout.cs @@ -0,0 +1,334 @@ +// 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 System; +using System.Collections.Specialized; +using Microsoft.UI.Xaml.Controls; +using Windows.Foundation; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Arranges elements by wrapping them to fit the available space. + /// When is set to Orientation.Horizontal, element are arranged in rows until the available width is reached and then to a new row. + /// When is set to Orientation.Vertical, element are arranged in columns until the available height is reached. + /// + public class WrapLayout : VirtualizingLayout + { + /// + /// Gets or sets a uniform Horizontal distance (in pixels) between items when is set to Horizontal, + /// or between columns of items when is set to Vertical. + /// + public double HorizontalSpacing + { + get { return (double)GetValue(HorizontalSpacingProperty); } + set { SetValue(HorizontalSpacingProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty HorizontalSpacingProperty = + DependencyProperty.Register( + nameof(HorizontalSpacing), + typeof(double), + typeof(WrapLayout), + new PropertyMetadata(0d, LayoutPropertyChanged)); + + /// + /// Gets or sets a uniform Vertical distance (in pixels) between items when is set to Vertical, + /// or between rows of items when is set to Horizontal. + /// + public double VerticalSpacing + { + get { return (double)GetValue(VerticalSpacingProperty); } + set { SetValue(VerticalSpacingProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty VerticalSpacingProperty = + DependencyProperty.Register( + nameof(VerticalSpacing), + typeof(double), + typeof(WrapLayout), + new PropertyMetadata(0d, LayoutPropertyChanged)); + + /// + /// Gets or sets the orientation of the WrapLayout. + /// Horizontal means that child controls will be added horizontally until the width of the panel is reached, then a new row is added to add new child controls. + /// Vertical means that children will be added vertically until the height of the panel is reached, then a new column is added. + /// + public Orientation Orientation + { + get { return (Orientation)GetValue(OrientationProperty); } + set { SetValue(OrientationProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty OrientationProperty = + DependencyProperty.Register( + nameof(Orientation), + typeof(Orientation), + typeof(WrapLayout), + new PropertyMetadata(Orientation.Horizontal, LayoutPropertyChanged)); + + private static void LayoutPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is WrapLayout wp) + { + wp.InvalidateMeasure(); + wp.InvalidateArrange(); + } + } + + /// + protected override void InitializeForContextCore(VirtualizingLayoutContext context) + { + var state = new WrapLayoutState(context); + context.LayoutState = state; + base.InitializeForContextCore(context); + } + + /// + protected override void UninitializeForContextCore(VirtualizingLayoutContext context) + { + context.LayoutState = null; + base.UninitializeForContextCore(context); + } + + /// + protected override void OnItemsChangedCore(VirtualizingLayoutContext context, object source, NotifyCollectionChangedEventArgs args) + { + var state = (WrapLayoutState)context.LayoutState; + + switch (args.Action) + { + case NotifyCollectionChangedAction.Add: + state.RemoveFromIndex(args.NewStartingIndex); + break; + case NotifyCollectionChangedAction.Move: + int minIndex = Math.Min(args.NewStartingIndex, args.OldStartingIndex); + state.RemoveFromIndex(minIndex); + + state.RecycleElementAt(args.OldStartingIndex); + state.RecycleElementAt(args.NewStartingIndex); + break; + case NotifyCollectionChangedAction.Remove: + state.RemoveFromIndex(args.OldStartingIndex); + break; + case NotifyCollectionChangedAction.Replace: + state.RemoveFromIndex(args.NewStartingIndex); + state.RecycleElementAt(args.NewStartingIndex); + break; + case NotifyCollectionChangedAction.Reset: + state.Clear(); + break; + } + + base.OnItemsChangedCore(context, source, args); + } + + /// + protected override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize) + { + var totalMeasure = UvMeasure.Zero; + var parentMeasure = new UvMeasure(Orientation, availableSize.Width, availableSize.Height); + var spacingMeasure = new UvMeasure(Orientation, HorizontalSpacing, VerticalSpacing); + var realizationBounds = new UvBounds(Orientation, context.RealizationRect); + var position = UvMeasure.Zero; + + var state = (WrapLayoutState)context.LayoutState; + if (state.Orientation != Orientation) + { + state.SetOrientation(Orientation); + } + + if (spacingMeasure.Equals(state.Spacing) == false) + { + state.ClearPositions(); + state.Spacing = spacingMeasure; + } + + if (state.AvailableU != parentMeasure.U) + { + state.ClearPositions(); + state.AvailableU = parentMeasure.U; + } + + double currentV = 0; + for (int i = 0; i < context.ItemCount; i++) + { + bool measured = false; + WrapItem item = state.GetItemAt(i); + if (item.Measure == null) + { + item.Element = context.GetOrCreateElementAt(i); + item.Element.Measure(availableSize); + item.Measure = new UvMeasure(Orientation, item.Element.DesiredSize.Width, item.Element.DesiredSize.Height); + measured = true; + } + + UvMeasure currentMeasure = item.Measure.Value; + if (currentMeasure.U == 0) + { + continue; // ignore collapsed items + } + + if (item.Position == null) + { + if (parentMeasure.U < position.U + currentMeasure.U) + { + // New Row + position.U = 0; + position.V += currentV + spacingMeasure.V; + currentV = 0; + } + + item.Position = position; + } + + position = item.Position.Value; + + double vEnd = position.V + currentMeasure.V; + if (vEnd < realizationBounds.VMin) + { + // Item is "above" the bounds + if (item.Element != null) + { + context.RecycleElement(item.Element); + item.Element = null; + } + } + else if (position.V > realizationBounds.VMax) + { + // Item is "below" the bounds. + if (item.Element != null) + { + context.RecycleElement(item.Element); + item.Element = null; + } + + // We don't need to measure anything below the bounds + break; + } + else if (measured == false) + { + // Always measure elements that are within the bounds + item.Element = context.GetOrCreateElementAt(i); + item.Element.Measure(availableSize); + + currentMeasure = new UvMeasure(Orientation, item.Element.DesiredSize.Width, item.Element.DesiredSize.Height); + if (currentMeasure.Equals(item.Measure) == false) + { + // this item changed size; we need to recalculate layout for everything after this + state.RemoveFromIndex(i + 1); + item.Measure = currentMeasure; + + // did the change make it go into the new row? + if (parentMeasure.U < position.U + currentMeasure.U) + { + // New Row + position.U = 0; + position.V += currentV + spacingMeasure.V; + currentV = 0; + } + + item.Position = position; + } + } + + position.U += currentMeasure.U + spacingMeasure.U; + currentV = Math.Max(currentMeasure.V, currentV); + } + + // update value with the last line + // if the the last loop is(parentMeasure.U > currentMeasure.U + lineMeasure.U) the total isn't calculated then calculate it + // if the last loop is (parentMeasure.U > currentMeasure.U) the currentMeasure isn't added to the total so add it here + // for the last condition it is zeros so adding it will make no difference + // this way is faster than an if condition in every loop for checking the last item + totalMeasure.U = parentMeasure.U; + totalMeasure.V = state.GetHeight(); + + totalMeasure.U = Math.Ceiling(totalMeasure.U); + + return Orientation == Orientation.Horizontal ? new Size(totalMeasure.U, totalMeasure.V) : new Size(totalMeasure.V, totalMeasure.U); + } + + /// + protected override Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize) + { + if (context.ItemCount > 0) + { + var parentMeasure = new UvMeasure(Orientation, finalSize.Width, finalSize.Height); + var spacingMeasure = new UvMeasure(Orientation, HorizontalSpacing, VerticalSpacing); + var realizationBounds = new UvBounds(Orientation, context.RealizationRect); + + var state = (WrapLayoutState)context.LayoutState; + bool Arrange(WrapItem item, bool isLast = false) + { + if (item.Measure.HasValue == false) + { + return false; + } + + if (item.Position == null) + { + return false; + } + + var desiredMeasure = item.Measure.Value; + if (desiredMeasure.U == 0) + { + return true; // if an item is collapsed, avoid adding the spacing + } + + UvMeasure position = item.Position.Value; + + // Stretch the last item to fill the available space + if (isLast) + { + desiredMeasure.U = parentMeasure.U - position.U; + } + + if (((position.V + desiredMeasure.V) >= realizationBounds.VMin) && (position.V <= realizationBounds.VMax)) + { + // place the item + UIElement child = context.GetOrCreateElementAt(item.Index); + if (Orientation == Orientation.Horizontal) + { + child.Arrange(new Rect(position.U, position.V, desiredMeasure.U, desiredMeasure.V)); + } + else + { + child.Arrange(new Rect(position.V, position.U, desiredMeasure.V, desiredMeasure.U)); + } + } + else if (position.V > realizationBounds.VMax) + { + return false; + } + + return true; + } + + for (var i = 0; i < context.ItemCount; i++) + { + bool continueArranging = Arrange(state.GetItemAt(i)); + if (continueArranging == false) + { + break; + } + } + } + + return finalSize; + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/WrapLayout/WrapLayoutState.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/WrapLayout/WrapLayoutState.cs new file mode 100644 index 00000000000..e9a55cfd40f --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/WrapLayout/WrapLayoutState.cs @@ -0,0 +1,146 @@ +// 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 System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.UI.Xaml.Controls; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + internal class WrapLayoutState + { + private List _items = new List(); + private VirtualizingLayoutContext _context; + + public WrapLayoutState(VirtualizingLayoutContext context) + { + this._context = context; + } + + public Orientation Orientation { get; private set; } + + public UvMeasure Spacing { get; internal set; } + + public double AvailableU { get; internal set; } + + internal WrapItem GetItemAt(int index) + { + if (index < 0) + { + throw new IndexOutOfRangeException(); + } + + if (index <= (_items.Count - 1)) + { + return _items[index]; + } + else + { + WrapItem item = new WrapItem(index); + _items.Add(item); + return item; + } + } + + internal void Clear() + { + _items.Clear(); + } + + internal void RemoveFromIndex(int index) + { + if (index >= _items.Count) + { + // Item was added/removed but we haven't realized that far yet + return; + } + + int numToRemove = _items.Count - index; + _items.RemoveRange(index, numToRemove); + } + + internal void SetOrientation(Orientation orientation) + { + foreach (var item in _items.Where(i => i.Measure.HasValue)) + { + UvMeasure measure = item.Measure.Value; + double v = measure.V; + measure.V = measure.U; + measure.U = v; + item.Measure = measure; + item.Position = null; + } + + Orientation = orientation; + AvailableU = 0; + } + + internal void ClearPositions() + { + foreach (var item in _items) + { + item.Position = null; + } + } + + internal double GetHeight() + { + if (_items.Count == 0) + { + return 0; + } + + bool calculateAverage = true; + if ((_items.Count == _context.ItemCount) && _items[_items.Count - 1].Position.HasValue) + { + calculateAverage = false; + } + + UvMeasure? lastPosition = null; + double maxV = 0; + + int itemCount = _items.Count; + for (int i = _items.Count - 1; i >= 0; i--) + { + var item = _items[i]; + if (item.Position == null) + { + itemCount--; + continue; + } + + if (lastPosition != null) + { + if (lastPosition.Value.V > item.Position.Value.V) + { + // This is a row above the last item. Exit and calculate the average + break; + } + } + + lastPosition = item.Position; + maxV = Math.Max(maxV, item.Measure.Value.V); + } + + double totalHeight = lastPosition.Value.V + maxV; + if (calculateAverage) + { + return (totalHeight / itemCount) * _context.ItemCount; + } + else + { + return totalHeight; + } + } + + internal void RecycleElementAt(int index) + { + UIElement element = _context.GetOrCreateElementAt(index); + _context.RecycleElement(element); + } + } +} \ No newline at end of file diff --git a/Windows Community Toolkit.sln b/Windows Community Toolkit.sln index a7a3f016cb5..b963eda466f 100644 --- a/Windows Community Toolkit.sln +++ b/Windows Community Toolkit.sln @@ -90,6 +90,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GazeInputTest", "GazeInputT EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.Uwp.UI.Media", "Microsoft.Toolkit.Uwp.UI.Media\Microsoft.Toolkit.Uwp.UI.Media.csproj", "{75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.Uwp.UI.Controls.Layout", "Microsoft.Toolkit.Uwp.UI.Controls.Layout\Microsoft.Toolkit.Uwp.UI.Controls.Layout.csproj", "{CB444381-18BA-4A51-BB32-3A498BCC1E99}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.HighPerformance", "Microsoft.Toolkit.HighPerformance\Microsoft.Toolkit.HighPerformance.csproj", "{7E30D48C-4CD8-47BE-B557-10A20391DCC4}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests.HighPerformance.NetCore", "UnitTests\UnitTests.HighPerformance.NetCore\UnitTests.HighPerformance.NetCore.csproj", "{D9BDBC68-3D0A-47FC-9C88-0BF769101644}" @@ -813,6 +814,36 @@ Global {75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}.Release|x64.Build.0 = Release|Any CPU {75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}.Release|x86.ActiveCfg = Release|Any CPU {75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}.Release|x86.Build.0 = Release|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Debug|ARM.ActiveCfg = Debug|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Debug|ARM.Build.0 = Debug|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Debug|ARM64.Build.0 = Debug|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Debug|x64.ActiveCfg = Debug|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Debug|x64.Build.0 = Debug|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Debug|x86.ActiveCfg = Debug|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Debug|x86.Build.0 = Debug|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Native|Any CPU.ActiveCfg = Debug|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Native|Any CPU.Build.0 = Debug|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Native|ARM.ActiveCfg = Debug|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Native|ARM.Build.0 = Debug|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Native|ARM64.ActiveCfg = Debug|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Native|ARM64.Build.0 = Debug|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Native|x64.ActiveCfg = Debug|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Native|x64.Build.0 = Debug|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Native|x86.ActiveCfg = Debug|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Native|x86.Build.0 = Debug|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Release|Any CPU.Build.0 = Release|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Release|ARM.ActiveCfg = Release|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Release|ARM.Build.0 = Release|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Release|ARM64.ActiveCfg = Release|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Release|ARM64.Build.0 = Release|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Release|x64.ActiveCfg = Release|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Release|x64.Build.0 = Release|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Release|x86.ActiveCfg = Release|Any CPU + {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Release|x86.Build.0 = Release|Any CPU {7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Debug|Any CPU.Build.0 = Debug|Any CPU {7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Debug|ARM.ActiveCfg = Debug|Any CPU @@ -919,6 +950,7 @@ Global {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF} = {096ECFD7-7035-4487-9C87-81DCE9389620} {A122EA02-4DE7-413D-BFBF-AF7DFC668DD6} = {B30036C4-D514-4E5B-A323-587A061772CE} {75F9EE44-3EFA-47BC-AEDD-351B9834A0AF} = {F1AFFFA7-28FE-4770-BA48-10D76F3E59BC} + {CB444381-18BA-4A51-BB32-3A498BCC1E99} = {F1AFFFA7-28FE-4770-BA48-10D76F3E59BC} {D9BDBC68-3D0A-47FC-9C88-0BF769101644} = {262CDB74-CF21-47AC-8DD9-CBC33C73B7CF} {9B3A94A6-0D29-4523-880B-6938E2EFEEF7} = {262CDB74-CF21-47AC-8DD9-CBC33C73B7CF} {262CDB74-CF21-47AC-8DD9-CBC33C73B7CF} = {B30036C4-D514-4E5B-A323-587A061772CE}