зеркало из https://github.com/DeGsoft/maui-linux.git
Implement LayoutAlignment for Core layouts (#505)
* Implement LayoutAlignment in Core * Fix parameter name
This commit is contained in:
Родитель
b7012dbdb2
Коммит
613dbc0072
|
@ -40,8 +40,8 @@ namespace Maui.Controls.Sample.Pages
|
|||
label.Margin = new Thickness(15, 10, 20, 15);
|
||||
|
||||
verticalStack.Add(label);
|
||||
verticalStack.Add(new Label { Text = "This should be BIG text!", FontSize = 24 });
|
||||
verticalStack.Add(new Label { Text = "This should be BOLD text!", FontAttributes = FontAttributes.Bold });
|
||||
verticalStack.Add(new Label { Text = "This should be BIG text!", FontSize = 24, HorizontalOptions = LayoutOptions.End });
|
||||
verticalStack.Add(new Label { Text = "This should be BOLD text!", FontAttributes = FontAttributes.Bold, HorizontalOptions = LayoutOptions.Center });
|
||||
verticalStack.Add(new Label { Text = "This should be a CUSTOM font!", FontFamily = "Dokdo" });
|
||||
verticalStack.Add(new Label { Text = "This should have padding", Padding = new Thickness(40), BackgroundColor = Color.LightBlue });
|
||||
verticalStack.Add(new Label { Text = loremIpsum });
|
||||
|
@ -77,7 +77,7 @@ namespace Maui.Controls.Sample.Pages
|
|||
|
||||
horizontalStack.Add(button);
|
||||
horizontalStack.Add(button2);
|
||||
horizontalStack.Add(new Label { Text = "And these buttons are in a HorizontalStackLayout" });
|
||||
horizontalStack.Add(new Label { Text = "And these buttons are in a HorizontalStackLayout", VerticalOptions = LayoutOptions.Center });
|
||||
|
||||
verticalStack.Add(horizontalStack);
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@ namespace Microsoft.Maui.Controls
|
|||
|
||||
Thickness Maui.IView.Margin => new Thickness();
|
||||
|
||||
public Primitives.LayoutAlignment HorizontalLayoutAlignment => Primitives.LayoutAlignment.Fill;
|
||||
|
||||
void Maui.ILayout.Add(IView child)
|
||||
{
|
||||
Content = (View)child;
|
||||
|
@ -61,5 +63,7 @@ namespace Microsoft.Maui.Controls
|
|||
layout.ResolveLayoutChanges();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,5 +123,7 @@ namespace Microsoft.Maui.Controls
|
|||
}
|
||||
|
||||
Maui.FlowDirection IFrameworkElement.FlowDirection => FlowDirection.ToPlatformFlowDirection();
|
||||
Primitives.LayoutAlignment IFrameworkElement.HorizontalLayoutAlignment => default;
|
||||
Primitives.LayoutAlignment IFrameworkElement.VerticalLayoutAlignment => default;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,5 +35,22 @@ namespace Microsoft.Maui.Controls
|
|||
get { return (_flags & (int)LayoutExpandFlag.Expand) != 0; }
|
||||
set { _flags = (_flags & 3) | (value ? (int)LayoutExpandFlag.Expand : 0); }
|
||||
}
|
||||
|
||||
internal Primitives.LayoutAlignment ToCore()
|
||||
{
|
||||
switch (Alignment)
|
||||
{
|
||||
case LayoutAlignment.Start:
|
||||
return Primitives.LayoutAlignment.Start;
|
||||
case LayoutAlignment.Center:
|
||||
return Primitives.LayoutAlignment.Center;
|
||||
case LayoutAlignment.End:
|
||||
return Primitives.LayoutAlignment.End;
|
||||
case LayoutAlignment.Fill:
|
||||
return Primitives.LayoutAlignment.Fill;
|
||||
}
|
||||
|
||||
return Primitives.LayoutAlignment.Start;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -195,6 +195,9 @@ namespace Microsoft.Maui.Controls
|
|||
double IFrameworkElement.Width { get => WidthRequest; }
|
||||
double IFrameworkElement.Height { get => HeightRequest; }
|
||||
|
||||
Primitives.LayoutAlignment IFrameworkElement.HorizontalLayoutAlignment => HorizontalOptions.ToCore();
|
||||
Primitives.LayoutAlignment IFrameworkElement.VerticalLayoutAlignment => VerticalOptions.ToCore();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
namespace Microsoft.Maui
|
||||
using Microsoft.Maui.Primitives;
|
||||
|
||||
namespace Microsoft.Maui
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a framework-level set of properties, events, and methods for .NET MAUI elements.
|
||||
|
@ -88,5 +90,15 @@
|
|||
/// Direction in which the UI elements on the page are scanned by the eye
|
||||
/// </summary>
|
||||
FlowDirection FlowDirection { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines the horizontal aspect of this element's arrangement in a container
|
||||
/// </summary>
|
||||
LayoutAlignment HorizontalLayoutAlignment { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines the vertical aspect of this element's arrangement in a container
|
||||
/// </summary>
|
||||
LayoutAlignment VerticalLayoutAlignment { get; }
|
||||
}
|
||||
}
|
|
@ -59,8 +59,8 @@ namespace Microsoft.Maui.Handlers
|
|||
var deviceWidthConstraint = Context.ToPixels(widthConstraint);
|
||||
var deviceHeightConstraint = Context.ToPixels(heightConstraint);
|
||||
|
||||
var widthSpec = MeasureSpecMode.Exactly.MakeMeasureSpec((int)deviceWidthConstraint);
|
||||
var heightSpec = MeasureSpecMode.Exactly.MakeMeasureSpec((int)deviceHeightConstraint);
|
||||
var widthSpec = MeasureSpecMode.AtMost.MakeMeasureSpec((int)deviceWidthConstraint);
|
||||
var heightSpec = MeasureSpecMode.AtMost.MakeMeasureSpec((int)deviceHeightConstraint);
|
||||
|
||||
TypedNativeView.Measure(widthSpec, heightSpec);
|
||||
|
||||
|
|
|
@ -20,17 +20,17 @@ namespace Microsoft.Maui.Layouts
|
|||
return new Size(finalWidth, measure.Height);
|
||||
}
|
||||
|
||||
public override void ArrangeChildren(Rectangle childBounds)
|
||||
public override void ArrangeChildren(Rectangle bounds)
|
||||
{
|
||||
if (Stack.FlowDirection == FlowDirection.LeftToRight)
|
||||
{
|
||||
ArrangeLeftToRight(Stack.Spacing, Stack.Children);
|
||||
ArrangeLeftToRight(bounds.Height, Stack.Spacing, Stack.Children);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We _could_ simply reverse the list of child views when arranging from right to left,
|
||||
// but this way we avoid extra list and enumerator allocations
|
||||
ArrangeRightToLeft(Stack.Spacing, Stack.Children);
|
||||
ArrangeRightToLeft(bounds.Height, Stack.Spacing, Stack.Children);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,31 +53,31 @@ namespace Microsoft.Maui.Layouts
|
|||
return new Size(totalRequestedWidth, requestedHeight);
|
||||
}
|
||||
|
||||
static void ArrangeLeftToRight(int spacing, IReadOnlyList<IView> views)
|
||||
static void ArrangeLeftToRight(double height, int spacing, IReadOnlyList<IView> views)
|
||||
{
|
||||
double xPosition = 0;
|
||||
|
||||
for (int n = 0; n < views.Count; n++)
|
||||
{
|
||||
var child = views[n];
|
||||
xPosition += ArrangeChild(child, spacing, xPosition);
|
||||
xPosition += ArrangeChild(child, height, spacing, xPosition);
|
||||
}
|
||||
}
|
||||
|
||||
static void ArrangeRightToLeft(int spacing, IReadOnlyList<IView> views)
|
||||
static void ArrangeRightToLeft(double height, int spacing, IReadOnlyList<IView> views)
|
||||
{
|
||||
double xPostition = 0;
|
||||
|
||||
for (int n = views.Count - 1; n >= 0; n--)
|
||||
{
|
||||
var child = views[n];
|
||||
xPostition += ArrangeChild(child, spacing, xPostition);
|
||||
xPostition += ArrangeChild(child, height, spacing, xPostition);
|
||||
}
|
||||
}
|
||||
|
||||
static double ArrangeChild(IView child, int spacing, double x)
|
||||
static double ArrangeChild(IView child, double height, int spacing, double x)
|
||||
{
|
||||
var destination = new Rectangle(x, 0, child.DesiredSize.Width, child.DesiredSize.Height);
|
||||
var destination = new Rectangle(x, 0, child.DesiredSize.Width, height);
|
||||
child.Arrange(destination);
|
||||
return destination.Width + spacing;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
using System;
|
||||
using Microsoft.Maui;
|
||||
using Microsoft.Maui.Primitives;
|
||||
|
||||
namespace Microsoft.Maui.Layouts
|
||||
{
|
||||
|
@ -36,16 +36,18 @@ namespace Microsoft.Maui.Layouts
|
|||
{
|
||||
Thickness margin = frameworkElement.GetMargin();
|
||||
|
||||
// If the margins are too big for the bounds, then simply collapse them to zero
|
||||
var frameWidth = Math.Max(0, bounds.Width - margin.HorizontalThickness);
|
||||
var frameHeight = Math.Max(0, bounds.Height - margin.VerticalThickness);
|
||||
var frameWidth = frameworkElement.HorizontalLayoutAlignment == LayoutAlignment.Fill
|
||||
? Math.Max(0, bounds.Width - margin.HorizontalThickness)
|
||||
: frameworkElement.DesiredSize.Width;
|
||||
|
||||
var xMarginAdjustment = frameworkElement.FlowDirection == FlowDirection.LeftToRight
|
||||
? margin.Left
|
||||
: margin.Right;
|
||||
var frameHeight = frameworkElement.VerticalLayoutAlignment == LayoutAlignment.Fill
|
||||
? Math.Max(0, bounds.Height - margin.VerticalThickness)
|
||||
: frameworkElement.DesiredSize.Height;
|
||||
|
||||
return new Rectangle(bounds.X + xMarginAdjustment, bounds.Y + margin.Top,
|
||||
frameWidth, frameHeight);
|
||||
var frameX = AlignHorizontal(frameworkElement, bounds, margin);
|
||||
var frameY = AlignVertical(frameworkElement, bounds, margin);
|
||||
|
||||
return new Rectangle(frameX, frameY, frameWidth, frameHeight);
|
||||
}
|
||||
|
||||
static Thickness GetMargin(this IFrameworkElement frameworkElement)
|
||||
|
@ -53,7 +55,94 @@ namespace Microsoft.Maui.Layouts
|
|||
if (frameworkElement is IView view)
|
||||
return view.Margin;
|
||||
|
||||
return new Thickness();
|
||||
return Thickness.Zero;
|
||||
}
|
||||
|
||||
static double AlignHorizontal(IFrameworkElement frameworkElement, Rectangle bounds, Thickness margin)
|
||||
{
|
||||
var alignment = frameworkElement.HorizontalLayoutAlignment;
|
||||
var desiredWidth = frameworkElement.DesiredSize.Width;
|
||||
var startX = bounds.X;
|
||||
|
||||
if (frameworkElement.FlowDirection == FlowDirection.LeftToRight)
|
||||
{
|
||||
return AlignHorizontal(startX, margin.Left, margin.Right, bounds.Width, desiredWidth, alignment);
|
||||
}
|
||||
|
||||
// If the flowdirection is RTL, then we can use the same logic to determine the X position of the Frame;
|
||||
// we just have to flip a few parameters. First we flip the alignment if it's start or end:
|
||||
|
||||
if (alignment == LayoutAlignment.End)
|
||||
{
|
||||
alignment = LayoutAlignment.Start;
|
||||
}
|
||||
else if (alignment == LayoutAlignment.Start)
|
||||
{
|
||||
alignment = LayoutAlignment.End;
|
||||
}
|
||||
|
||||
// And then we swap the left and right margins:
|
||||
return AlignHorizontal(startX, margin.Right, margin.Left, bounds.Width, desiredWidth, alignment);
|
||||
}
|
||||
|
||||
static double AlignHorizontal(double startX, double startMargin, double endMargin, double boundsWidth,
|
||||
double desiredWidth, LayoutAlignment horizontalLayoutAlignment)
|
||||
{
|
||||
double frameX = 0;
|
||||
|
||||
switch (horizontalLayoutAlignment)
|
||||
{
|
||||
case LayoutAlignment.Fill:
|
||||
case LayoutAlignment.Start:
|
||||
frameX = startX + startMargin;
|
||||
break;
|
||||
|
||||
case LayoutAlignment.Center:
|
||||
|
||||
frameX = (boundsWidth - desiredWidth) / 2;
|
||||
var marginOffset = (startMargin - endMargin) / 2;
|
||||
frameX += marginOffset;
|
||||
|
||||
break;
|
||||
case LayoutAlignment.End:
|
||||
|
||||
frameX = boundsWidth - endMargin - desiredWidth;
|
||||
break;
|
||||
}
|
||||
|
||||
return frameX;
|
||||
}
|
||||
|
||||
static double AlignVertical(IFrameworkElement frameworkElement, Rectangle bounds, Thickness margin)
|
||||
{
|
||||
double frameY = 0;
|
||||
|
||||
switch (frameworkElement.VerticalLayoutAlignment)
|
||||
{
|
||||
case LayoutAlignment.Fill:
|
||||
|
||||
frameY = bounds.Y + margin.Top;
|
||||
break;
|
||||
|
||||
case LayoutAlignment.Start:
|
||||
|
||||
frameY = bounds.Y + margin.Top;
|
||||
break;
|
||||
|
||||
case LayoutAlignment.Center:
|
||||
|
||||
frameY = (bounds.Height - frameworkElement.DesiredSize.Height) / 2;
|
||||
var offset = (margin.Top - margin.Bottom) / 2;
|
||||
frameY += offset;
|
||||
break;
|
||||
|
||||
case LayoutAlignment.End:
|
||||
|
||||
frameY = bounds.Height - margin.Bottom - frameworkElement.DesiredSize.Height;
|
||||
break;
|
||||
}
|
||||
|
||||
return frameY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Maui;
|
||||
|
||||
namespace Microsoft.Maui.Layouts
|
||||
{
|
||||
|
@ -21,7 +20,7 @@ namespace Microsoft.Maui.Layouts
|
|||
return new Size(measure.Width, finalHeight);
|
||||
}
|
||||
|
||||
public override void ArrangeChildren(Rectangle childBounds) => Arrange(Stack.Spacing, Stack.Children);
|
||||
public override void ArrangeChildren(Rectangle bounds) => Arrange(bounds.Width, Stack.Spacing, Stack.Children);
|
||||
|
||||
static Size Measure(double widthConstraint, int spacing, IReadOnlyList<IView> views)
|
||||
{
|
||||
|
@ -41,13 +40,13 @@ namespace Microsoft.Maui.Layouts
|
|||
return new Size(requestedWidth, totalRequestedHeight);
|
||||
}
|
||||
|
||||
static void Arrange(int spacing, IEnumerable<IView> views)
|
||||
static void Arrange(double width, int spacing, IEnumerable<IView> views)
|
||||
{
|
||||
double stackHeight = 0;
|
||||
|
||||
foreach (var child in views)
|
||||
{
|
||||
var destination = new Rectangle(0, stackHeight, child.DesiredSize.Width, child.DesiredSize.Height);
|
||||
var destination = new Rectangle(0, stackHeight, width, child.DesiredSize.Height);
|
||||
child.Arrange(destination);
|
||||
stackHeight += destination.Height + spacing;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
namespace Microsoft.Maui.Primitives
|
||||
{
|
||||
// We don't use Microsoft.Maui.Controls.LayoutAlignment directly because it has a Flags attribute, which we do not want
|
||||
|
||||
/// <summary>
|
||||
/// Determines the position and size of an IFrameworkElement when arranged in a parent element
|
||||
/// </summary>
|
||||
public enum LayoutAlignment
|
||||
{
|
||||
/// <summary>
|
||||
/// Fill the available space
|
||||
/// </summary>
|
||||
Fill,
|
||||
|
||||
/// <summary>
|
||||
/// Align with the leading edge of the available space, as determined by FlowDirection
|
||||
/// </summary>
|
||||
Start,
|
||||
|
||||
/// <summary>
|
||||
/// Center in the available space
|
||||
/// </summary>
|
||||
Center,
|
||||
|
||||
/// <summary>
|
||||
/// Align with the trailing edge of the available space, as determined by FlowDirection
|
||||
/// </summary>
|
||||
End
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Maui.Primitives;
|
||||
|
||||
namespace Microsoft.Maui.DeviceTests.Stubs
|
||||
{
|
||||
|
@ -32,6 +33,10 @@ namespace Microsoft.Maui.DeviceTests.Stubs
|
|||
|
||||
public FlowDirection FlowDirection { get; set; }
|
||||
|
||||
public LayoutAlignment HorizontalLayoutAlignment { get; set; }
|
||||
|
||||
public LayoutAlignment VerticalLayoutAlignment { get; set; }
|
||||
|
||||
public void Arrange(Rectangle bounds)
|
||||
{
|
||||
Frame = bounds;
|
||||
|
|
|
@ -3,6 +3,7 @@ using Microsoft.Maui;
|
|||
using Microsoft.Maui.Layouts;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using Microsoft.Maui.Primitives;
|
||||
|
||||
namespace Microsoft.Maui.UnitTests.Layouts
|
||||
{
|
||||
|
@ -82,33 +83,6 @@ namespace Microsoft.Maui.UnitTests.Layouts
|
|||
Assert.Equal(expectedWidth, measurement.Width);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ViewsArrangedWithDesiredHeights()
|
||||
{
|
||||
var stack = CreateTestLayout();
|
||||
var manager = new HorizontalStackLayoutManager(stack);
|
||||
|
||||
var view1 = LayoutTestHelpers.CreateTestView(new Size(100, 200));
|
||||
var view2 = LayoutTestHelpers.CreateTestView(new Size(100, 150));
|
||||
|
||||
var children = new List<IView>() { view1, view2 }.AsReadOnly();
|
||||
stack.Children.Returns(children);
|
||||
|
||||
var measurement = manager.Measure(double.PositiveInfinity, double.PositiveInfinity);
|
||||
manager.ArrangeChildren(new Rectangle(Point.Zero, measurement));
|
||||
|
||||
// The tallest IView is 200, so the stack should be that tall
|
||||
Assert.Equal(200, measurement.Height);
|
||||
|
||||
// We expect the first IView to be at 0,0 with a width of 100 and a height of 200
|
||||
var expectedRectangle1 = new Rectangle(0, 0, 100, 200);
|
||||
view1.Received().Arrange(Arg.Is(expectedRectangle1));
|
||||
|
||||
// We expect the second IView to be at 100, 0 with a width of 100 and a height of 150
|
||||
var expectedRectangle2 = new Rectangle(100, 0, 100, 150);
|
||||
view2.Received().Arrange(Arg.Is(expectedRectangle2));
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "First View in LTR Horizontal Stack is on the left")]
|
||||
public void LtrShouldHaveFirstItemOnTheLeft()
|
||||
{
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
using Microsoft.Maui;
|
||||
using Microsoft.Maui.Layouts;
|
||||
using Microsoft.Maui.Layouts;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using Microsoft.Maui.Primitives;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Maui.UnitTests.Layouts
|
||||
{
|
||||
|
@ -27,12 +28,18 @@ namespace Microsoft.Maui.UnitTests.Layouts
|
|||
Assert.Equal(60, frame.Height);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FrameSizeGoesToZeroWhenMarginsExceedBounds()
|
||||
[Theory]
|
||||
[InlineData(LayoutAlignment.Fill)]
|
||||
[InlineData(LayoutAlignment.Start)]
|
||||
[InlineData(LayoutAlignment.Center)]
|
||||
[InlineData(LayoutAlignment.End)]
|
||||
public void FrameSizeGoesToZeroWhenMarginsExceedBounds(LayoutAlignment layoutAlignment)
|
||||
{
|
||||
var element = Substitute.For<IView>();
|
||||
var margin = new Thickness(200);
|
||||
element.Margin.Returns(margin);
|
||||
element.HorizontalLayoutAlignment.Returns(layoutAlignment);
|
||||
element.VerticalLayoutAlignment.Returns(layoutAlignment);
|
||||
|
||||
var bounds = new Rectangle(0, 0, 100, 100);
|
||||
var frame = element.ComputeFrame(bounds);
|
||||
|
@ -69,27 +76,118 @@ namespace Microsoft.Maui.UnitTests.Layouts
|
|||
Assert.Equal(90, desiredSize.Height);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MarginsAccountForFlowDirectionRTL()
|
||||
public static IEnumerable<object[]> AlignmentTestData()
|
||||
{
|
||||
var margin = Thickness.Zero;
|
||||
|
||||
// No margin
|
||||
yield return new object[] { LayoutAlignment.Start, margin, 0, 100};
|
||||
yield return new object[] { LayoutAlignment.Center, margin, 100, 100 };
|
||||
yield return new object[] { LayoutAlignment.End, margin, 200, 100};
|
||||
yield return new object[] { LayoutAlignment.Fill, margin, 0, 300};
|
||||
|
||||
// Even margin
|
||||
margin = new Thickness(10);
|
||||
yield return new object[] { LayoutAlignment.Start, margin, 10, 100};
|
||||
yield return new object[] { LayoutAlignment.Center, margin, 100, 100};
|
||||
yield return new object[] { LayoutAlignment.End, margin, 190, 100};
|
||||
yield return new object[] { LayoutAlignment.Fill, margin, 10, 280 };
|
||||
|
||||
// Lopsided margin
|
||||
margin = new Thickness(5, 5, 10, 10);
|
||||
yield return new object[] { LayoutAlignment.Start, margin, 5, 100};
|
||||
yield return new object[] { LayoutAlignment.Center, margin, 97.5, 100 };
|
||||
yield return new object[] { LayoutAlignment.End, margin, 190, 100 };
|
||||
yield return new object[] { LayoutAlignment.Fill, margin, 5, 285 };
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(AlignmentTestData))]
|
||||
public void FrameAccountsForHorizontalLayoutAlignment(LayoutAlignment layoutAlignment, Thickness margin,
|
||||
double expectedX, double expectedWidth)
|
||||
{
|
||||
var widthConstraint = 300;
|
||||
var heightConstraint = 50;
|
||||
var viewSize = new Size(100, 50);
|
||||
|
||||
var element = Substitute.For<IView>();
|
||||
element.FlowDirection.Returns(FlowDirection.RightToLeft);
|
||||
var margin = new Thickness(10, 0, 15, 0);
|
||||
|
||||
element.Margin.Returns(margin);
|
||||
element.DesiredSize.Returns(viewSize);
|
||||
element.HorizontalLayoutAlignment.Returns(layoutAlignment);
|
||||
|
||||
var bounds = new Rectangle(0, 0, 100, 100);
|
||||
var frame = element.ComputeFrame(bounds);
|
||||
var frame = element.ComputeFrame(new Rectangle(0, 0, widthConstraint, heightConstraint));
|
||||
|
||||
// The top and bottom margins are zero, so we expect the Frame to have the full height of 100
|
||||
Assert.Equal(0, frame.Top);
|
||||
Assert.Equal(100, frame.Height);
|
||||
Assert.Equal(expectedX, frame.Left);
|
||||
Assert.Equal(expectedWidth, frame.Width);
|
||||
}
|
||||
|
||||
// The left and right margins together are 25, so we expect the Frame to have a width of 75
|
||||
Assert.Equal(75, frame.Width);
|
||||
[Theory]
|
||||
[MemberData(nameof(AlignmentTestData))]
|
||||
public void FrameAccountsForVerticalLayoutAlignment(LayoutAlignment layoutAlignment, Thickness margin,
|
||||
double expectedY, double expectedHeight)
|
||||
{
|
||||
var widthConstraint = 50;
|
||||
var heightConstraint = 300;
|
||||
var viewSize = new Size(50, 100);
|
||||
|
||||
// The left and right margins should be swapped (because of RTL) so we expect the Frame location
|
||||
// to have a Left value of 15 (the "right side" margin)
|
||||
Assert.Equal(15, frame.Left);
|
||||
var element = Substitute.For<IView>();
|
||||
|
||||
element.Margin.Returns(margin);
|
||||
element.DesiredSize.Returns(viewSize);
|
||||
element.VerticalLayoutAlignment.Returns(layoutAlignment);
|
||||
|
||||
var frame = element.ComputeFrame(new Rectangle(0, 0, widthConstraint, heightConstraint));
|
||||
|
||||
Assert.Equal(expectedY, frame.Top);
|
||||
Assert.Equal(expectedHeight, frame.Height);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> AlignmentTestDataRtl()
|
||||
{
|
||||
var margin = Thickness.Zero;
|
||||
|
||||
// No margin
|
||||
yield return new object[] { LayoutAlignment.Start, margin, 200, 100 };
|
||||
yield return new object[] { LayoutAlignment.Center, margin, 100, 100 };
|
||||
yield return new object[] { LayoutAlignment.End, margin, 0, 100 };
|
||||
yield return new object[] { LayoutAlignment.Fill, margin, 0, 300 };
|
||||
|
||||
// Even margin
|
||||
margin = new Thickness(10);
|
||||
yield return new object[] { LayoutAlignment.Start, margin, 190, 100 };
|
||||
yield return new object[] { LayoutAlignment.Center, margin, 100, 100 };
|
||||
yield return new object[] { LayoutAlignment.End, margin, 10, 100 };
|
||||
yield return new object[] { LayoutAlignment.Fill, margin, 10, 280 };
|
||||
|
||||
// Lopsided margin
|
||||
margin = new Thickness(5, 5, 10, 10);
|
||||
yield return new object[] { LayoutAlignment.Start, margin, 195, 100 };
|
||||
yield return new object[] { LayoutAlignment.Center, margin, 102.5, 100 };
|
||||
yield return new object[] { LayoutAlignment.End, margin, 10, 100 };
|
||||
yield return new object[] { LayoutAlignment.Fill, margin, 10, 285 };
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(AlignmentTestDataRtl))]
|
||||
public void FrameAccountsForHorizontalLayoutAlignmentRtl(LayoutAlignment layoutAlignment, Thickness margin,
|
||||
double expectedX, double expectedWidth)
|
||||
{
|
||||
var widthConstraint = 300;
|
||||
var heightConstraint = 50;
|
||||
var viewSize = new Size(100, 50);
|
||||
|
||||
var element = Substitute.For<IView>();
|
||||
|
||||
element.Margin.Returns(margin);
|
||||
element.DesiredSize.Returns(viewSize);
|
||||
element.FlowDirection.Returns(FlowDirection.RightToLeft);
|
||||
element.HorizontalLayoutAlignment.Returns(layoutAlignment);
|
||||
|
||||
var frame = element.ComputeFrame(new Rectangle(0, 0, widthConstraint, heightConstraint));
|
||||
|
||||
Assert.Equal(expectedX, frame.Left);
|
||||
Assert.Equal(expectedWidth, frame.Width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,9 +29,11 @@ namespace Microsoft.Maui.UnitTests.Layouts
|
|||
protected IView CreateTestView(Size viewSize)
|
||||
{
|
||||
var view = CreateTestView();
|
||||
var handler = Substitute.For<IViewHandler>();
|
||||
|
||||
view.Measure(Arg.Any<double>(), Arg.Any<double>()).Returns(viewSize);
|
||||
view.DesiredSize.Returns(viewSize);
|
||||
view.Handler.Returns(handler);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using Microsoft.Maui;
|
||||
using Microsoft.Maui.Layouts;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
@ -81,32 +80,5 @@ namespace Microsoft.Maui.UnitTests.Layouts
|
|||
var measurement = manager.Measure(100, double.PositiveInfinity);
|
||||
Assert.Equal(expectedHeight, measurement.Height);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ViewsArrangedWithDesiredWidths()
|
||||
{
|
||||
var stack = CreateTestLayout();
|
||||
var manager = new VerticalStackLayoutManager(stack);
|
||||
|
||||
var view1 = LayoutTestHelpers.CreateTestView(new Size(200, 100));
|
||||
var view2 = LayoutTestHelpers.CreateTestView(new Size(150, 100));
|
||||
|
||||
var children = new List<IView>() { view1, view2 }.AsReadOnly();
|
||||
stack.Children.Returns(children);
|
||||
|
||||
var measurement = manager.Measure(double.PositiveInfinity, double.PositiveInfinity);
|
||||
manager.ArrangeChildren(new Rectangle(Point.Zero, measurement));
|
||||
|
||||
// The widest IView is 200, so the stack should be that wide
|
||||
Assert.Equal(200, measurement.Width);
|
||||
|
||||
// We expect the first IView to be at 0,0 with a width of 200 and a height of 100
|
||||
var expectedRectangle1 = new Rectangle(0, 0, 200, 100);
|
||||
view1.Received().Arrange(Arg.Is(expectedRectangle1));
|
||||
|
||||
// We expect the second IView to be at 0, 100 with a width of 150 and a height of 100
|
||||
var expectedRectangle2 = new Rectangle(0, 100, 150, 100);
|
||||
view2.Received().Arrange(Arg.Is(expectedRectangle2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
using System;
|
||||
using Microsoft.Maui;
|
||||
using Microsoft.Maui.Primitives;
|
||||
|
||||
namespace Microsoft.Maui.Tests
|
||||
{
|
||||
|
@ -31,6 +31,10 @@ namespace Microsoft.Maui.Tests
|
|||
|
||||
public FlowDirection FlowDirection => throw new NotImplementedException();
|
||||
|
||||
|
||||
public LayoutAlignment HorizontalLayoutAlignment { get; set; }
|
||||
public LayoutAlignment VerticalLayoutAlignment { get; set; }
|
||||
|
||||
public void Arrange(Rectangle bounds)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
|
Загрузка…
Ссылка в новой задаче