Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add CounterAxisAlignment #995

Merged
merged 1 commit into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions doc/controls/AutoLayoutControl.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ Property|Type|Description
Orientation | `Orientation` | Gets or sets the dimension by which the items are stacked
Spacing | `double` | Gets or sets a uniform distance (in pixels) between stacked items. It is applied in the direction of the `AutoLayout`'s Orientation
Justify | `AutoLayoutJustify` | Gets or sets the value to determine how items are justified within the container. Options are `Stack` or `SpaceBetween`. Note: if a child has its `PrimaryAlignment` set to `Stretch`, it will default to `Stack`
PrimaryAxisAlignment | `AutoLayoutAlignment` | Gets or sets the alignment characteristics that are applied to the content, based on the value of the `Orientation` property. Options are `Start`, `Center`, and `End`. The default is `Start`
PrimaryAxisAlignment | `AutoLayoutAlignment` | Gets or sets the alignment characteristics that are applied to the content, based on the value of the `Orientation` property. Options are `Start`, `Center`, and `End`. The default is `Start`.
CounterAxisAlignment | `AutoLayoutAlignment` | Gets or sets the alignment characteristics that are applied to the content, based on the inverse value of the `Orientation` property. Options are `Start`, `Center`, and `End`. The default is `Start`. If already set in `CounterAlignment` CounterAlignment will have priority.
IsReverseZIndex | `bool` | Gets or sets whether or not the ZIndex of the children should be reversed. The default is `false`
Padding | `Thickness` | **WARNING:** Padding for `AutoLayout` behaves the same as it does within the Figma Plugin: The anchor points determine which sides of the Padding will be taken into consideration. For example, items that are aligned to the Right and Top positions will only take the `Tickness.Right` and `Thickness.Top` values of `Padding` into consideration

Expand All @@ -54,7 +55,7 @@ Padding | `Thickness` | **WARNING:** Padding for `AutoLayout` behaves the same a
Property|Type|Description
-|-|-
PrimaryAlignment|`AutoLayoutPrimaryAlignment`| Gets or sets the alignment characteristics that are applied to this element when it is composed in an `AutoLayout` parent, based on the value of the `Orientation` property. Options are `Auto` and `Stretch`. The default is `Auto`
CounterAlignment|`AutoLayoutAlignment`| Gets or sets the counter-alignment characteristics that are applied to this element when it is composed in an `AutoLayout` parent, based on the inverse value of the `Orientation` property. Options are `Start`, `Center`, `End`, and `Stretch`. The default is `Start`
CounterAlignment|`AutoLayoutAlignment`| Gets or sets the counter-alignment characteristics that are applied to this element when it is composed in an `AutoLayout` parent, based on the inverse value of the `Orientation` property. Options are `Start`, `Center`, `End`, and `Stretch`. The default is `Start`. If already set in `CounterAxisAlignment` CounterAlignment will have priority.
PrimaryLength|`double`| Gets or sets the height or width of the child depending on the `Orientation`. If `Height` or `Width` are already set they will have priority.
CounterLength|`double`| Gets or sets the height or width of the child depending on the inverse of `Orientation`. If `Height` or `Width` are already set they will have priority.
IsIndependentLayout | `bool` | **WARNING:** Should not be used with other Attached Properties. Gets or sets whether the element is independent of the `AutoLayout` positioning. Reflects the `Absolute Position` option from Figma
117 changes: 117 additions & 0 deletions src/Uno.Toolkit.RuntimeTests/Tests/AutoLayoutTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,84 @@ public async Task When_Padding(bool isStretch, Orientation orientation, AutoLayo
Assert.AreEqual(rec2expected, border1Transform!.X);
}

