Browse Source

Merge pull request #2431 from AvaloniaUI/fixes/2350-stackpanel-layout

Fix StackPanel layout
pull/2476/head
Steven Kirk 7 years ago
committed by GitHub
parent
commit
e8a6908c3b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      src/Avalonia.Controls/Image.cs
  2. 54
      src/Avalonia.Controls/StackPanel.cs
  3. 2
      src/Avalonia.Controls/Viewbox.cs
  4. 62
      src/Avalonia.Layout/LayoutExtensions.cs
  5. 2
      src/Avalonia.Layout/Layoutable.cs
  6. 160
      tests/Avalonia.Controls.UnitTests/StackPanelTests.cs

11
src/Avalonia.Controls/Image.cs

@ -81,23 +81,22 @@ namespace Avalonia.Controls
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
var source = Source; var source = Source;
var result = new Size();
if (source != null) if (source != null)
{ {
Size sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height); Size sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height);
if (double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height)) if (double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height))
{ {
return sourceSize; result = sourceSize;
} }
else else
{ {
return Stretch.CalculateSize(availableSize, sourceSize); result = Stretch.CalculateSize(availableSize, sourceSize);
} }
} }
else
{ return result.Constrain(availableSize);
return new Size();
}
} }
/// <inheritdoc/> /// <inheritdoc/>

54
src/Avalonia.Controls/StackPanel.cs

@ -4,6 +4,7 @@
using System; using System;
using System.Linq; using System.Linq;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Layout;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -219,30 +220,16 @@ namespace Avalonia.Controls
measuredWidth -= (hasVisibleChild ? spacing : 0); measuredWidth -= (hasVisibleChild ? spacing : 0);
} }
return new Size(measuredWidth, measuredHeight); return new Size(measuredWidth, measuredHeight).Constrain(availableSize);
} }
/// <summary> /// <inheritdoc/>
/// Arranges the control's children.
/// </summary>
/// <param name="finalSize">The size allocated to the control.</param>
/// <returns>The space taken.</returns>
protected override Size ArrangeOverride(Size finalSize) protected override Size ArrangeOverride(Size finalSize)
{ {
var orientation = Orientation; var orientation = Orientation;
double arrangedWidth = finalSize.Width; var spacing = Spacing;
double arrangedHeight = finalSize.Height; var finalRect = new Rect(finalSize);
double spacing = Spacing; var pos = 0.0;
bool hasVisibleChild = Children.Any(c => c.IsVisible);
if (Orientation == Orientation.Vertical)
{
arrangedHeight = 0;
}
else
{
arrangedWidth = 0;
}
foreach (Control child in Children) foreach (Control child in Children)
{ {
@ -251,32 +238,21 @@ namespace Avalonia.Controls
if (orientation == Orientation.Vertical) if (orientation == Orientation.Vertical)
{ {
double width = Math.Max(childWidth, arrangedWidth); var rect = new Rect(0, pos, childWidth, childHeight)
Rect childFinal = new Rect(0, arrangedHeight, width, childHeight); .Align(finalRect, child.HorizontalAlignment, VerticalAlignment.Top);
ArrangeChild(child, childFinal, finalSize, orientation); ArrangeChild(child, rect, finalSize, orientation);
arrangedWidth = Math.Max(arrangedWidth, childWidth); pos += childHeight + spacing;
arrangedHeight += childHeight + (child.IsVisible ? spacing : 0);
} }
else else
{ {
double height = Math.Max(childHeight, arrangedHeight); var rect = new Rect(pos, 0, childWidth, childHeight)
Rect childFinal = new Rect(arrangedWidth, 0, childWidth, height); .Align(finalRect, HorizontalAlignment.Left, child.VerticalAlignment);
ArrangeChild(child, childFinal, finalSize, orientation); ArrangeChild(child, rect, finalSize, orientation);
arrangedWidth += childWidth + (child.IsVisible ? spacing : 0); pos += childWidth + spacing;
arrangedHeight = Math.Max(arrangedHeight, childHeight);
} }
} }
if (orientation == Orientation.Vertical) return finalSize;
{
arrangedHeight = Math.Max(arrangedHeight - (hasVisibleChild ? spacing : 0), finalSize.Height);
}
else
{
arrangedWidth = Math.Max(arrangedWidth - (hasVisibleChild ? spacing : 0), finalSize.Width);
}
return new Size(arrangedWidth, arrangedHeight);
} }
internal virtual void ArrangeChild( internal virtual void ArrangeChild(

2
src/Avalonia.Controls/Viewbox.cs

@ -49,7 +49,7 @@ namespace Avalonia.Controls
var scale = GetScale(availableSize, childSize, Stretch); var scale = GetScale(availableSize, childSize, Stretch);
return childSize * scale; return (childSize * scale).Constrain(availableSize);
} }
return new Size(); return new Size();

