Browse Source

Fix layout in ScrollContentPresenter.

- Share common layout logic between `Border`, `ContentPresenter` and `ScrollContentPresenter`
- Added a bunch of tests for things not previously convered
- Fix `ScrollContentPresenter` child layout
pull/1338/head
Steven Kirk 9 years ago
parent
commit
46bcbacc53
  1. 38
      src/Avalonia.Controls/Border.cs
  2. 127
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  3. 66
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  4. 194
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Layout.cs
  5. 4
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs
  6. 129
      tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs
  7. 110
      tests/Avalonia.Layout.UnitTests/LayoutableTests.cs

38
src/Avalonia.Controls/Border.cs

@ -108,18 +108,7 @@ namespace Avalonia.Controls
/// <returns>The desired size of the control.</returns> /// <returns>The desired size of the control.</returns>
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
var child = Child; return MeasureOverrideImpl(availableSize, Child, Padding, BorderThickness);
var padding = Padding + new Thickness(BorderThickness);
if (child != null)
{
child.Measure(availableSize.Deflate(padding));
return child.DesiredSize.Inflate(padding);
}
else
{
return new Size(padding.Left + padding.Right, padding.Bottom + padding.Top);
}
} }
/// <summary> /// <summary>
@ -129,15 +118,32 @@ namespace Avalonia.Controls
/// <returns>The space taken.</returns> /// <returns>The space taken.</returns>
protected override Size ArrangeOverride(Size finalSize) protected override Size ArrangeOverride(Size finalSize)
{ {
var child = Child; if (Child != null)
if (child != null)
{ {
var padding = Padding + new Thickness(BorderThickness); var padding = Padding + new Thickness(BorderThickness);
child.Arrange(new Rect(finalSize).Deflate(padding)); Child.Arrange(new Rect(finalSize).Deflate(padding));
} }
return finalSize; return finalSize;
} }
internal static Size MeasureOverrideImpl(
Size availableSize,
IControl child,
Thickness padding,
double borderThickness)
{
padding += new Thickness(borderThickness);
if (child != null)
{
child.Measure(availableSize.Deflate(padding));
return child.DesiredSize.Inflate(padding);
}
else
{
return new Size(padding.Left + padding.Right, padding.Bottom + padding.Top);
}
}
} }
} }

