From 4a637aa0738313587a8ef09cad6a25aa2a255e94 Mon Sep 17 00:00:00 2001 From: michael-hawker <24302614+michael-hawker@users.noreply.github.com> Date: Thu, 9 Dec 2021 12:18:35 -0800 Subject: [PATCH 1/6] [WIP] Add Initial CanvasLayout for ItemsRepeater Issues: - Text isn't realized on first load if all items on screen (may be because we aren't getting/measuring containers in current measure step) - Scrolling is getting locked trying to keep an item on screen, not sure what this may be (maybe not recycling containers?) Need to try properly measuring/recylcing containers and see if that resolves any current issues... --- .../CanvasLayout.Sample.csproj | 2 + .../SampleOne/SamplePage.xaml.cs | 2 +- .../SampleThree/SamplePage3.xaml | 39 +++++++ .../SampleThree/SamplePage3.xaml.cs | 62 ++++++++++ .../SampleTwo/SamplePage2.xaml.cs | 2 +- Labs/CanvasLayout/src/CanvasLayout.cs | 110 ++++++++++++++++++ 6 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleThree/SamplePage3.xaml create mode 100644 Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleThree/SamplePage3.xaml.cs diff --git a/Labs/CanvasLayout/samples/CanvasLayout.Sample/CanvasLayout.Sample.csproj b/Labs/CanvasLayout/samples/CanvasLayout.Sample/CanvasLayout.Sample.csproj index 935bb389d..e8f62ae6e 100644 --- a/Labs/CanvasLayout/samples/CanvasLayout.Sample/CanvasLayout.Sample.csproj +++ b/Labs/CanvasLayout/samples/CanvasLayout.Sample/CanvasLayout.Sample.csproj @@ -19,11 +19,13 @@ + + diff --git a/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleOne/SamplePage.xaml.cs b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleOne/SamplePage.xaml.cs index b9e80ba2b..faaaa4411 100644 --- a/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleOne/SamplePage.xaml.cs +++ b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleOne/SamplePage.xaml.cs @@ -51,7 +51,7 @@ namespace CanvasLayout.Sample.SampleOne [ToolkitSampleMultiChoiceOption("TextFontFamily", label: "Arial", value: "Arial")] [ToolkitSampleMultiChoiceOption("TextFontFamily", label: "Consolas", value: "Consolas")] - [ToolkitSample(id: nameof(SamplePage), "Canvas Layout", ToolkitSampleCategory.Controls, ToolkitSampleSubcategory.Layout, description: "A canvas-like VirtualizingPanel for use in an ItemsControl")] + [ToolkitSample(id: nameof(SamplePage), "Simple Options", ToolkitSampleCategory.Controls, ToolkitSampleSubcategory.Layout, description: "A sample page for showing how to do simple options.")] public sealed partial class SamplePage : Page { public SamplePage() diff --git a/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleThree/SamplePage3.xaml b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleThree/SamplePage3.xaml new file mode 100644 index 000000000..97bd1e84b --- /dev/null +++ b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleThree/SamplePage3.xaml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + diff --git a/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleThree/SamplePage3.xaml.cs b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleThree/SamplePage3.xaml.cs new file mode 100644 index 000000000..b9e80ba2b --- /dev/null +++ b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleThree/SamplePage3.xaml.cs @@ -0,0 +1,62 @@ +// 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.IO; +using System.Linq; +using CommunityToolkit.Labs.Core.SourceGenerators; +using CommunityToolkit.Labs.Core.SourceGenerators.Attributes; +using System.Runtime.InteropServices.WindowsRuntime; + +#if !WINAPPSDK +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; +#else +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Navigation; +#endif + +// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238 + +namespace CanvasLayout.Sample.SampleOne +{ + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + [ToolkitSampleBoolOption("IsTextVisible", "IsVisible", true)] + + [ToolkitSampleMultiChoiceOption("TextForeground", label: "Teal", value: "#0ddc8c", title: "Text foreground")] + [ToolkitSampleMultiChoiceOption("TextForeground", label: "Sand", value: "#e7a676")] + [ToolkitSampleMultiChoiceOption("TextForeground", label: "Dull green", value: "#5d7577")] + + [ToolkitSampleMultiChoiceOption("TextSize", label: "Small", value: "12", title: "Text size")] + [ToolkitSampleMultiChoiceOption("TextSize", label: "Normal", value: "16")] + [ToolkitSampleMultiChoiceOption("TextSize", label: "Big", value: "32")] + + [ToolkitSampleMultiChoiceOption("TextFontFamily", label: "Segoe UI", value: "Segoe UI")] + [ToolkitSampleMultiChoiceOption("TextFontFamily", label: "Arial", value: "Arial")] + [ToolkitSampleMultiChoiceOption("TextFontFamily", label: "Consolas", value: "Consolas")] + + [ToolkitSample(id: nameof(SamplePage), "Canvas Layout", ToolkitSampleCategory.Controls, ToolkitSampleSubcategory.Layout, description: "A canvas-like VirtualizingPanel for use in an ItemsControl")] + public sealed partial class SamplePage : Page + { + public SamplePage() + { + this.InitializeComponent(); + } + } +} diff --git a/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleTwo/SamplePage2.xaml.cs b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleTwo/SamplePage2.xaml.cs index c6ff1026d..b84d8295a 100644 --- a/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleTwo/SamplePage2.xaml.cs +++ b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleTwo/SamplePage2.xaml.cs @@ -13,7 +13,7 @@ namespace CanvasLayout.Sample.SampleTwo { - [ToolkitSample(id: nameof(SamplePage2), "Example sample", ToolkitSampleCategory.Controls, ToolkitSampleSubcategory.Layout, description: "An empty sample used to demonstrate the sample system.")] + [ToolkitSample(id: nameof(SamplePage2), "Custom options", ToolkitSampleCategory.Controls, ToolkitSampleSubcategory.Layout, description: "An empty sample used to demonstrate the sample system.")] public sealed partial class SamplePage2 : UserControl { public SamplePage2() diff --git a/Labs/CanvasLayout/src/CanvasLayout.cs b/Labs/CanvasLayout/src/CanvasLayout.cs index 8f3c2edba..f4017dcde 100644 --- a/Labs/CanvasLayout/src/CanvasLayout.cs +++ b/Labs/CanvasLayout/src/CanvasLayout.cs @@ -2,11 +2,121 @@ // 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 Microsoft.UI.Xaml.Controls; +using Windows.Foundation; namespace CommunityToolkit.Labs.Uwp { public class CanvasLayout : VirtualizingLayout { + #region Setup / teardown + protected override void InitializeForContextCore(VirtualizingLayoutContext context) + { + base.InitializeForContextCore(context); + + if (!(context.LayoutState is CanvasLayoutState state)) + { + // Store any state we might need since (in theory) the layout could be in use by multiple + // elements simultaneously + context.LayoutState = new CanvasLayoutState(); + } + } + + protected override void UninitializeForContextCore(VirtualizingLayoutContext context) + { + base.UninitializeForContextCore(context); + + // clear any state + context.LayoutState = null; + } + + #endregion + + #region Layout + + protected override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize) + { + int maxWidth = 0; + int maxHeight = 0; + + // Get underlying data about positioning of items and determine if in viewport. + for (int i = 0; i < context.ItemCount; i++) + { + if (context.GetItemAt(i) is CanvasLayoutItem item) + { + // See if this item pushes our maximum boundary + maxWidth = Math.Max(item.Left + item.Width, maxWidth); + maxHeight = Math.Max(item.Top + item.Height, maxHeight); + + // Calculate if this item is in our current viewport + Rect rect = new(item.Left, item.Top, item.Width, item.Height); + rect.Intersect(context.RealizationRect); + + // TODO: If the item is currently in view and will be in view, we should recycle the element's container + item.IsInView = rect.Width > 0 || rect.Height > 0; + + // TODO: If item is in view, we should call GetElementOrCreateAt to realize container here. (And call it's measure method.) + } + } + + return new Size(maxWidth, maxHeight); + } + + protected override Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize) + { + for (int i = 0; i < context.ItemCount; i++) + { + if (context.GetItemAt(i) is CanvasLayoutItem item && item.IsInView) + { + var container = context.GetOrCreateElementAt(i); + // Is it better to have cached this from above? + container.Arrange(new Rect(item.Left, item.Top, item.Width, item.Height)); + } + } + + return finalSize; + } + + #endregion + } + + internal class CanvasLayoutState + { + public int FirstRealizedIndex { get; set; } + + /// + /// List of layout bounds for items starting with the + /// FirstRealizedIndex. + /// + public List LayoutRects + { + get + { + if (_layoutRects == null) + { + _layoutRects = new List(); + } + + return _layoutRects; + } + } + + private List _layoutRects; + } + + // TODO: Make DP? Can we do this with property mapping instead? + public class CanvasLayoutItem + { + public int Left { get; set; } + + public int Top { get; set; } + + public int Width { get; set; } + + public int Height { get; set; } + + public bool IsInView { get; internal set; } } } From 018c73030319eb33c26f53fa2118cc30dd68eb37 Mon Sep 17 00:00:00 2001 From: michael-hawker <24302614+michael-hawker@users.noreply.github.com> Date: Thu, 9 Dec 2021 12:45:52 -0800 Subject: [PATCH 2/6] Add Uno Guard --- .../samples/CanvasLayout.Sample/SampleThree/SamplePage3.xaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleThree/SamplePage3.xaml b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleThree/SamplePage3.xaml index 97bd1e84b..e9f9f25e5 100644 --- a/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleThree/SamplePage3.xaml +++ b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleThree/SamplePage3.xaml @@ -6,6 +6,7 @@ xmlns:local="using:CanvasLayout.Sample" xmlns:muxc="using:Microsoft.UI.Xaml.Controls" xmlns:labs="using:CommunityToolkit.Labs.Uwp" + xmlns:win="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" mc:Ignorable="d"> @@ -29,7 +30,7 @@ FontSize="24" FontWeight="Bold" HorizontalAlignment="Center" - TextLineBounds="Tight" + win:TextLineBounds="Tight" VerticalAlignment="Center"/> From 4f28464e463da385bb5067555ae35dda65c9eb81 Mon Sep 17 00:00:00 2001 From: michael-hawker <24302614+michael-hawker@users.noreply.github.com> Date: Thu, 9 Dec 2021 13:02:25 -0800 Subject: [PATCH 3/6] Add container realization to CanvasLayout Still has issue in UWP about scrolling getting stuck... (had tested seemed ok in WASM) --- Labs/CanvasLayout/src/CanvasLayout.cs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Labs/CanvasLayout/src/CanvasLayout.cs b/Labs/CanvasLayout/src/CanvasLayout.cs index f4017dcde..c0b9f221d 100644 --- a/Labs/CanvasLayout/src/CanvasLayout.cs +++ b/Labs/CanvasLayout/src/CanvasLayout.cs @@ -54,10 +54,24 @@ protected override Size MeasureOverride(VirtualizingLayoutContext context, Size Rect rect = new(item.Left, item.Top, item.Width, item.Height); rect.Intersect(context.RealizationRect); - // TODO: If the item is currently in view and will be in view, we should recycle the element's container + // Check if we're in view now so we can compare to if we were last time. + bool nowInView = rect.Width > 0 || rect.Height > 0; + + // If it wasn't visible and now is, realize the container + if (nowInView && !item.IsInView) + { + var element = context.GetOrCreateElementAt(i); + element.Measure(new Size(item.Width, item.Height)); + } + // If it was visible, but now isn't recycle the container + else if (!nowInView && item.IsInView) + { + var element = context.GetOrCreateElementAt(i); + context.RecycleElement(element); + } + + // Update our current visibility item.IsInView = rect.Width > 0 || rect.Height > 0; - - // TODO: If item is in view, we should call GetElementOrCreateAt to realize container here. (And call it's measure method.) } } From 0187f676d4027fa0b9efc5dad31aecc42e2f5a9e Mon Sep 17 00:00:00 2001 From: michael-hawker <24302614+michael-hawker@users.noreply.github.com> Date: Wed, 12 Jan 2022 15:44:04 -0800 Subject: [PATCH 4/6] Add project references? --- .../samples/CanvasLayout.Wasm/CanvasLayout.Wasm.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/Labs/CanvasLayout/samples/CanvasLayout.Wasm/CanvasLayout.Wasm.csproj b/Labs/CanvasLayout/samples/CanvasLayout.Wasm/CanvasLayout.Wasm.csproj index 9613ce94f..3588925e7 100644 --- a/Labs/CanvasLayout/samples/CanvasLayout.Wasm/CanvasLayout.Wasm.csproj +++ b/Labs/CanvasLayout/samples/CanvasLayout.Wasm/CanvasLayout.Wasm.csproj @@ -7,6 +7,7 @@ + \ No newline at end of file From f914d3b81fb30e952bbda7ed9dba2be000d86689 Mon Sep 17 00:00:00 2001 From: michael-hawker <24302614+michael-hawker@users.noreply.github.com> Date: Tue, 8 Mar 2022 16:59:28 -0800 Subject: [PATCH 5/6] Fix messed up rebase --- .../SampleThree/SamplePage3.xaml | 5 ++- .../SampleThree/SamplePage3.xaml.cs | 41 ++++++++++--------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleThree/SamplePage3.xaml b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleThree/SamplePage3.xaml index e9f9f25e5..19dc5ee21 100644 --- a/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleThree/SamplePage3.xaml +++ b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleThree/SamplePage3.xaml @@ -1,9 +1,10 @@ /// An empty page that can be used on its own or navigated to within a Frame. /// - [ToolkitSampleBoolOption("IsTextVisible", "IsVisible", true)] - - [ToolkitSampleMultiChoiceOption("TextForeground", label: "Teal", value: "#0ddc8c", title: "Text foreground")] - [ToolkitSampleMultiChoiceOption("TextForeground", label: "Sand", value: "#e7a676")] - [ToolkitSampleMultiChoiceOption("TextForeground", label: "Dull green", value: "#5d7577")] - - [ToolkitSampleMultiChoiceOption("TextSize", label: "Small", value: "12", title: "Text size")] - [ToolkitSampleMultiChoiceOption("TextSize", label: "Normal", value: "16")] - [ToolkitSampleMultiChoiceOption("TextSize", label: "Big", value: "32")] - - [ToolkitSampleMultiChoiceOption("TextFontFamily", label: "Segoe UI", value: "Segoe UI")] - [ToolkitSampleMultiChoiceOption("TextFontFamily", label: "Arial", value: "Arial")] - [ToolkitSampleMultiChoiceOption("TextFontFamily", label: "Consolas", value: "Consolas")] - - [ToolkitSample(id: nameof(SamplePage), "Canvas Layout", ToolkitSampleCategory.Controls, ToolkitSampleSubcategory.Layout, description: "A canvas-like VirtualizingPanel for use in an ItemsControl")] - public sealed partial class SamplePage : Page + [ToolkitSample(id: nameof(SamplePage3), "Canvas Layout", ToolkitSampleCategory.Controls, ToolkitSampleSubcategory.Layout, description: "A canvas-like VirtualizingLayout for use in an ItemsRepeater")] + public sealed partial class SamplePage3 : Page { - public SamplePage() + public ObservableCollection Items = new() + { + new() { Left = 100, Top = 50, Width = 100, Height = 100, Text = "Item 1" }, + new() { Left = 400, Top = 250, Width = 200, Height = 200, Text = "Item 2" }, + new() { Left = 200, Top = 500, Width = 100, Height = 100, Text = "Item 3" }, + new() { Left = 1200, Top = 2500, Width = 100, Height = 100, Text = "Item 4" }, + new() { Left = 2200, Top = 1500, Width = 100, Height = 100, Text = "Item 5" }, + new() { Left = 1200, Top = 3500, Width = 100, Height = 100, Text = "Item 6" }, + }; + + public SamplePage3() { this.InitializeComponent(); } } + + public class CanvasItem : CanvasLayoutItem + { + public string Text { get; set; } + } } From 8efb390c95d7f56350b67697813d208add0019a5 Mon Sep 17 00:00:00 2001 From: michael-hawker <24302614+michael-hawker@users.noreply.github.com> Date: Wed, 9 Mar 2022 10:55:02 -0800 Subject: [PATCH 6/6] Add missing project reference --- CommunityToolkit.Labs.Uwp/CommunityToolkit.Labs.Uwp.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CommunityToolkit.Labs.Uwp/CommunityToolkit.Labs.Uwp.csproj b/CommunityToolkit.Labs.Uwp/CommunityToolkit.Labs.Uwp.csproj index fc4e9fff7..344a4e4eb 100644 --- a/CommunityToolkit.Labs.Uwp/CommunityToolkit.Labs.Uwp.csproj +++ b/CommunityToolkit.Labs.Uwp/CommunityToolkit.Labs.Uwp.csproj @@ -25,6 +25,10 @@ {a14189c0-39a8-4fbe-bf86-a78a94654c48} CanvasLayout.Sample + + {fe19fff0-6ab6-4fc7-bfdf-b6499153dcd5} + CommunityToolkit.Labs.Uwp.UI.CanvasLayout + \ No newline at end of file