Skip to content

Commit

Permalink
Merge pull request #1035 from unoplatform/dev/cdb/autolayout-stretche…
Browse files Browse the repository at this point in the history
…d-with-max

fix: Fixed Axis Alignments (both primary and counter) when children is not filling space
  • Loading branch information
carldebilly authored Feb 7, 2024
2 parents 34543f8 + a98586c commit d882c6f
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 40 deletions.
79 changes: 79 additions & 0 deletions src/Uno.Toolkit.RuntimeTests/Tests/AutoLayoutTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,85 @@ public async Task When_Space_between_With_AbsolutePosition(Orientation orientati
}
}

[TestMethod]
[DataRow(300d, 100d)]
[DataRow(100d, 0d)]
[DataRow(120d, 10d)]
public async Task When_PrimaryAxisAlignment_Centered_And_Filled_Single_Element_With_MaxSize(double height, double expectedOffset)
{
var SUT = new AutoLayout()
{
Background = new SolidColorBrush(Color.FromArgb(255, 0, 0, 0)),
Orientation = Orientation.Vertical,
PrimaryAxisAlignment = AutoLayoutAlignment.Center,
Width = 300,
Height = height,
};

var border1 = new Border()
{
Background = new SolidColorBrush(Color.FromArgb(255, 0, 0, 255)),
MaxHeight = 100,
MaxWidth = 100,
};

AutoLayout.SetPrimaryAlignment(border1, AutoLayoutPrimaryAlignment.Stretch);

SUT.Children.Add(border1);

await UnitTestUIContentHelperEx.SetContentAndWait(SUT);

var border1Transform = border1.TransformToVisual(SUT).TransformPoint(new Windows.Foundation.Point(0, 0));
border1Transform!.Y.Should().BeApproximately(expectedOffset, precision: 1d);
}

[TestMethod]
[DataRow(300d, 75d, 125d)]
[DataRow(150d, 0d, 50d)]
public async Task When_PrimaryAxisAlignment_Centered_And_Filled_Elements_With_MaxSize(
double height,
double expectedOffset1,
double expectedOffset2)
{
var SUT = new AutoLayout()
{
Background = new SolidColorBrush(Color.FromArgb(255, 0, 0, 0)),
Orientation = Orientation.Vertical,
PrimaryAxisAlignment = AutoLayoutAlignment.Center,
Width = 300,
Height = height,
};

var border1 = new Border()
{
Background = new SolidColorBrush(Color.FromArgb(255, 0, 0, 255)),
Height = 50,
MaxWidth = 100,
};

var border2 = new Border()
{
Background = new SolidColorBrush(Color.FromArgb(255, 0, 0, 255)),
MaxHeight = 100,
MaxWidth = 100,
};

AutoLayout.SetPrimaryAlignment(border1, AutoLayoutPrimaryAlignment.Auto);
AutoLayout.SetPrimaryAlignment(border2, AutoLayoutPrimaryAlignment.Stretch);

SUT.Children.Add(border1);
SUT.Children.Add(border2);

await UnitTestUIContentHelperEx.SetContentAndWait(SUT);

var border1Transform = border1.TransformToVisual(SUT).TransformPoint(new Windows.Foundation.Point(0, 0));
var border2Transform = border2.TransformToVisual(SUT).TransformPoint(new Windows.Foundation.Point(0, 0));

border1Transform!.Y.Should().BeApproximately(expectedOffset1, precision: 1d);
border2Transform!.Y.Should().BeApproximately(expectedOffset2, precision: 1d);
}


[TestMethod]
public async Task When_Stretched_PrimaryAlignment_In_ScrollViewer()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,10 @@ protected override Size ArrangeOverride(Size finalSize)
continue; // Independent children are not stacked, skip it from the calculation
case AutoLayoutRole.Filled:
numberOfFilledChildren++;
if (calculatedChild.Element as FrameworkElement is { } frameworkElement && double.IsInfinity(frameworkElement.GetMaxLength(orientation)) is false)
if (calculatedChild.MeasuredLength < double.MaxValue)
{
var maxLenght = frameworkElement.GetMaxLength(orientation);
totalOfFillMaxSize += calculatedChild.MeasuredLength;
numberOfFilledChildrenWithMax++;
totalOfFillMaxSize = totalOfFillMaxSize + maxLenght;
}
break;
case AutoLayoutRole.Hug:
Expand Down Expand Up @@ -141,9 +140,9 @@ protected override Size ArrangeOverride(Size finalSize)

