diff --git a/src/Avalonia.Controls/Border.cs b/src/Avalonia.Controls/Border.cs index f425ae3f01..002c5ea3f2 100644 --- a/src/Avalonia.Controls/Border.cs +++ b/src/Avalonia.Controls/Border.cs @@ -108,18 +108,7 @@ namespace Avalonia.Controls /// The desired size of the control. protected override Size MeasureOverride(Size availableSize) { - var child = Child; - 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); - } + return MeasureOverrideImpl(availableSize, Child, Padding, BorderThickness); } /// @@ -129,15 +118,32 @@ namespace Avalonia.Controls /// The space taken. protected override Size ArrangeOverride(Size finalSize) { - var child = Child; - - if (child != null) + if (Child != null) { var padding = Padding + new Thickness(BorderThickness); - child.Arrange(new Rect(finalSize).Deflate(padding)); + Child.Arrange(new Rect(finalSize).Deflate(padding)); } 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); + } + } } } \ No newline at end of file diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index a97fdf8784..d0a438cc2b 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/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. using System; -using System.Reactive.Linq; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.Metadata; -using Avalonia.VisualTree; namespace Avalonia.Controls.Presenters { @@ -340,94 +338,121 @@ namespace Avalonia.Controls.Presenters /// protected override Size MeasureOverride(Size availableSize) { - var child = Child; - var padding = Padding + new Thickness(BorderThickness); + return Border.MeasureOverrideImpl(availableSize, Child, Padding, BorderThickness); + } + + /// + protected override Size ArrangeOverride(Size finalSize) + { + return ArrangeOverrideImpl(finalSize, new Vector()); + } + + /// + /// Called when the property changes. + /// + /// The event args. + private void ContentChanged(AvaloniaPropertyChangedEventArgs e) + { + _createdChild = false; - if (child != null) + if (((ILogical)this).IsAttachedToLogicalTree) { - child.Measure(availableSize.Deflate(padding)); - return child.DesiredSize.Inflate(padding); + UpdateChild(); } - 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(); } - /// - protected override Size ArrangeOverride(Size finalSize) + internal Size ArrangeOverrideImpl(Size finalSize, Vector offset) { - var child = Child; - - if (child != null) + if (Child != null) { - var padding = Padding + new Thickness(BorderThickness); - var sizeMinusPadding = finalSize.Deflate(padding); - var size = sizeMinusPadding; - var horizontalAlignment = HorizontalContentAlignment; - var verticalAlignment = VerticalContentAlignment; - var originX = padding.Left; - var originY = padding.Top; - - if (horizontalAlignment != HorizontalAlignment.Stretch) + var padding = Padding; + var borderThickness = BorderThickness; + var horizontalContentAlignment = HorizontalContentAlignment; + var verticalContentAlignment = VerticalContentAlignment; + var useLayoutRounding = UseLayoutRounding; + var availableSizeMinusMargins = new Size( + Math.Max(0, finalSize.Width - padding.Left - padding.Right - borderThickness), + Math.Max(0, finalSize.Height - padding.Top - padding.Bottom - borderThickness)); + 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: - originX += (sizeMinusPadding.Width - size.Width) / 2; + case HorizontalAlignment.Stretch: + originX += (availableSizeMinusMargins.Width - size.Width) / 2; break; case HorizontalAlignment.Right: - originX = size.Width - child.DesiredSize.Width; + originX += availableSizeMinusMargins.Width - size.Width; break; } - switch (verticalAlignment) + switch (verticalContentAlignment) { - case VerticalAlignment.Stretch: case VerticalAlignment.Center: - originY += (sizeMinusPadding.Height - size.Height) / 2; + case VerticalAlignment.Stretch: + originY += (availableSizeMinusMargins.Height - size.Height) / 2; break; case VerticalAlignment.Bottom: - originY = size.Height - child.DesiredSize.Height; + originY += availableSizeMinusMargins.Height - size.Height; 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; } - /// - /// Called when the property changes. - /// - /// The event args. - private void ContentChanged(AvaloniaPropertyChangedEventArgs e) + private double GetLayoutScale() { - _createdChild = false; + var result = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0; - if (((ILogical)this).IsAttachedToLogicalTree) + if (result == 0 || double.IsNaN(result) || double.IsInfinity(result)) { - UpdateChild(); - } - else if (Child != null) - { - VisualChildren.Remove(Child); - LogicalChildren.Remove(Child); - Child = null; - _dataTemplate = null; + throw new Exception($"Invalid LayoutScaling returned from {VisualRoot.GetType()}"); } - InvalidateMeasure(); + return result; } private void TemplatedParentChanged(AvaloniaPropertyChangedEventArgs e) diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs index d982ed4ac4..75b1602919 100644 --- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs @@ -48,7 +48,6 @@ namespace Avalonia.Controls.Presenters ScrollViewer.CanScrollHorizontallyProperty.AddOwner(); private Size _extent; - private Size _measuredExtent; private Vector _offset; private IDisposable _logicalScrollSubscription; private Size _viewport; @@ -176,63 +175,36 @@ namespace Avalonia.Controls.Presenters /// protected override Size MeasureOverride(Size availableSize) { - var child = Child; - - if (child != null) + if (_logicalScrollSubscription != null || Child == null) { - var measureSize = availableSize; - - if (_logicalScrollSubscription == null) - { - measureSize = new Size(double.PositiveInfinity, double.PositiveInfinity); + return base.MeasureOverride(availableSize); + } - if (!CanScrollHorizontally) - { - measureSize = measureSize.WithWidth(availableSize.Width); - } - } + var constraint = new Size( + CanScrollHorizontally ? double.PositiveInfinity : availableSize.Width, + double.PositiveInfinity); - child.Measure(measureSize); - var size = child.DesiredSize; - _measuredExtent = size; - return size.Constrain(availableSize); - } - else - { - return Extent = new Size(); - } + Child.Measure(constraint); + return Child.DesiredSize.Constrain(availableSize); } /// protected override Size ArrangeOverride(Size finalSize) { - var child = this.GetVisualChildren().SingleOrDefault() as ILayoutable; - 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) + if (_logicalScrollSubscription != null || Child == null) { - child.Arrange(new Rect(finalSize)); - return finalSize; + return base.ArrangeOverride(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; } /// diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Layout.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Layout.cs new file mode 100644 index 0000000000..450b85696e --- /dev/null +++ b/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() + { + } + } +} \ No newline at end of file diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs index 58f4127cdc..ece79d3a95 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs +++ b/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)); 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); target.VirtualizationMode = ItemVirtualizationMode.Simple; @@ -266,7 +266,7 @@ namespace Avalonia.Controls.UnitTests.Presenters scroll.Arrange(new Rect(0, 0, 100, 100)); 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); } diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs index d8d1f127f8..1830c3e5c1 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs @@ -12,55 +12,32 @@ namespace Avalonia.Controls.UnitTests.Presenters { public class ScrollContentPresenterTests { - [Fact] - public void Content_Can_Be_Left_Aligned() - { - Border content; - var target = new ScrollContentPresenter - { - Content = content = new Border - { - Padding = new Thickness(8), - HorizontalAlignment = HorizontalAlignment.Left - }, - }; - - target.UpdateChild(); - target.Measure(new Size(100, 100)); - 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() + [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 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 ScrollContentPresenter { + Padding = new Thickness(10), Content = content = new Border { - Padding = new Thickness(8), - HorizontalAlignment = HorizontalAlignment.Right + MinWidth = 16, + MinHeight = 16, + HorizontalAlignment = h, + VerticalAlignment = v, }, }; @@ -68,19 +45,19 @@ namespace Avalonia.Controls.UnitTests.Presenters target.Measure(new Size(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] - public void Content_Can_Be_Bottom_Aligned() + public void DesiredSize_Is_Content_Size_When_Smaller_Than_AvailableSize() { - Border content; var target = new ScrollContentPresenter { - Content = content = new Border + Padding = new Thickness(10), + Content = new Border { - Padding = new Thickness(8), - VerticalAlignment = VerticalAlignment.Bottom, + MinWidth = 16, + MinHeight = 16, }, }; @@ -88,20 +65,19 @@ namespace Avalonia.Controls.UnitTests.Presenters target.Measure(new Size(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] - public void Content_Can_Be_TopRight_Aligned() + public void DesiredSize_Is_AvailableSize_When_Content_Larger_Than_AvailableSize() { - Border content; var target = new ScrollContentPresenter { - Content = content = new Border + Padding = new Thickness(10), + Content = new Border { - Padding = new Thickness(8), - HorizontalAlignment = HorizontalAlignment.Right, - VerticalAlignment = VerticalAlignment.Top, + MinWidth = 160, + MinHeight = 160, }, }; @@ -109,7 +85,7 @@ namespace Avalonia.Controls.UnitTests.Presenters target.Measure(new Size(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] @@ -201,6 +177,19 @@ namespace Avalonia.Controls.UnitTests.Presenters 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] 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); } + [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] public void Setting_Offset_Should_Invalidate_Arrange() { diff --git a/tests/Avalonia.Layout.UnitTests/LayoutableTests.cs b/tests/Avalonia.Layout.UnitTests/LayoutableTests.cs index dcc65edc74..410b2ffb2e 100644 --- a/tests/Avalonia.Layout.UnitTests/LayoutableTests.cs +++ b/tests/Avalonia.Layout.UnitTests/LayoutableTests.cs @@ -7,6 +7,97 @@ namespace Avalonia.Layout.UnitTests { 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] public void Only_Calls_LayoutManager_InvalidateMeasure_Once() { @@ -86,5 +177,24 @@ namespace Avalonia.Layout.UnitTests AvaloniaLocator.CurrentMutable.Bind().ToConstant(layoutManager); 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); + } + } } }