62
src/Avalonia.Layout/LayoutExtensions.cs

@ -0,0 +1,62 @@
using System;
namespace Avalonia.Layout
{
/// <summary>
/// Extension methods for layout types.
/// </summary>
public static class LayoutExtensions
{
/// <summary>
/// Aligns a rect in a constraining rect according to horizontal and vertical alignment
/// settings.
/// </summary>
/// <param name="rect">The rect to align.</param>
/// <param name="constraint">The constraining rect.</param>
/// <param name="horizontalAlignment">The horizontal alignment.</param>
/// <param name="verticalAlignment">The vertical alignment.</param>
/// <returns></returns>
public static Rect Align(
this Rect rect,
Rect constraint,
HorizontalAlignment horizontalAlignment,
VerticalAlignment verticalAlignment)
{
switch (horizontalAlignment)
{
case HorizontalAlignment.Center:
rect = rect.WithX((constraint.Width - rect.Width) / 2);
break;
case HorizontalAlignment.Right:
rect = rect.WithX(constraint.Width - rect.Width);
break;
case HorizontalAlignment.Stretch:
rect = new Rect(
0,
rect.Y,
Math.Max(constraint.Width, rect.Width),
rect.Height);
break;
}
switch (verticalAlignment)
{
case VerticalAlignment.Center:
rect = rect.WithY((constraint.Height - rect.Height) / 2);
break;
case VerticalAlignment.Bottom:
rect = rect.WithY(constraint.Height - rect.Height);
break;
case VerticalAlignment.Stretch:
rect = new Rect(
rect.X,
0,
rect.Width,
Math.Max(constraint.Height, rect.Height));
break;
}
return rect;
}
}
}

2
src/Avalonia.Layout/Layoutable.cs