EnsureZeroFloor(ref filledMaxSurplus);

var filledChildrenSizeAfterMaxSize = numberOfFilledChildren - numberOfFilledChildrenWithMax > 0 ?
filledChildrenSize + (filledMaxSurplus / (numberOfFilledChildren - numberOfFilledChildrenWithMax)) :
filledChildrenSize + GetChildrenLowerThanAllocateSurplus(_calculatedChildren, filledChildrenSize);
var filledChildrenSizeAfterMaxSize = numberOfFilledChildren - numberOfFilledChildrenWithMax > 0
? filledChildrenSize + (filledMaxSurplus / (numberOfFilledChildren - numberOfFilledChildrenWithMax))
: filledChildrenSize + GetChildrenLowerThanAllocateSurplus(_calculatedChildren, filledChildrenSize);

EnsureZeroFloor(ref filledChildrenSizeAfterMaxSize);

Expand Down Expand Up @@ -213,9 +212,14 @@ protected override Size ArrangeOverride(Size finalSize)
EnsureZeroFloor(ref childLength);

// Calculate the position of the child by applying the alignment instructions
var isCounterAlignemntDefined = child.Element.ReadLocalValue(CounterAlignmentProperty) as AutoLayoutAlignment? is { };

var counterAlignment = !isCounterAlignemntDefined ? this.CounterAxisAlignment : GetCounterAlignment(child.Element);
// Check if the child has a local value for CounterAlignment
if (child.Element.ReadLocalValue(CounterAlignmentProperty) is not AutoLayoutAlignment counterAlignment)
{
// If not, use the panel's CounterAXisAlignment property instead
counterAlignment = CounterAxisAlignment;
}

var isStretch = counterAlignment is AutoLayoutAlignment.Stretch;
var haveCounterStartPadding = isStretch || counterAlignment is AutoLayoutAlignment.Start;
var counterStartPadding = haveCounterStartPadding ? (isHorizontal ? padding.Top : padding.Left) : 0;
Expand All @@ -234,8 +238,10 @@ protected override Size ArrangeOverride(Size finalSize)

ApplyMinMaxValues(child.Element, orientation, ref childSize);

var alignmentForCounterAlignmentOffset = counterAlignment is AutoLayoutAlignment.Stretch ? CounterAxisAlignment : counterAlignment;

var counterAlignmentOffset =
ComputeCounterAlignmentOffset(counterAlignment, childSize.GetCounterLength(orientation), availableCounterLength, padding, isHorizontal);
ComputeCounterAlignmentOffset(alignmentForCounterAlignmentOffset, childSize.GetCounterLength(orientation), availableCounterLength, padding, isHorizontal);

var calculatedOffset = counterAlignmentOffset + counterStartPadding + borderThickness.GetCounterStartLength(orientation);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,23 +168,24 @@ private void ApplyMinMaxValues(ref Size desiredSize)
role = AutoLayoutRole.Independent;
numberOfStackedChildren--;
}
else if (GetPrimaryAlignment(child) == AutoLayoutPrimaryAlignment.Stretch && !filledAsHug)
else if (child is FrameworkElement frameworkElement)
{
atLeastOneFilledChild = true;
role = AutoLayoutRole.Filled;
}
else if(GetPrimaryLength(child) is var l and >= 0 and < double.PositiveInfinity)
{
role = AutoLayoutRole.Fixed;
length = l;
}
else
{
var size = child is FrameworkElement frameworkElement ?
Math.Max(frameworkElement.GetLength(orientation), frameworkElement.GetMinLength(orientation)) :
-1;
var size = frameworkElement.GetPrimaryLength(orientation) is var l and >= 0 and < double.PositiveInfinity
? l
: -1; // no size defined
var minSize = frameworkElement.GetMinLength(orientation);
var maxSize = Math.Max(frameworkElement.GetMaxLength(orientation), minSize);

// Apply min/max size constraints
size = size >= 0 ? Math.Max(Math.Min(size, maxSize), minSize) : size;

if (size >= 0)
if (GetPrimaryAlignment(child) == AutoLayoutPrimaryAlignment.Stretch && !filledAsHug)
{
// If the max size is not defined, we can't use it as the hug size
role = AutoLayoutRole.Filled;
atLeastOneFilledChild = true;
}
else if (size >= 0)
{
role = AutoLayoutRole.Fixed;
length = size;
Expand All @@ -194,6 +195,10 @@ private void ApplyMinMaxValues(ref Size desiredSize)
role = AutoLayoutRole.Hug;
}
}
else
{
role = AutoLayoutRole.Hug;
}