127
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -2,14 +2,12 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Reactive.Linq;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Presenters namespace Avalonia.Controls.Presenters
{ {
@ -340,94 +338,121 @@ namespace Avalonia.Controls.Presenters
/// <inheritdoc/> /// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
var child = Child; return Border.MeasureOverrideImpl(availableSize, Child, Padding, BorderThickness);
var padding = Padding + new Thickness(BorderThickness); }
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
return ArrangeOverrideImpl(finalSize, new Vector());
}
/// <summary>
/// Called when the <see cref="Content"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
private void ContentChanged(AvaloniaPropertyChangedEventArgs e)
{
_createdChild = false;
if (child != null) if (((ILogical)this).IsAttachedToLogicalTree)
{ {
child.Measure(availableSize.Deflate(padding)); UpdateChild();
return child.DesiredSize.Inflate(padding);
} }
else else if (Child != null)
{ {
return new Size(padding.Left + padding.Right, padding.Bottom + padding.Top); VisualChildren.Remove(Child);
LogicalChildren.Remove(Child);
Child = null;
_dataTemplate = null;
} }
InvalidateMeasure();
} }
/// <inheritdoc/> internal Size ArrangeOverrideImpl(Size finalSize, Vector offset)
protected override Size ArrangeOverride(Size finalSize)
{ {
var child = Child; if (Child != null)
if (child != null)
{ {
var padding = Padding + new Thickness(BorderThickness); var padding = Padding;
var sizeMinusPadding = finalSize.Deflate(padding); var borderThickness = BorderThickness;
var size = sizeMinusPadding; var horizontalContentAlignment = HorizontalContentAlignment;
var horizontalAlignment = HorizontalContentAlignment; var verticalContentAlignment = VerticalContentAlignment;
var verticalAlignment = VerticalContentAlignment; var useLayoutRounding = UseLayoutRounding;
var originX = padding.Left; var availableSizeMinusMargins = new Size(
var originY = padding.Top; Math.Max(0, finalSize.Width - padding.Left - padding.Right - borderThickness),
Math.Max(0, finalSize.Height - padding.Top - padding.Bottom - borderThickness));
if (horizontalAlignment != HorizontalAlignment.Stretch) var size = availableSizeMinusMargins;
var scale = GetLayoutScale();
var originX = offset.X + padding.Left + borderThickness;
var originY = offset.Y + padding.Top + borderThickness;
if (horizontalContentAlignment != HorizontalAlignment.Stretch)
{ {
size = size.WithWidth(child.DesiredSize.Width); size = size.WithWidth(Math.Min(size.Width, DesiredSize.Width - padding.Left - padding.Right));
} }
if (verticalAlignment != VerticalAlignment.Stretch) if (verticalContentAlignment != VerticalAlignment.Stretch)
{ {
size = size.WithHeight(child.DesiredSize.Height); size = size.WithHeight(Math.Min(size.Height, DesiredSize.Height - padding.Top - padding.Bottom));
} }
switch (horizontalAlignment) size = LayoutHelper.ApplyLayoutConstraints(Child, size);
if (useLayoutRounding)
{
size = new Size(
Math.Ceiling(size.Width * scale) / scale,
Math.Ceiling(size.Height * scale) / scale);
availableSizeMinusMargins = new Size(
Math.Ceiling(availableSizeMinusMargins.Width * scale) / scale,
Math.Ceiling(availableSizeMinusMargins.Height * scale) / scale);
}
switch (horizontalContentAlignment)
{ {
case HorizontalAlignment.Stretch:
case HorizontalAlignment.Center: case HorizontalAlignment.Center:
originX += (sizeMinusPadding.Width - size.Width) / 2; case HorizontalAlignment.Stretch:
originX += (availableSizeMinusMargins.Width - size.Width) / 2;
break; break;
case HorizontalAlignment.Right: case HorizontalAlignment.Right:
originX = size.Width - child.DesiredSize.Width; originX += availableSizeMinusMargins.Width - size.Width;
break; break;
} }
switch (verticalAlignment) switch (verticalContentAlignment)
{ {
case VerticalAlignment.Stretch:
case VerticalAlignment.Center: case VerticalAlignment.Center:
originY += (sizeMinusPadding.Height - size.Height) / 2; case VerticalAlignment.Stretch:
originY += (availableSizeMinusMargins.Height - size.Height) / 2;
break; break;
case VerticalAlignment.Bottom: case VerticalAlignment.Bottom:
originY = size.Height - child.DesiredSize.Height; originY += availableSizeMinusMargins.Height - size.Height;
break; break;
} }
child.Arrange(new Rect(originX, originY, size.Width, size.Height)); if (useLayoutRounding)
{
originX = Math.Floor(originX * scale) / scale;
originY = Math.Floor(originY * scale) / scale;
}
Child.Arrange(new Rect(originX, originY, size.Width, size.Height));
} }
return finalSize; return finalSize;
} }
/// <summary> private double GetLayoutScale()
/// Called when the <see cref="Content"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
private void ContentChanged(AvaloniaPropertyChangedEventArgs e)
{ {
_createdChild = false; var result = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0;
if (((ILogical)this).IsAttachedToLogicalTree) if (result == 0 || double.IsNaN(result) || double.IsInfinity(result))
{ {
UpdateChild(); throw new Exception($"Invalid LayoutScaling returned from {VisualRoot.GetType()}");
}
else if (Child != null)
{
VisualChildren.Remove(Child);
LogicalChildren.Remove(Child);
Child = null;
_dataTemplate = null;
} }
InvalidateMeasure(); return result;
} }
private void TemplatedParentChanged(AvaloniaPropertyChangedEventArgs e) private void TemplatedParentChanged(AvaloniaPropertyChangedEventArgs e)

