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

122 UniformGrid #139

Merged
merged 18 commits into from
Dec 10, 2021
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8" ?>
<pages:BasePage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:CommunityToolkit.Maui.Sample.Pages"
xmlns:mct="clr-namespace:CommunityToolkit.Maui.Views;assembly=CommunityToolkit.Maui"
x:Class="CommunityToolkit.Maui.Sample.Pages.Views.UniformGridPage">
<ContentView Padding="{StaticResource ContentPadding}">
<VerticalStackLayout>

<Label Text="UniformGrid with default settings"/>
<mct:UniformGrid>
<BoxView BackgroundColor="Red" WidthRequest="25" HeightRequest="25"/>
<BoxView BackgroundColor="Green" WidthRequest="25" HeightRequest="25"/>
<BoxView BackgroundColor="Yellow" WidthRequest="25" HeightRequest="25"/>
</mct:UniformGrid>

<Label Text="UniformGrid with MaxColumns=1 and MaxRows=1"/>
<mct:UniformGrid MaxColumns="1" MaxRows="1">
<BoxView BackgroundColor="Red" WidthRequest="25" HeightRequest="25"/>
<BoxView BackgroundColor="Green" WidthRequest="25" HeightRequest="25"/>
<BoxView BackgroundColor="Yellow" WidthRequest="25" HeightRequest="25"/>
</mct:UniformGrid>

<Label Text="UniformGrid with MaxRows=1"/>
<mct:UniformGrid MaxRows="1">
<BoxView BackgroundColor="Red" WidthRequest="25" HeightRequest="25"/>
<BoxView BackgroundColor="Green" WidthRequest="25" HeightRequest="25"/>
<BoxView BackgroundColor="Yellow" WidthRequest="25" HeightRequest="25"/>
</mct:UniformGrid>

<Label Text="UniformGrid with MaxColumns=1"/>
<mct:UniformGrid MaxColumns="1">
<BoxView BackgroundColor="Red" WidthRequest="25" HeightRequest="25"/>
<BoxView BackgroundColor="Green" WidthRequest="25" HeightRequest="25"/>
<BoxView BackgroundColor="Yellow" WidthRequest="25" HeightRequest="25"/>
</mct:UniformGrid>
</VerticalStackLayout>
</ContentView>
</pages:BasePage>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace CommunityToolkit.Maui.Sample.Pages.Views;

