Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Initial CanvasLayout component on top of initial project to aid testing/setup #38

Merged
merged 6 commits into from
Mar 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CommunityToolkit.Labs.Uwp/CommunityToolkit.Labs.Uwp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
<Project>{a14189c0-39a8-4fbe-bf86-a78a94654c48}</Project>
<Name>CanvasLayout.Sample</Name>
</ProjectReference>
<ProjectReference Include="..\Labs\CanvasLayout\src\CommunityToolkit.Labs.Uwp.UI.CanvasLayout.csproj">
Arlodotexe marked this conversation as resolved.
Show resolved Hide resolved
<Project>{fe19fff0-6ab6-4fc7-bfdf-b6499153dcd5}</Project>
<Name>CommunityToolkit.Labs.Uwp.UI.CanvasLayout</Name>
</ProjectReference>
</ItemGroup>
<Import Project="../Common/Labs.Uwp.props" />
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@
<ItemGroup>
<None Remove="SamplePage.xaml" />
<None Remove="SamplePage2.xaml" />
<None Remove="SamplePage3.xaml" />
<None Remove="SamplePageOptions.xaml" />
</ItemGroup>
<ItemGroup>
<UpToDateCheckInput Remove="SamplePage.xaml" />
<UpToDateCheckInput Remove="SamplePage2.xaml" />
<UpToDateCheckInput Remove="SamplePage3.xaml" />
<UpToDateCheckInput Remove="SamplePageOptions.xaml" />
</ItemGroup>
<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<Page
x:Class="CanvasLayout.Sample.SampleThree.SamplePage3"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="using:CanvasLayout.Sample.SampleThree"
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">

<ScrollViewer
HorizontalScrollMode="Auto"
HorizontalScrollBarVisibility="Auto"
VerticalScrollMode="Auto"
VerticalScrollBarVisibility="Auto">
<muxc:ItemsRepeater ItemsSource="{x:Bind Items}">
<muxc:ItemsRepeater.Layout>
<labs:CanvasLayout/>
</muxc:ItemsRepeater.Layout>
<muxc:ItemsRepeater.ItemTemplate>
<DataTemplate x:DataType="local:CanvasItem">
<Border Width="{x:Bind Width}" Height="{x:Bind Height}"
CornerRadius="9999"
Background="Red"
BorderBrush="White" BorderThickness="2">
<TextBlock Text="{x:Bind Text}"
Foreground="White"
FontSize="24"
FontWeight="Bold"
HorizontalAlignment="Center"
win:TextLineBounds="Tight"
VerticalAlignment="Center"/>
</Border>
</DataTemplate>
</muxc:ItemsRepeater.ItemTemplate>
</muxc:ItemsRepeater>
</ScrollViewer>
</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// 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.ObjectModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using CommunityToolkit.Labs.Core.SourceGenerators;
using CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
using CommunityToolkit.Labs.Uwp;

#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.SampleThree
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
[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 ObservableCollection<CanvasItem> 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; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\CommunityToolkit.Labs.Uwp.UI.CanvasLayout.csproj" />
<ProjectReference Include="..\CanvasLayout.Sample\CanvasLayout.Sample.csproj" />
</ItemGroup>
</Project>
124 changes: 124 additions & 0 deletions Labs/CanvasLayout/src/CanvasLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,135 @@
// 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);

// 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;
}
}

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; }

/// <summary>
/// List of layout bounds for items starting with the
/// FirstRealizedIndex.
/// </summary>
public List<Rect> LayoutRects
{
get
{
if (_layoutRects == null)
{
_layoutRects = new List<Rect>();
}

return _layoutRects;
}
}

private List<Rect> _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; }
}
}