[TestMethod]
[RequiresFullWindow]
[DataRow(false, Orientation.Vertical, AutoLayoutAlignment.Start, AutoLayoutAlignment.Center, 100, 25)]
[DataRow(false, Orientation.Vertical, AutoLayoutAlignment.Center, AutoLayoutAlignment.Center, 25, 25)]
[DataRow(false, Orientation.Vertical, AutoLayoutAlignment.Start, AutoLayoutAlignment.Start, 100, 100)]
[DataRow(false, Orientation.Vertical, AutoLayoutAlignment.Center, AutoLayoutAlignment.Start, 25, 100)]
[DataRow(false, Orientation.Horizontal, AutoLayoutAlignment.Center, AutoLayoutAlignment.Start, 100, 25)]
[DataRow(false, Orientation.Horizontal, AutoLayoutAlignment.Center, AutoLayoutAlignment.Center, 25, 25)]
[DataRow(false, Orientation.Horizontal, AutoLayoutAlignment.Start, AutoLayoutAlignment.Start, 100, 100)]
[DataRow(false, Orientation.Horizontal, AutoLayoutAlignment.Start, AutoLayoutAlignment.Center, 25, 100)]
[DataRow(true, Orientation.Vertical, AutoLayoutAlignment.Start, AutoLayoutAlignment.Center, 100, 25)]
[DataRow(true, Orientation.Vertical, AutoLayoutAlignment.Center, AutoLayoutAlignment.Center, 100, 25)]
[DataRow(true, Orientation.Vertical, AutoLayoutAlignment.End, AutoLayoutAlignment.Center, 100, 25)]
[DataRow(true, Orientation.Vertical, AutoLayoutAlignment.Start, AutoLayoutAlignment.Start, 100, 100)]
[DataRow(true, Orientation.Vertical, AutoLayoutAlignment.Center, AutoLayoutAlignment.Start, 100, 100)]
[DataRow(true, Orientation.Vertical, AutoLayoutAlignment.End, AutoLayoutAlignment.Start, 100, 100)]
[DataRow(true, Orientation.Horizontal, AutoLayoutAlignment.Start, AutoLayoutAlignment.Center, 25, 100)]
[DataRow(true, Orientation.Horizontal, AutoLayoutAlignment.Center, AutoLayoutAlignment.Center, 25, 100)]
[DataRow(true, Orientation.Horizontal, AutoLayoutAlignment.End, AutoLayoutAlignment.Center, 25, 100)]
[DataRow(true, Orientation.Horizontal, AutoLayoutAlignment.Start, AutoLayoutAlignment.Start, 100, 100)]
[DataRow(true, Orientation.Horizontal, AutoLayoutAlignment.Center, AutoLayoutAlignment.Start, 100, 100)]
[DataRow(true, Orientation.Horizontal, AutoLayoutAlignment.End, AutoLayoutAlignment.Start, 100, 100)]

// Issue with TransformToVisual not having the same result in iOS and Android and WinIU uno issue #11774
//https://github.com/unoplatform/uno/issues/11774
#if !(__IOS__ || __ANDROID__)
[DataRow(false, Orientation.Vertical, AutoLayoutAlignment.Start, AutoLayoutAlignment.End, 100, -50)]
[DataRow(false, Orientation.Vertical, AutoLayoutAlignment.Center, AutoLayoutAlignment.End, 25, -50)]
[DataRow(false, Orientation.Vertical, AutoLayoutAlignment.End, AutoLayoutAlignment.End, -50, -50)]
[DataRow(false, Orientation.Vertical, AutoLayoutAlignment.End, AutoLayoutAlignment.Center, -50, 25)]
[DataRow(false, Orientation.Vertical, AutoLayoutAlignment.End, AutoLayoutAlignment.Start, -50, 100)]
[DataRow(false, Orientation.Horizontal, AutoLayoutAlignment.Center, AutoLayoutAlignment.End, -50, 25)]
[DataRow(false, Orientation.Horizontal, AutoLayoutAlignment.End, AutoLayoutAlignment.Start, 100, -50)]
[DataRow(false, Orientation.Horizontal, AutoLayoutAlignment.End, AutoLayoutAlignment.Center, 25, -50)]
[DataRow(false, Orientation.Horizontal, AutoLayoutAlignment.End, AutoLayoutAlignment.End, -50, -50)]
[DataRow(false, Orientation.Horizontal, AutoLayoutAlignment.Start, AutoLayoutAlignment.End, -50, 100)]
[DataRow(true, Orientation.Vertical, AutoLayoutAlignment.Start, AutoLayoutAlignment.End, 100, -50)]
[DataRow(true, Orientation.Vertical, AutoLayoutAlignment.Center, AutoLayoutAlignment.End, 100, -50)]
[DataRow(true, Orientation.Vertical, AutoLayoutAlignment.End, AutoLayoutAlignment.End, 100, -50)]
[DataRow(true, Orientation.Horizontal, AutoLayoutAlignment.Start, AutoLayoutAlignment.End, -50, 100)]
[DataRow(true, Orientation.Horizontal, AutoLayoutAlignment.Center, AutoLayoutAlignment.End, -50, 100)]
[DataRow(true, Orientation.Horizontal, AutoLayoutAlignment.End, AutoLayoutAlignment.End, -50, 100)]
#endif

public async Task When_Padding_CounterAxis(bool isStretch, Orientation orientation, AutoLayoutAlignment primaryAxisAlignment, AutoLayoutAlignment counterAlignment, double rec1expected, double rec2expected)
{
var SUT = new AutoLayout()
{
Orientation = orientation,
Background = new SolidColorBrush(Color.FromArgb(255, 0, 0, 0)),
Padding = new Thickness(100),
Width = 400,
Height = 400,
PrimaryAxisAlignment = primaryAxisAlignment,
CounterAxisAlignment = counterAlignment,
};
var border1 = new Border()
{
Background = new SolidColorBrush(Color.FromArgb(255, 0, 0, 255)),
Width = orientation is Orientation.Horizontal && isStretch ? double.NaN : 350,
Height = orientation is Orientation.Vertical && isStretch ? double.NaN : 350,
};

if (isStretch)
{
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));