public partial class UniformGridPage : BasePage
{
public UniformGridPage()
{
InitializeComponent();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;
using CommunityToolkit.Maui.Sample.ViewModels.Views;

namespace CommunityToolkit.Maui.Sample.Pages.Views;

public class ViewsGalleryPage : BaseGalleryPage<ViewsGalleryViewModel>
{
public ViewsGalleryPage() : base("Views")
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using CommunityToolkit.Maui.Sample.Pages.Alerts;
using CommunityToolkit.Maui.Sample.Pages.Behaviors;
using CommunityToolkit.Maui.Sample.Pages.Converters;
using CommunityToolkit.Maui.Sample.Pages.Views;
using CommunityToolkit.Maui.Sample.Pages.Extensions;
using Microsoft.Maui.Graphics;

Expand All @@ -15,6 +16,12 @@ protected override IEnumerable<SectionModel> CreateItems() => new[]
new SectionModel(typeof(BehaviorsGalleryPage), "Behaviors", Color.FromArgb("#8E8CD8"),
"Behaviors lets you add functionality to user interface controls without having to subclass them. Behaviors are written in code and added to controls in XAML or code"),

new SectionModel(typeof(ConvertersGalleryPage), "Converters", Color.FromArgb("#EA005E"),
"Converters let you convert bindings of a certain type to a different value, based on custom logic"),

new SectionModel(typeof(ViewsGalleryPage), "Views", Color.FromArgb("#EA005E"),
"Views"),
};
new SectionModel(typeof(ConvertersGalleryPage), "Converters", Color.FromArgb("#EA005E"),
"Converters let you convert bindings of a certain type to a different value, based on custom logic"),

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Collections.Generic;
using CommunityToolkit.Maui.Sample.Models;
using CommunityToolkit.Maui.Sample.Pages.Views;
using CommunityToolkit.Maui.Views;

namespace CommunityToolkit.Maui.Sample.ViewModels.Views;

public class ViewsGalleryViewModel : BaseGalleryViewModel
{
protected override IEnumerable<SectionModel> CreateItems() => new[]
{
new SectionModel(
typeof(UniformGridPage),
nameof(UniformGrid),
"UniformGrid")
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<PackageReference Include="FluentAssertions" Version="6.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="Coverlet.Collector" Version="3.1.0" PrivateAssets="all" />
<PackageReference Include="NSubstitute" Version="4.2.2" />
<PackageReference Include="Xunit" Version="2.4.1" />
<PackageReference Include="Xunit.Runner.VisualStudio" Version="2.4.3" />
</ItemGroup>
Expand Down
82 changes: 82 additions & 0 deletions src/CommunityToolkit.Maui.UnitTests/Views/UniformGrid_Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CommunityToolkit.Maui.UnitTests.Mocks;
using CommunityToolkit.Maui.Views;
using Microsoft.Maui;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
using NSubstitute;
using Xunit;

namespace CommunityToolkit.Maui.UnitTests.Views;

public class UniformGridTests : BaseTest
{
UniformGrid uniformGrid;
IView uniformChild;
const double childCount = 3;
const double childWidth = 20;
const double childHeight = 10;
public UniformGridTests()
{
uniformChild = Substitute.For<IView>();
uniformGrid = new UniformGrid()
{
Children =
{
uniformChild,
uniformChild,
uniformChild
}
};
}

[Fact]
public void MeasureUniformGrid_NoWrap()
{
var expectedSize = new Size(childWidth * childCount, childHeight);
var childSize = new Size(childWidth, childHeight);
SetupChildrenSize(childSize);
var actualSize = uniformGrid.Measure(childWidth * childCount, childHeight * childCount);
Assert.Equal(expectedSize, actualSize);
}

[Fact]
public void MeasureUniformGrid_WrapOnNextRow()
{
var expectedSize = new Size(childWidth, childHeight * childCount);
var childSize = new Size(childWidth, childHeight);
SetupChildrenSize(childSize);
var actualSize = uniformGrid.Measure(childWidth, childHeight * childCount);
Assert.Equal(expectedSize, actualSize);
}

[Fact]
public void ArrangeChildrenUniformGrid()
{
var expectedSize = new Size(childWidth, childHeight);
SetupChildrenSize(expectedSize);
var actualSize = uniformGrid.ArrangeChildren(new Rectangle(0, 0, childWidth * childCount, childHeight * childCount));
Assert.Equal(expectedSize, actualSize);
}

[Fact]
public void MaxRowsArrangeChildrenUniformGrid()
{
var expectedSize = new Size(childWidth, childHeight);
SetupChildrenSize(expectedSize);
uniformGrid.MaxColumns = 1;
uniformGrid.MaxRows = 1;
var actualSize = uniformGrid.Measure(double.PositiveInfinity, double.PositiveInfinity);
Assert.Equal(expectedSize, actualSize);
}

void SetupChildrenSize(Size size)
{
uniformChild.Measure(double.PositiveInfinity, double.PositiveInfinity).ReturnsForAnyArgs(size);
uniformGrid.Measure(childWidth * childCount, childHeight * childCount);
}
}
117 changes: 117 additions & 0 deletions src/CommunityToolkit.Maui/Views/UniformGrid.shared.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using System;
using Microsoft.Maui;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Layouts;

namespace CommunityToolkit.Maui.Views;

/// <summary>
/// The UniformGrid is just like the Grid, with the possibility of multiple rows and columns, but with one important difference:
/// All rows and columns will have the same size.
/// Use this when you need the Grid behavior without the need to specify different sizes for the rows and columns.
/// </summary>
public class UniformGrid : Grid, ILayoutManager
{
double childWidth;

double childHeight;

/// <summary>
/// Assign this as a LayoutManager
/// </summary>
/// <returns><see cref="ILayoutManager"/></returns>
protected override ILayoutManager CreateLayoutManager()
{
return this;
}

/// <summary>
/// Backing BindableProperty for the <see cref="MaxRows"/> property.
/// </summary>
public static readonly BindableProperty MaxRowsProperty = BindableProperty.Create(nameof(MaxRows), typeof(int), typeof(UniformGrid), int.MaxValue);

/// <summary>
/// Backing BindableProperty for the <see cref="MaxColumns"/> property.
/// </summary>
public static readonly BindableProperty MaxColumnsProperty = BindableProperty.Create(nameof(MaxColumns), typeof(int), typeof(UniformGrid), int.MaxValue);

/// <summary>
/// Max rows
/// </summary>
public int MaxRows
{
get { return (int)GetValue(MaxRowsProperty); }
set { SetValue(MaxRowsProperty, value); }
}

/// <summary>
/// Max columns
/// </summary>
public int MaxColumns
{
get { return (int)GetValue(MaxColumnsProperty); }
set { SetValue(MaxColumnsProperty, value); }
}

/// <summary>
/// Arrange children
/// </summary>
/// <param name="rectangle">Grid rectangle</param>
/// <returns>Child size</returns>
public Size ArrangeChildren(Rectangle rectangle)
{
Measure(rectangle.Width, rectangle.Height, MeasureFlags.None);
var columns = GetColumnsCount(Children.Count, rectangle.Width, childWidth);
var rows = GetRowsCount(Children.Count, columns);
var boundsWidth = rectangle.Width / columns;
var boundsHeight = childHeight;
var bounds = new Rectangle(0, 0, boundsWidth, boundsHeight);
var count = 0;

for (var i = 0; i < rows; i++)
{
for (var j = 0; j < columns && count < Children.Count; j++)
{
var item = Children[count];
bounds.X = j * boundsWidth;
bounds.Y = i * boundsHeight;
item.Arrange(bounds);
count++;
}
}

return new Size(boundsWidth, boundsHeight);
}

/// <summary>
/// Measure grid size
/// </summary>
/// <param name="widthConstraint">Width constraint</param>
/// <param name="heightConstraint">Height constraint</param>
/// <returns>Grid size</returns>
public Size Measure(double widthConstraint, double heightConstraint)
{
foreach (var child in Children)
{
if (child.Visibility != Visibility.Visible)
continue;

var sizeRequest = child.Measure(double.PositiveInfinity, double.PositiveInfinity);
childHeight = sizeRequest.Height;
childWidth = sizeRequest.Width;
}

var columns = GetColumnsCount(Children.Count, widthConstraint, childWidth);
var rows = GetRowsCount(Children.Count, columns);
return new Size(columns * childWidth, rows * childHeight);
}

int GetColumnsCount(int visibleChildrenCount, double widthConstraint, double maxChildWidth)
=> Math.Min(double.IsPositiveInfinity(widthConstraint)
? visibleChildrenCount
: Math.Min((int)(widthConstraint / maxChildWidth), visibleChildrenCount), MaxColumns);

int GetRowsCount(int visibleChildrenCount, int columnsCount)
=> Math.Min((int)Math.Ceiling((double)visibleChildrenCount / columnsCount), MaxRows);
}