66
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@ -48,7 +48,6 @@ namespace Avalonia.Controls.Presenters
ScrollViewer.CanScrollHorizontallyProperty.AddOwner<ScrollContentPresenter>(); ScrollViewer.CanScrollHorizontallyProperty.AddOwner<ScrollContentPresenter>();
private Size _extent; private Size _extent;
private Size _measuredExtent;
private Vector _offset; private Vector _offset;
private IDisposable _logicalScrollSubscription; private IDisposable _logicalScrollSubscription;
private Size _viewport; private Size _viewport;
@ -176,63 +175,36 @@ namespace Avalonia.Controls.Presenters
/// <inheritdoc/> /// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
var child = Child; if (_logicalScrollSubscription != null || Child == null)
if (child != null)
{ {
var measureSize = availableSize; return base.MeasureOverride(availableSize);
}
if (_logicalScrollSubscription == null)
{
measureSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
if (!CanScrollHorizontally) var constraint = new Size(
{ CanScrollHorizontally ? double.PositiveInfinity : availableSize.Width,
measureSize = measureSize.WithWidth(availableSize.Width); double.PositiveInfinity);
}
}
child.Measure(measureSize); Child.Measure(constraint);
var size = child.DesiredSize; return Child.DesiredSize.Constrain(availableSize);
_measuredExtent = size;
return size.Constrain(availableSize);
}
else
{
return Extent = new Size();
}
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize) protected override Size ArrangeOverride(Size finalSize)
{ {
var child = this.GetVisualChildren().SingleOrDefault() as ILayoutable; if (_logicalScrollSubscription != null || Child == null)
var logicalScroll = _logicalScrollSubscription != null;
if (!logicalScroll)
{
Viewport = finalSize;
Extent = _measuredExtent;
if (child != null)
{
var size = new Size(
CanScrollHorizontally ?
Math.Max(finalSize.Width, child.DesiredSize.Width) :
Math.Min(finalSize.Width, child.DesiredSize.Width),
Math.Max(finalSize.Height, child.DesiredSize.Height));
child.Arrange(new Rect((Point)(-Offset), size));
return finalSize;
}
}
else if (child != null)
{ {
child.Arrange(new Rect(finalSize)); return base.ArrangeOverride(finalSize);
return finalSize;
} }
return new Size(); var size = new Size(
CanScrollHorizontally ?
Math.Max(Child.DesiredSize.Width, finalSize.Width) :
finalSize.Width,
Math.Max(Child.DesiredSize.Height, finalSize.Height));
ArrangeOverrideImpl(size, -Offset);
Viewport = finalSize;
Extent = Child.Bounds.Size;
return finalSize;
} }
/// <inheritdoc/> /// <inheritdoc/>

194
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Layout.cs

@ -0,0 +1,194 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls.Presenters;
using Avalonia.Layout;
using Xunit;
namespace Avalonia.Controls.UnitTests.Presenters
{
public class ContentPresenterTests_Layout
{
[Theory]
[InlineData(HorizontalAlignment.Stretch, VerticalAlignment.Stretch, 0, 0, 100, 100)]
[InlineData(HorizontalAlignment.Left, VerticalAlignment.Stretch, 0, 0, 16, 100)]
[InlineData(HorizontalAlignment.Right, VerticalAlignment.Stretch, 84, 0, 16, 100)]
[InlineData(HorizontalAlignment.Center, VerticalAlignment.Stretch, 42, 0, 16, 100)]
[InlineData(HorizontalAlignment.Stretch, VerticalAlignment.Top, 0, 0, 100, 16)]
[InlineData(HorizontalAlignment.Stretch, VerticalAlignment.Bottom, 0, 84, 100, 16)]
[InlineData(HorizontalAlignment.Stretch, VerticalAlignment.Center, 0, 42, 100, 16)]
public void Content_Alignment_Is_Applied_To_Child_Bounds(
HorizontalAlignment h,
VerticalAlignment v,
double expectedX,
double expectedY,
double expectedWidth,
double expectedHeight)
{
Border content;
var target = new ContentPresenter
{
HorizontalContentAlignment = h,
VerticalContentAlignment = v,
Content = content = new Border
{
MinWidth = 16,
MinHeight = 16,
},
};
target.UpdateChild();
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(new Rect(expectedX, expectedY, expectedWidth, expectedHeight), content.Bounds);
}
[Theory]
[InlineData(HorizontalAlignment.Stretch, VerticalAlignment.Stretch, 10, 10, 80, 80)]
[InlineData(HorizontalAlignment.Left, VerticalAlignment.Stretch, 10, 10, 16, 80)]
[InlineData(HorizontalAlignment.Right, VerticalAlignment.Stretch, 74, 10, 16, 80)]
[InlineData(HorizontalAlignment.Center, VerticalAlignment.Stretch, 42, 10, 16, 80)]
[InlineData(HorizontalAlignment.Stretch, VerticalAlignment.Top, 10, 10, 80, 16)]
[InlineData(HorizontalAlignment.Stretch, VerticalAlignment.Bottom, 10, 74, 80, 16)]
[InlineData(HorizontalAlignment.Stretch, VerticalAlignment.Center, 10, 42, 80, 16)]
public void Content_Alignment_And_Padding_Are_Applied_To_Child_Bounds(
HorizontalAlignment h,
VerticalAlignment v,
double expectedX,
double expectedY,
double expectedWidth,
double expectedHeight)
{
Border content;
var target = new ContentPresenter
{
HorizontalContentAlignment = h,
VerticalContentAlignment = v,
Padding = new Thickness(10),
Content = content = new Border
{
MinWidth = 16,
MinHeight = 16,
},
};
target.UpdateChild();
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(new Rect(expectedX, expectedY, expectedWidth, expectedHeight), content.Bounds);
}
[Fact]
public void Content_Can_Be_Stretched()
{
Border content;
var target = new ContentPresenter
{
Content = content = new Border
{
MinWidth = 16,
MinHeight = 16,
},
};
target.UpdateChild();
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(new Rect(0, 0, 100, 100), content.Bounds);
}
[Fact]
public void Content_Can_Be_Right_Aligned()
{
Border content;
var target = new ContentPresenter
{
Content = content = new Border
{
MinWidth = 16,
MinHeight = 16,
HorizontalAlignment = HorizontalAlignment.Right
},
};
target.UpdateChild();
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(new Rect(84, 0, 16, 100), content.Bounds);
}
[Fact]
public void Content_Can_Be_Bottom_Aligned()
{
Border content;
var target = new ContentPresenter
{
Content = content = new Border
{
MinWidth = 16,
MinHeight = 16,
VerticalAlignment = VerticalAlignment.Bottom,
},
};
target.UpdateChild();
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(new Rect(0, 84, 100, 16), content.Bounds);
}
[Fact]
public void Content_Can_Be_TopLeft_Aligned()
{
Border content;
var target = new ContentPresenter
{
Content = content = new Border
{
MinWidth = 16,
MinHeight = 16,
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Top,
},
};
target.UpdateChild();
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(new Rect(84, 0, 16, 16), content.Bounds);
}
[Fact]
public void Content_Can_Be_TopRight_Aligned()
{
Border content;
var target = new ContentPresenter
{
Content = content = new Border
{
MinWidth = 16,
MinHeight = 16,
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Top,
},
};
target.UpdateChild();
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(new Rect(84, 0, 16, 16), content.Bounds);
}
[Fact]
public void Padding_Is_Applied_To_TopLeft_Aligned_Content()
{
}
}
}

