Skip to content

Commit

Permalink
Merge pull request #38 from CommunityToolkit/llama/canvaslayout-rebase
Browse files Browse the repository at this point in the history
Add Initial CanvasLayout component on top of initial project to aid testing/setup
  • Loading branch information
michael-hawker authored Mar 9, 2022
2 parents 63001ee + 8efb390 commit 238a976
Show file tree
Hide file tree
Showing 8 changed files with 239 additions and 2 deletions.
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">
<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; }
}
}

0 comments on commit 238a976

Please sign in to comment.