Assert.AreEqual(rec1expected, border1Transform!.Y);
Assert.AreEqual(rec2expected, border1Transform!.X);
}

[TestMethod]
[RequiresFullWindow]
[DataRow(Orientation.Vertical, 10, 340, 280)]
Expand Down Expand Up @@ -590,6 +668,7 @@ public async Task When_Hug_With_Padding()
var autolayout = new AutoLayout()
{
PrimaryAxisAlignment = AutoLayoutAlignment.Center,
Background = new SolidColorBrush(Color.FromArgb(255, 0, 255, 255)),
};

var textBlock = new TextBlock()
Expand All @@ -612,4 +691,42 @@ public async Task When_Hug_With_Padding()

Assert.AreEqual(Math.Ceiling(autoLayoutAcutalWidth - textBlockCenter), Math.Ceiling(textBlockTransform!.X));
}

[TestMethod]
[RequiresFullWindow]
public async Task When_Hug_With_Padding_CounterAxis()
{
var SUT = new AutoLayout()
{
PrimaryAxisAlignment = AutoLayoutAlignment.Center,
CounterAxisAlignment = AutoLayoutAlignment.Center,
Background = new SolidColorBrush(Color.FromArgb(255, 0, 0, 255)),
Width = 300,
Height = 300,
Padding = new Thickness(50)
};

var autolayout = new AutoLayout()
{
PrimaryAxisAlignment = AutoLayoutAlignment.Center,
Background = new SolidColorBrush(Color.FromArgb(255, 0, 255, 255)),
};

var textBlock = new TextBlock()
{
Text = "should be center",
};

SUT.Children.Add(autolayout);
autolayout.Children.Add(textBlock);

await UnitTestUIContentHelperEx.SetContentAndWait(SUT);

var textBlockTransform = textBlock.TransformToVisual(SUT).TransformPoint(new Windows.Foundation.Point(0, 0));

var autoLayoutAcutalWidth = SUT.ActualWidth / 2;
var textBlockCenter = textBlock.ActualWidth / 2;

Assert.AreEqual(Math.Ceiling(autoLayoutAcutalWidth - textBlockCenter), Math.Ceiling(textBlockTransform!.X));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,13 @@ protected override Size ArrangeOverride(Size finalSize)
EnsureZeroFloor(ref childLength);

// Calculate the position of the child by applying the alignment instructions
var counterAlignment = GetCounterAlignment(child.Element);
var isStretch = counterAlignment is AutoLayoutAlignment.Stretch || (child.Element is FrameworkElement fe && fe.GetCounterLength(orientation) is double.NaN) && child.Role is not AutoLayoutRole.Hug;
var counterA = child.Element.ReadLocalValue(CounterAlignmentProperty);
var isCounterAlignemntDefined = counterA != DependencyProperty.UnsetValue;

var childCounterAlignment = GetCounterAlignment(child.Element);
var isStretch = childCounterAlignment is AutoLayoutAlignment.Stretch;
var useCounterAxisAlignement = isStretch || !isCounterAlignemntDefined;
var counterAlignment = useCounterAxisAlignement ? this.CounterAxisAlignment : childCounterAlignment;
var haveCounterStartPadding = isStretch || counterAlignment is AutoLayoutAlignment.Start;
var counterStartPadding = haveCounterStartPadding ? (isHorizontal ? padding.Top : padding.Left) : 0;

Expand Down Expand Up @@ -287,7 +292,7 @@ private static void ApplyMinMaxValues(UIElement element, Orientation orientation

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double ComputeCounterAlignmentOffset(
AutoLayoutAlignment counterAlignment,
AutoLayoutAlignment? counterAlignment,
double childCounterLength,
double availableCounterLength,
Thickness padding,
Expand All @@ -301,9 +306,10 @@ private static double ComputeCounterAlignmentOffset(
var alignmentOffsetSize = availableCounterLength - (counterStartPadding + counterEndPadding);
var relativeOffset = Math.Abs(alignmentOffsetSize) / 2;

return alignmentOffsetSize > 0 ?
var test = alignmentOffsetSize > 0 ?
(relativeOffset + counterStartPadding) - (childCounterLength / 2) :
relativeOffset + (availableCounterLength - counterEndPadding) - (childCounterLength / 2);
return test;
case AutoLayoutAlignment.End:
return availableCounterLength - childCounterLength;
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,8 @@ private void MeasureFixedChildren(
fixedSize, // The available size for the child is its defined fixed size
availableCounterSize,
ref desiredCounterSize,
counterPaddingSize);
counterPaddingSize,
CounterAxisAlignment);

Decrement(ref remainingSize, fixedSize);
}
Expand Down Expand Up @@ -255,7 +256,8 @@ private void MeasureHugChildren(
double.PositiveInfinity, // We don't want the child to limit its own desired size to available one
availableCounterSize,
ref desiredCounterSize,
counterPaddingSize);
counterPaddingSize,
CounterAxisAlignment);

calculatedChild.MeasuredLength = desiredSize;

Expand Down Expand Up @@ -305,7 +307,7 @@ private bool MeasureFilledChildren(
continue;
}

MeasureChild(child.Element, orientation, filledSize, availableCounterSize, ref desiredCounterSize, counterPaddingSize);
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;
}
Expand All @@ -319,15 +321,18 @@ private static double MeasureChild(
double availableSize,
double availableCounterSize,
ref double desiredCounterSize,
double counterPaddingSize)
double counterPaddingSize,
AutoLayoutAlignment counterAxisAlignment)
{
var isOrientationHorizontal = orientation is Orientation.Horizontal;
var isPrimaryAlignmentStretch = GetPrimaryAlignment(child) is AutoLayoutPrimaryAlignment.Stretch;
var isCounterAlignmentStretch = GetCounterAlignment(child) is AutoLayoutAlignment.Stretch;

if (child as FrameworkElement is { } frameworkElement)
{
UpdateCounterAlignment(ref frameworkElement, isOrientationHorizontal, isPrimaryAlignmentStretch, GetCounterAlignment(child));
var test = frameworkElement.ReadLocalValue(CounterAlignmentProperty) == DependencyProperty.UnsetValue ? counterAxisAlignment : GetCounterAlignment(child);

UpdateCounterAlignment(ref frameworkElement, isOrientationHorizontal, isPrimaryAlignmentStretch, test);

var isStretch = isOrientationHorizontal ?
frameworkElement.VerticalAlignment is VerticalAlignment.Stretch :
Expand Down
23 changes: 18 additions & 5 deletions src/Uno.Toolkit.UI/Controls/AutoLayout/AutoLayout.Properties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,19 @@ public AutoLayoutAlignment PrimaryAxisAlignment
set => SetValue(PrimaryAxisAlignmentProperty, value);
}

// -- PrimaryAxisAlignment DependencyProperty --
public static readonly DependencyProperty CounterAxisAlignmentProperty = DependencyProperty.Register(
nameof(CounterAxisAlignment),
typeof(AutoLayoutAlignment),
typeof(AutoLayout),
new PropertyMetadata(default(AutoLayoutAlignment), propertyChangedCallback: InvalidateArrangeCallback));

public AutoLayoutAlignment CounterAxisAlignment
{
get => (AutoLayoutAlignment)GetValue(CounterAxisAlignmentProperty);
set => SetValue(CounterAxisAlignmentProperty, value);
}

// -- PrimaryAlignment Attached Property --
[DynamicDependency(nameof(GetPrimaryAlignment))]
public static readonly DependencyProperty PrimaryAlignmentProperty = DependencyProperty.RegisterAttached(
Expand All @@ -98,20 +111,20 @@ public static AutoLayoutPrimaryAlignment GetPrimaryAlignment(DependencyObject el
}

// -- CounterAlignment Attached Property --
[DynamicDependency(nameof(GetCounterAlignment))]
//[DynamicDependency(nameof(GetCounterAlignment))]
public static readonly DependencyProperty CounterAlignmentProperty = DependencyProperty.RegisterAttached(
"CounterAlignment",
"CounterAlignment",
typeof(AutoLayoutAlignment),
typeof(AutoLayout),
new PropertyMetadata(AutoLayoutAlignment.Stretch, propertyChangedCallback: InvalidateArrangeCallback));
new PropertyMetadata(default(AutoLayoutAlignment), propertyChangedCallback: InvalidateArrangeCallback));

[DynamicDependency(nameof(GetCounterAlignment))]
//[DynamicDependency(nameof(GetCounterAlignment))]
public static void SetCounterAlignment(DependencyObject element, AutoLayoutAlignment value)
{
element.SetValue(CounterAlignmentProperty, value);
}

[DynamicDependency(nameof(SetCounterAlignment))]
//[DynamicDependency(nameof(SetCounterAlignment))]
public static AutoLayoutAlignment GetCounterAlignment(DependencyObject element)
{
return (AutoLayoutAlignment)element.GetValue(CounterAlignmentProperty);
Expand Down
Loading