_calculatedChildren[i] = new CalculatedChildren(child, role, length);
}
Expand Down Expand Up @@ -285,11 +290,11 @@ private bool MeasureFilledChildren(
{
var child = _calculatedChildren![i];

if (child.Role == AutoLayoutRole.Filled && child.IsVisible)
if (child is { Role: AutoLayoutRole.Filled, IsVisible: true })
{
filledChildrenCount++;
}
}
}

if (filledChildrenCount <= 0)
{
Expand All @@ -309,7 +314,24 @@ private bool MeasureFilledChildren(

MeasureChild(child.Element, orientation, filledSize, availableCounterSize, ref desiredCounterSize, counterPaddingSize, CounterAxisAlignment);

child.MeasuredLength = child.Element is FrameworkElement fe ? Math.Min(fe.GetMaxLength(orientation), double.MaxValue) : double.MaxValue;
if (child.Element is FrameworkElement fe)
{
var maxLength = fe.GetMaxLength(orientation);
var length = fe.GetPrimaryLength(orientation);

if (maxLength.IsFinite())
{
child.MeasuredLength = length.IsFinite() ? Math.Min(maxLength, length) : maxLength;
}
else if (length.IsFinite())
{
child.MeasuredLength = length;
}
else
{
child.MeasuredLength = double.MaxValue;
}
}
}

return true; // at least one filled child
Expand Down Expand Up @@ -473,6 +495,7 @@ public CalculatedChildren(UIElement element, AutoLayoutRole role, double measure
/// <remarks>
/// Will be zero when the element is absolutely positioned, because it is not stacked
/// with others.
/// When the element role is FILLED, this value will be the maximum length of the element.
/// </remarks>
internal double MeasuredLength { get; set; }
}
Expand Down
29 changes: 16 additions & 13 deletions src/Uno.Toolkit.UI/Controls/AutoLayout/AutoLayoutExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,23 @@ internal static double GetLength(this Thickness thickness, Orientation orientati
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static double GetLength(this FrameworkElement frameworkElement, Orientation orientation)
internal static bool IsFinite(this double value)
{
return !double.IsNaN(value) && !double.IsInfinity(value);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static double GetPrimaryLength(this FrameworkElement frameworkElement, Orientation orientation)
{
// Check first if a PrimaryLength attached property is set on the element
// (legacy stuff - not really used anymore because it's too verbose and difficult to understand)
var primaryLength = AutoLayout.GetPrimaryLength(frameworkElement);
if (primaryLength.IsFinite())
{
return primaryLength;
}

// Fallback to width or height depending on the orientation
return orientation switch
{
Orientation.Horizontal => frameworkElement.Width,
Expand Down Expand Up @@ -114,18 +129,6 @@ internal static double GetCounterLength(this Thickness thickness, Orientation or
return thickness.GetLength(orientation == Orientation.Horizontal ? Orientation.Vertical : Orientation.Horizontal);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static double GetCounterLength(this FrameworkElement frameworkElement, Orientation orientation)
{
return frameworkElement.GetLength(orientation == Orientation.Horizontal ? Orientation.Vertical : Orientation.Horizontal);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static double GetMinCounterLength(this FrameworkElement frameworkElement, Orientation orientation)
{
return frameworkElement.GetMinLength(orientation == Orientation.Horizontal ? Orientation.Vertical : Orientation.Horizontal);
}

internal static HorizontalAlignment ToHorizontalAlignment(this AutoLayoutAlignment layoutAlignment)
{
return layoutAlignment switch
Expand Down

0 comments on commit d882c6f

Please sign in to comment.