4
tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs

@ -204,7 +204,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
scroll.Arrange(new Rect(0, 0, 100, 100)); scroll.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(20, target.Panel.Children.Count); Assert.Equal(20, target.Panel.Children.Count);
Assert.Equal(new Size(10, 200), scroll.Extent); Assert.Equal(new Size(100, 200), scroll.Extent);
Assert.Equal(new Size(100, 100), scroll.Viewport); Assert.Equal(new Size(100, 100), scroll.Viewport);
target.VirtualizationMode = ItemVirtualizationMode.Simple; target.VirtualizationMode = ItemVirtualizationMode.Simple;
@ -266,7 +266,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
scroll.Arrange(new Rect(0, 0, 100, 100)); scroll.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(20, target.Panel.Children.Count); Assert.Equal(20, target.Panel.Children.Count);
Assert.Equal(new Size(10, 200), scroll.Extent); Assert.Equal(new Size(100, 200), scroll.Extent);
Assert.Equal(new Size(100, 100), scroll.Viewport); Assert.Equal(new Size(100, 100), scroll.Viewport);
} }

129
tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs

@ -12,55 +12,32 @@ namespace Avalonia.Controls.UnitTests.Presenters
{ {
public class ScrollContentPresenterTests public class ScrollContentPresenterTests
{ {
[Fact] [Theory]
public void Content_Can_Be_Left_Aligned() [InlineData(HorizontalAlignment.Stretch, VerticalAlignment.Stretch, 10, 10, 80, 80)]
{ [InlineData(HorizontalAlignment.Left, VerticalAlignment.Stretch, 10, 10, 16, 80)]
Border content; [InlineData(HorizontalAlignment.Right, VerticalAlignment.Stretch, 74, 10, 16, 80)]
var target = new ScrollContentPresenter [InlineData(HorizontalAlignment.Center, VerticalAlignment.Stretch, 42, 10, 16, 80)]
{ [InlineData(HorizontalAlignment.Stretch, VerticalAlignment.Top, 10, 10, 80, 16)]
Content = content = new Border [InlineData(HorizontalAlignment.Stretch, VerticalAlignment.Bottom, 10, 74, 80, 16)]
{ [InlineData(HorizontalAlignment.Stretch, VerticalAlignment.Center, 10, 42, 80, 16)]
Padding = new Thickness(8), public void Alignment_And_Padding_Are_Applied_To_Child_Bounds(
HorizontalAlignment = HorizontalAlignment.Left HorizontalAlignment h,
}, VerticalAlignment v,
}; double expectedX,
double expectedY,
target.UpdateChild(); double expectedWidth,
target.Measure(new Size(100, 100)); double expectedHeight)
target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(new Rect(0, 0, 16, 100), content.Bounds);
}
[Fact]
public void Content_Can_Be_Stretched()
{
Border content;
var target = new ScrollContentPresenter
{
Content = content = new Border
{
Padding = new Thickness(8),
},
};
target.UpdateChild();
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(new Rect(0, 0, 100, 100), content.Bounds);
}
[Fact]
public void Content_Can_Be_Right_Aligned()
{ {
Border content; Border content;
var target = new ScrollContentPresenter var target = new ScrollContentPresenter
{ {
Padding = new Thickness(10),
Content = content = new Border Content = content = new Border
{ {
Padding = new Thickness(8), MinWidth = 16,
HorizontalAlignment = HorizontalAlignment.Right MinHeight = 16,
HorizontalAlignment = h,
VerticalAlignment = v,
}, },
}; };
@ -68,19 +45,19 @@ namespace Avalonia.Controls.UnitTests.Presenters
target.Measure(new Size(100, 100)); target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100)); target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(new Rect(84, 0, 16, 100), content.Bounds); Assert.Equal(new Rect(expectedX, expectedY, expectedWidth, expectedHeight), content.Bounds);
} }
[Fact] [Fact]
public void Content_Can_Be_Bottom_Aligned() public void DesiredSize_Is_Content_Size_When_Smaller_Than_AvailableSize()
{ {
Border content;
var target = new ScrollContentPresenter var target = new ScrollContentPresenter
{ {
Content = content = new Border Padding = new Thickness(10),
Content = new Border
{ {
Padding = new Thickness(8), MinWidth = 16,
VerticalAlignment = VerticalAlignment.Bottom, MinHeight = 16,
}, },
}; };
@ -88,20 +65,19 @@ namespace Avalonia.Controls.UnitTests.Presenters
target.Measure(new Size(100, 100)); target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100)); target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(new Rect(0, 84, 100, 16), content.Bounds); Assert.Equal(new Size(16, 16), target.DesiredSize);
} }
[Fact] [Fact]
public void Content_Can_Be_TopRight_Aligned() public void DesiredSize_Is_AvailableSize_When_Content_Larger_Than_AvailableSize()
{ {
Border content;
var target = new ScrollContentPresenter var target = new ScrollContentPresenter
{ {
Content = content = new Border Padding = new Thickness(10),
Content = new Border
{ {
Padding = new Thickness(8), MinWidth = 160,
HorizontalAlignment = HorizontalAlignment.Right, MinHeight = 160,
VerticalAlignment = VerticalAlignment.Top,
}, },
}; };
@ -109,7 +85,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
target.Measure(new Size(100, 100)); target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100)); target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(new Rect(84, 0, 16, 16), content.Bounds); Assert.Equal(new Size(100, 100), target.DesiredSize);
} }
[Fact] [Fact]
@ -201,6 +177,19 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Equal(new[] { "Viewport", "Extent" }, set); Assert.Equal(new[] { "Viewport", "Extent" }, set);
} }
[Fact]
public void Should_Correctly_Arrange_Child_Larger_Than_Viewport()
{
var child = new Canvas { MinWidth = 150, MinHeight = 150 };
var target = new ScrollContentPresenter { Content = child, };
target.UpdateChild();
target.Measure(Size.Infinity);
target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(new Size(150, 150), child.Bounds.Size);
}
[Fact] [Fact]
public void Arrange_Should_Constrain_Child_Width_When_CanScrollHorizontally_False() public void Arrange_Should_Constrain_Child_Width_When_CanScrollHorizontally_False()
{ {
@ -227,6 +216,32 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Equal(100, child.Bounds.Width); Assert.Equal(100, child.Bounds.Width);
} }
[Fact]
public void Extent_Width_Should_Be_Arrange_Width_When_CanScrollHorizontally_False()
{
var child = new WrapPanel
{
Children =
{
new Border { Width = 40, Height = 50 },
new Border { Width = 40, Height = 50 },
new Border { Width = 40, Height = 50 },
}
};
var target = new ScrollContentPresenter
{
Content = child,
CanScrollHorizontally = false,
};
target.UpdateChild();
target.Measure(Size.Infinity);
target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(new Size(100, 100), target.Extent);
}
[Fact] [Fact]
public void Setting_Offset_Should_Invalidate_Arrange() public void Setting_Offset_Should_Invalidate_Arrange()
{ {

110
tests/Avalonia.Layout.UnitTests/LayoutableTests.cs

@ -7,6 +7,97 @@ namespace Avalonia.Layout.UnitTests
{ {
public class LayoutableTests public class LayoutableTests
{ {
[Theory]
[InlineData(0, 0, 0, 0, 100, 100)]
[InlineData(10, 0, 0, 0, 90, 100)]
[InlineData(10, 0, 5, 0, 85, 100)]
[InlineData(0, 10, 0, 0, 100, 90)]
[InlineData(0, 10, 0, 5, 100, 85)]
[InlineData(4, 4, 6, 7, 90, 89)]
public void Margin_Is_Applied_To_MeasureOverride_Size(
double l,
double t,
double r,
double b,
double expectedWidth,
double expectedHeight)
{
var target = new TestLayoutable
{
Margin = new Thickness(l, t, r, b),
};
target.Measure(new Size(100, 100));
Assert.Equal(new Size(expectedWidth, expectedHeight), target.MeasureSize);
}
[Theory]
[InlineData(HorizontalAlignment.Stretch, 100)]
[InlineData(HorizontalAlignment.Left, 10)]
[InlineData(HorizontalAlignment.Center, 10)]
[InlineData(HorizontalAlignment.Right, 10)]
public void HorizontalAlignment_Is_Applied_To_ArrangeOverride_Size(
HorizontalAlignment h,
double expectedWidth)
{
var target = new TestLayoutable
{
HorizontalAlignment = h,
};
target.Measure(Size.Infinity);
target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(new Size(expectedWidth, 100), target.ArrangeSize);
}
[Theory]
[InlineData(VerticalAlignment.Stretch, 100)]
[InlineData(VerticalAlignment.Top, 10)]
[InlineData(VerticalAlignment.Center, 10)]
[InlineData(VerticalAlignment.Bottom, 10)]
public void VerticalAlignment_Is_Applied_To_ArrangeOverride_Size(
VerticalAlignment v,
double expectedHeight)
{
var target = new TestLayoutable
{
VerticalAlignment = v,
};
target.Measure(Size.Infinity);
target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(new Size(100, expectedHeight), target.ArrangeSize);
}
[Theory]
[InlineData(0, 0, 0, 0, 100, 100)]
[InlineData(10, 0, 0, 0, 90, 100)]
[InlineData(10, 0, 5, 0, 85, 100)]
[InlineData(0, 10, 0, 0, 100, 90)]
[InlineData(0, 10, 0, 5, 100, 85)]
[InlineData(4, 4, 6, 7, 90, 89)]
public void Margin_Is_Applied_To_ArrangeOverride_Size(
double l,
double t,
double r,
double b,
double expectedWidth,
double expectedHeight)
{
var target = new TestLayoutable
{
Margin = new Thickness(l, t, r, b),
};
target.Measure(Size.Infinity);
target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(new Size(expectedWidth, expectedHeight), target.ArrangeSize);
}
[Fact] [Fact]
public void Only_Calls_LayoutManager_InvalidateMeasure_Once() public void Only_Calls_LayoutManager_InvalidateMeasure_Once()
{ {
@ -86,5 +177,24 @@ namespace Avalonia.Layout.UnitTests
AvaloniaLocator.CurrentMutable.Bind<ILayoutManager>().ToConstant(layoutManager); AvaloniaLocator.CurrentMutable.Bind<ILayoutManager>().ToConstant(layoutManager);
return result; return result;
} }
private class TestLayoutable : Layoutable
{
public Size ArrangeSize { get; private set; }
public Size MeasureResult { get; set; } = new Size(10, 10);
public Size MeasureSize { get; private set; }
protected override Size MeasureOverride(Size availableSize)
{
MeasureSize = availableSize;
return MeasureResult;
}
protected override Size ArrangeOverride(Size finalSize)
{
ArrangeSize = finalSize;
return base.ArrangeOverride(finalSize);
}
}
} }
} }

Loading…
Cancel
Save