@ -314,7 +314,7 @@ namespace Avalonia.Layout
try try
{ {
_measuring = true; _measuring = true;
desiredSize = MeasureCore(availableSize).Constrain(availableSize); desiredSize = MeasureCore(availableSize);
} }
finally finally
{ {

160
tests/Avalonia.Controls.UnitTests/StackPanelTests.cs

@ -1,7 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls; using System.Linq;
using Avalonia.Layout;
using Xunit; using Xunit;
namespace Avalonia.Controls.UnitTests namespace Avalonia.Controls.UnitTests
@ -147,6 +148,151 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Rect(50, 0, 50, 120), target.Children[2].Bounds); Assert.Equal(new Rect(50, 0, 50, 120), target.Children[2].Bounds);
} }
[Fact]
public void Arranges_Vertical_Children_With_Correct_Bounds()
{
var target = new StackPanel
{
Orientation = Orientation.Vertical,
Children =
{
new TestControl
{
HorizontalAlignment = HorizontalAlignment.Left,
MeasureSize = new Size(50, 10),
},
new TestControl
{
HorizontalAlignment = HorizontalAlignment.Left,
MeasureSize = new Size(150, 10),
},
new TestControl
{
HorizontalAlignment = HorizontalAlignment.Center,
MeasureSize = new Size(50, 10),
},
new TestControl
{
HorizontalAlignment = HorizontalAlignment.Center,
MeasureSize = new Size(150, 10),
},
new TestControl
{
HorizontalAlignment = HorizontalAlignment.Right,
MeasureSize = new Size(50, 10),
},
new TestControl
{
HorizontalAlignment = HorizontalAlignment.Right,
MeasureSize = new Size(150, 10),
},
new TestControl
{
HorizontalAlignment = HorizontalAlignment.Stretch,
MeasureSize = new Size(50, 10),
},
new TestControl
{
HorizontalAlignment = HorizontalAlignment.Stretch,
MeasureSize = new Size(150, 10),
},
}
};
target.Measure(new Size(100, 150));
Assert.Equal(new Size(100, 80), target.DesiredSize);
target.Arrange(new Rect(target.DesiredSize));
var bounds = target.Children.Select(x => x.Bounds).ToArray();
Assert.Equal(
new[]
{
new Rect(0, 0, 50, 10),
new Rect(0, 10, 150, 10),
new Rect(25, 20, 50, 10),
new Rect(-25, 30, 150, 10),
new Rect(50, 40, 50, 10),
new Rect(-50, 50, 150, 10),
new Rect(0, 60, 100, 10),
new Rect(0, 70, 150, 10),
}, bounds);
}
[Fact]
public void Arranges_Horizontal_Children_With_Correct_Bounds()
{
var target = new StackPanel
{
Orientation = Orientation.Horizontal,
Children =
{
new TestControl
{
VerticalAlignment = VerticalAlignment.Top,
MeasureSize = new Size(10, 50),
},
new TestControl
{
VerticalAlignment = VerticalAlignment.Top,
MeasureSize = new Size(10, 150),
},
new TestControl
{
VerticalAlignment = VerticalAlignment.Center,
MeasureSize = new Size(10, 50),
},
new TestControl
{
VerticalAlignment = VerticalAlignment.Center,
MeasureSize = new Size(10, 150),
},
new TestControl
{
VerticalAlignment = VerticalAlignment.Bottom,
MeasureSize = new Size(10, 50),
},
new TestControl
{
VerticalAlignment = VerticalAlignment.Bottom,
MeasureSize = new Size(10, 150),
},
new TestControl
{
VerticalAlignment = VerticalAlignment.Stretch,
MeasureSize = new Size(10, 50),
},
new TestControl
{
VerticalAlignment = VerticalAlignment.Stretch,
MeasureSize = new Size(10, 150),
},
}
};
target.Measure(new Size(150, 100));
Assert.Equal(new Size(80, 100), target.DesiredSize);
target.Arrange(new Rect(target.DesiredSize));
var bounds = target.Children.Select(x => x.Bounds).ToArray();
Assert.Equal(
new[]
{
new Rect(0, 0, 10, 50),
new Rect(10, 0, 10, 150),
new Rect(20, 25, 10, 50),
new Rect(30, -25, 10, 150),
new Rect(40, 50, 10, 50),
new Rect(50, -50, 10, 150),
new Rect(60, 0, 10, 100),
new Rect(70, 0, 10, 150),
}, bounds);
}
[Theory] [Theory]
[InlineData(Orientation.Horizontal)] [InlineData(Orientation.Horizontal)]
[InlineData(Orientation.Vertical)] [InlineData(Orientation.Vertical)]
@ -185,5 +331,17 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(sizeWithTwoChildren, sizeWithThreeChildren); Assert.Equal(sizeWithTwoChildren, sizeWithThreeChildren);
} }
private class TestControl : Control
{
public Size MeasureConstraint { get; private set; }
public Size MeasureSize { get; set; }
protected override Size MeasureOverride(Size availableSize)
{
MeasureConstraint = availableSize;
return MeasureSize;
}
}
} }
} }

Loading…
Cancel
Save