diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Assets/Samples/RangeSelector.png b/Microsoft.Toolkit.Uwp.SampleApp/Assets/Samples/RangeSelector.png deleted file mode 100644 index 6a95090eb34..00000000000 Binary files a/Microsoft.Toolkit.Uwp.SampleApp/Assets/Samples/RangeSelector.png and /dev/null differ diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Assets/checker.png b/Microsoft.Toolkit.Uwp.SampleApp/Assets/checker.png new file mode 100644 index 00000000000..739d14008bb Binary files /dev/null and b/Microsoft.Toolkit.Uwp.SampleApp/Assets/checker.png differ diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Assets/pixel.png b/Microsoft.Toolkit.Uwp.SampleApp/Assets/pixel.png deleted file mode 100644 index bf1b3a6bb6c..00000000000 Binary files a/Microsoft.Toolkit.Uwp.SampleApp/Assets/pixel.png and /dev/null differ diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj index 458053823c2..07b878addb8 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj +++ b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj @@ -1,4 +1,4 @@ - + Debug @@ -130,6 +130,7 @@ + @@ -271,6 +272,7 @@ + @@ -621,6 +623,9 @@ + + Designer + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Primitives/ConstrainedBox.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Primitives/ConstrainedBox.bind new file mode 100644 index 00000000000..3976530dc13 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Primitives/ConstrainedBox.bind @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Primitives/ConstrainedBox.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Primitives/ConstrainedBox.png new file mode 100644 index 00000000000..ca0f21f55f8 Binary files /dev/null and b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Primitives/ConstrainedBox.png differ diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml index eeff874174e..631cf5e5c95 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml @@ -47,6 +47,7 @@ + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json index 7d07e12fd9c..b07fedab76b 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json @@ -475,6 +475,15 @@ "XamlCodeFile": "/SamplePages/Primitives/SwitchPresenter.bind", "Icon": "/SamplePages/Primitives/SwitchPresenter.png", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/SwitchPresenter.md" + }, + { + "Name": "ConstrainedBox", + "Subcategory": "Layout", + "About": "The ConstrainedBox is a FrameworkElement which can allow a developer to constrain the scale, multiple of, or aspect ratio of its content.", + "CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/tree/main/Microsoft.Toolkit.Uwp.UI.Controls.Primitives/ConstrainedBox", + "XamlCodeFile": "/SamplePages/Primitives/ConstrainedBox.bind", + "Icon": "/SamplePages/Primitives/ConstrainedBox.png", + "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/ConstrainedBox.md" } ] }, @@ -1259,4 +1268,4 @@ } ] } -] \ No newline at end of file +] diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Primitives/ConstrainedBox/AspectRatio.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Primitives/ConstrainedBox/AspectRatio.cs new file mode 100644 index 00000000000..4475a247abf --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Primitives/ConstrainedBox/AspectRatio.cs @@ -0,0 +1,94 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// The structure is used by the control to + /// define a specific ratio to restrict its content. + /// + [Windows.Foundation.Metadata.CreateFromString(MethodName = "Microsoft.Toolkit.Uwp.UI.Controls.AspectRatio.ConvertToAspectRatio")] + public readonly struct AspectRatio + { + /// + /// Gets the width component of the aspect ratio or the aspect ratio itself (and height will be 1). + /// + public double Width { get; } + + /// + /// Gets the height component of the aspect ratio. + /// + public double Height { get; } + + /// + /// Gets the raw numeriucal aspect ratio value itself (Width / Height). + /// + public double Value => Width / Height; + + /// + /// Initializes a new instance of the struct with the provided width and height. + /// + /// Width side of the ratio. + /// Height side of the ratio. + public AspectRatio(double width, double height) + { + Width = width; + Height = height; + } + + /// + /// Initializes a new instance of the struct with the specific numerical aspect ratio. + /// + /// Raw Aspect Ratio, Height will be 1. + public AspectRatio(double ratio) + { + Width = ratio; + Height = 1; + } + + /// + /// Implicit conversion operator to convert an to a value. + /// This lets you use them easily in mathmatical expressions. + /// + /// instance. + public static implicit operator double(AspectRatio aspect) => aspect.Value; + + /// + /// Implicit conversion operator to convert a to an value. + /// This allows for x:Bind to bind to a double value. + /// + /// value representing the . + public static implicit operator AspectRatio(double ratio) => new AspectRatio(ratio); + + /// + /// Converter to take a string aspect ration like "16:9" and convert it to an struct. + /// Used automatically by XAML. + /// + /// The string to be converted in format "Width:Height" or a decimal value. + /// The struct representing that ratio. + public static AspectRatio ConvertToAspectRatio(string rawString) + { + string[] ratio = rawString.Split(":"); + + if (ratio.Length == 2) + { + return new AspectRatio(Convert.ToDouble(ratio[0]), Convert.ToDouble(ratio[1])); + } + else if (ratio.Length == 1) + { + return new AspectRatio(Convert.ToDouble(ratio[0])); + } + + return new AspectRatio(1); + } + + /// + public override string ToString() + { + return Width + ":" + Height; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Primitives/ConstrainedBox/ConstrainedBox.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Primitives/ConstrainedBox/ConstrainedBox.Properties.cs new file mode 100644 index 00000000000..f9d2f3c0584 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Primitives/ConstrainedBox/ConstrainedBox.Properties.cs @@ -0,0 +1,104 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Windows.Foundation; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Dependency properties for the class. + /// + public partial class ConstrainedBox + { + /// + /// Gets or sets the scale for the width of the panel. Should be a value between 0-1.0. Default is 1.0. + /// + public double ScaleX + { + get { return (double)GetValue(ScaleXProperty); } + set { SetValue(ScaleXProperty, value); } + } + + /// + /// Identifies the property. + /// + public static readonly DependencyProperty ScaleXProperty = + DependencyProperty.Register(nameof(ScaleX), typeof(double), typeof(ConstrainedBox), new PropertyMetadata(1.0, ConstraintPropertyChanged)); + + /// + /// Gets or sets the scale for the height of the panel. Should be a value between 0-1.0. Default is 1.0. + /// + public double ScaleY + { + get { return (double)GetValue(ScaleYProperty); } + set { SetValue(ScaleYProperty, value); } + } + + /// + /// Identifies the property. + /// + public static readonly DependencyProperty ScaleYProperty = + DependencyProperty.Register(nameof(ScaleY), typeof(double), typeof(ConstrainedBox), new PropertyMetadata(1.0, ConstraintPropertyChanged)); + + /// + /// Gets or sets the integer multiple that the width of the panel should be floored to. Default is null (no snap). + /// + public int MultipleX + { + get { return (int)GetValue(MultipleXProperty); } + set { SetValue(MultipleXProperty, value); } + } + + /// + /// Identifies the property. + /// + public static readonly DependencyProperty MultipleXProperty = + DependencyProperty.Register(nameof(MultipleX), typeof(int), typeof(ConstrainedBox), new PropertyMetadata(null)); + + /// + /// Gets or sets the integer multiple that the height of the panel should be floored to. Default is null (no snap). + /// + public int MultipleY + { + get { return (int)GetValue(MultipleYProperty); } + set { SetValue(MultipleYProperty, value); } + } + + /// + /// Identifies the property. + /// + public static readonly DependencyProperty MultipleYProperty = + DependencyProperty.Register(nameof(MultipleY), typeof(int), typeof(ConstrainedBox), new PropertyMetadata(null)); + + /// + /// Gets or sets aspect Ratio to use for the contents of the Panel (after scaling). + /// + public AspectRatio AspectRatio + { + get { return (AspectRatio)GetValue(AspectRatioProperty); } + set { SetValue(AspectRatioProperty, value); } + } + + /// + /// Identifies the property. + /// + public static readonly DependencyProperty AspectRatioProperty = + DependencyProperty.Register(nameof(AspectRatio), typeof(AspectRatio), typeof(ConstrainedBox), new PropertyMetadata(null, ConstraintPropertyChanged)); + + private static void ConstraintPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is ConstrainedBox panel) + { + panel.InvalidateMeasure(); + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Primitives/ConstrainedBox/ConstrainedBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Primitives/ConstrainedBox/ConstrainedBox.cs new file mode 100644 index 00000000000..c5c6dc9c48e --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Primitives/ConstrainedBox/ConstrainedBox.cs @@ -0,0 +1,174 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Windows.Foundation; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// The is a control akin to + /// which can modify the behavior of it's child element's layout. restricts the + /// available size for its content based on a scale factor, multiple factor, and/or a specific , in that order. + /// This is performed as a layout calculation modification. + /// + /// + /// Note that this class being implemented as a is an implementation detail, and + /// is not meant to be used as one with a template. It is recommended to avoid styling the frame of the control + /// with borders and not using for future compatibility of your + /// code if moving to WinUI 3 in the future. + /// + public partial class ConstrainedBox : ContentPresenter // TODO: Should be FrameworkElement directly, see https://github.com/microsoft/microsoft-ui-xaml/issues/5530 + { + //// Value used to determine when we re-calculate in the arrange step or re-use a previous calculation. Within roughly a pixel seems like a good value? + private const double CalculationTolerance = 1.5; + + private Size _originalSize; + private Size _lastMeasuredSize; + + private bool IsPositiveRealNumber(double value) => !double.IsNaN(value) && !double.IsInfinity(value) && value > 0; + + /// + protected override Size MeasureOverride(Size availableSize) + { + _originalSize = availableSize; + + CalculateConstrainedSize(ref availableSize); + + _lastMeasuredSize = availableSize; + + // Call base.MeasureOverride so any child elements know what room there is to work with. + // Don't return this though. An image that hasn't loaded yet for example will request very little space. + base.MeasureOverride(_lastMeasuredSize); + return _lastMeasuredSize; + } + + //// Our Arrange pass should just use the value we calculated in Measure, so we don't have extra work to do (at least the ContentPresenter we use presently does it for us.) + + /// + protected override Size ArrangeOverride(Size finalSize) + { + // Even though we requested in measure to be a specific size, that doesn't mean our parent + // panel respected that request. Grid for instance can by default Stretch and if you don't + // set Horizontal/VerticalAlignment on the control it won't constrain as we expect. + // We could also be in a StackPanel/ScrollViewer where it wants to provide as much space as possible. + // However, if we always re-calculate even if we are provided the proper finalSize, this can trigger + // multiple arrange passes and cause a rounding error in layout. Therefore, we only want to + // re-calculate if we think we will have a significant impact. + if (Math.Abs(finalSize.Width - _lastMeasuredSize.Width) > CalculationTolerance || + Math.Abs(finalSize.Height - _lastMeasuredSize.Height) > CalculationTolerance) + { + // Check if we can re-use our measure calculation if we're given effectively + // the same size as we had in the measure step. + if (Math.Abs(finalSize.Width - _originalSize.Width) <= CalculationTolerance && + Math.Abs(finalSize.Height - _originalSize.Height) <= CalculationTolerance) + { + finalSize = _lastMeasuredSize; + } + else + { + CalculateConstrainedSize(ref finalSize); + + // Copy again so if Arrange is re-triggered we won't re-re-calculate. + _lastMeasuredSize = finalSize; + } + } + + return base.ArrangeOverride(finalSize); + } + + private void CalculateConstrainedSize(ref Size availableSize) + { + // 1) We check for Infinity, in the case we have no constraint from parent + // we'll request the child's measurements first, so we can use that as + // a starting point to constrain it's dimensions based on the criteria + // set in our properties. + var hasWidth = IsPositiveRealNumber(availableSize.Width); + var hasHeight = IsPositiveRealNumber(availableSize.Height); + + if (!hasWidth && !hasHeight) + { + // We have infinite space, like a ScrollViewer with both scrolling directions + // Ask child how big they want to be first. + availableSize = base.MeasureOverride(availableSize); + + hasWidth = IsPositiveRealNumber(availableSize.Width); + hasHeight = IsPositiveRealNumber(availableSize.Height); + + if (!hasWidth && !hasHeight) + { + // At this point we have no way to determine a constraint, the Panel won't do anything + // This should be rare? We don't really have a way to provide a warning here. + return; + } + } + + // 2) Apply Scales to constrain based on a percentage + // -------------------------------------------------- + availableSize.Width *= ScaleX; + availableSize.Height *= ScaleY; + + // 3) Apply Multiples + // ------------------ + // These floor the Width/Height values to the nearest multiple of the property (if set). + // For instance you may have a responsive 4x4 repeated checkerboard pattern for transparency and + // want to snap to the nearest interval of 4 so the checkerboard is consistency across the layout. + if (hasWidth && + ReadLocalValue(MultipleXProperty) != DependencyProperty.UnsetValue && + MultipleX > 0) + { + availableSize.Width -= availableSize.Width % MultipleX; + } + + if (hasHeight && + ReadLocalValue(MultipleYProperty) != DependencyProperty.UnsetValue && + MultipleY > 0) + { + availableSize.Height -= availableSize.Height % MultipleY; + } + + // 4) Apply AspectRatio + // -------------------- + // Finally, we apply the AspectRatio property after we've determined the general + // area we have to work with based on the other constraints. + // Devs should be careful if they use both a MultipleX&Y that the AspectRatio is also + // within that same ratio. The Ratio will take preference here as the last step. + if (ReadLocalValue(AspectRatioProperty) == DependencyProperty.UnsetValue) + { + // Skip as last constraint if we have nothing to do. + return; + } + + // Calculate the Aspect Ratio constraint based on the newly scaled size. + var currentAspect = availableSize.Width / availableSize.Height; + + if (!hasWidth) + { + // If available width is infinite, set width based on height + availableSize.Width = availableSize.Height * AspectRatio; + } + else if (!hasHeight) + { + // If avalable height is infinite, set height based on width + availableSize.Height = availableSize.Width / AspectRatio; + } + else if (currentAspect > AspectRatio) + { + // If the container aspect ratio is wider than our aspect ratio, set width based on height + availableSize.Width = availableSize.Height * AspectRatio; + } + else + { + // If the container aspect ratio is taller than our aspect ratio, set height based on width + availableSize.Height = availableSize.Width / AspectRatio; + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Primitives/SwitchPresenter/SwitchPresenter.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Primitives/SwitchPresenter/SwitchPresenter.cs index 036d5a6638a..db6a3e9782d 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Primitives/SwitchPresenter/SwitchPresenter.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Primitives/SwitchPresenter/SwitchPresenter.cs @@ -28,7 +28,7 @@ public Case CurrentCase } /// - /// Indicates the property. + /// Identifies the property. /// public static readonly DependencyProperty CurrentCaseProperty = DependencyProperty.Register(nameof(CurrentCase), typeof(Case), typeof(SwitchPresenter), new PropertyMetadata(null)); @@ -43,7 +43,7 @@ public CaseCollection SwitchCases } /// - /// Indicates the property. + /// Identifies the property. /// public static readonly DependencyProperty SwitchCasesProperty = DependencyProperty.Register(nameof(SwitchCases), typeof(CaseCollection), typeof(SwitchPresenter), new PropertyMetadata(null, OnSwitchCasesPropertyChanged)); @@ -58,7 +58,7 @@ public object Value } /// - /// Indicates the property. + /// Identifies the property. /// public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(nameof(Value), typeof(object), typeof(SwitchPresenter), new PropertyMetadata(null, OnValuePropertyChanged)); @@ -73,7 +73,7 @@ public Type TargetType } /// - /// Indicates the property. + /// Identifies the property. /// public static readonly DependencyProperty TargetTypeProperty = DependencyProperty.Register(nameof(TargetType), typeof(Type), typeof(SwitchPresenter), new PropertyMetadata(null)); diff --git a/Microsoft.Toolkit.Uwp.UI/Extensions/FrameworkElement/FrameworkElementExtensions.LogicalTree.cs b/Microsoft.Toolkit.Uwp.UI/Extensions/FrameworkElement/FrameworkElementExtensions.LogicalTree.cs index 07c27b51012..71a766e1f77 100644 --- a/Microsoft.Toolkit.Uwp.UI/Extensions/FrameworkElement/FrameworkElementExtensions.LogicalTree.cs +++ b/Microsoft.Toolkit.Uwp.UI/Extensions/FrameworkElement/FrameworkElementExtensions.LogicalTree.cs @@ -196,6 +196,20 @@ public static partial class FrameworkElementExtensions goto Start; } } + else if (element is Viewbox viewbox) + { + if (viewbox.Child is FrameworkElement child) + { + if (child is T result && predicate.Match(result)) + { + return result; + } + + element = child; + + goto Start; + } + } else if (element is UserControl userControl) { // We put UserControl right before the slower reflection fallback path as @@ -398,6 +412,17 @@ public static IEnumerable FindChildren(this FrameworkElement e goto Start; } } + else if (element is Viewbox viewbox) + { + if (viewbox.Child is FrameworkElement child) + { + yield return child; + + element = child; + + goto Start; + } + } else if (element is UserControl userControl) { if (userControl.Content is FrameworkElement content) diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_ConstrainedBox.AspectRatio.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_ConstrainedBox.AspectRatio.cs new file mode 100644 index 00000000000..f5762c97eb0 --- /dev/null +++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_ConstrainedBox.AspectRatio.cs @@ -0,0 +1,96 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Toolkit.Uwp; +using Microsoft.Toolkit.Uwp.UI; +using Microsoft.Toolkit.Uwp.UI.Controls; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer; +using Windows.Foundation; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Markup; + +namespace UnitTests.UWP.UI.Controls +{ + [TestClass] + public partial class Test_ConstrainedBox : VisualUITestBase + { + [TestCategory("ConstrainedBox")] + [TestMethod] + public async Task Test_ConstrainedBox_Normal_AspectHorizontal() + { + await App.DispatcherQueue.EnqueueAsync(async () => + { + var treeRoot = XamlReader.Load(@" + + + +") as FrameworkElement; + + Assert.IsNotNull(treeRoot, "Could not load XAML tree."); + + // Initialize Visual Tree + await SetTestContentAsync(treeRoot); + + var panel = treeRoot.FindChild("ConstrainedBox") as ConstrainedBox; + + Assert.IsNotNull(panel, "Could not find ConstrainedBox in tree."); + + // Force Layout calculations + panel.UpdateLayout(); + + var child = panel.Content as Border; + + Assert.IsNotNull(child, "Could not find inner Border"); + + // Check Size + Assert.AreEqual(200, child.ActualWidth, 0.01, "Actual width does not meet expected value of 200"); + Assert.AreEqual(100, child.ActualHeight, 0.01, "Actual height does not meet expected value of 100"); + }); + } + + [TestCategory("ConstrainedBox")] + [TestMethod] + public async Task Test_ConstrainedBox_Normal_AspectVertical() + { + await App.DispatcherQueue.EnqueueAsync(async () => + { + var treeRoot = XamlReader.Load(@" + + + +") as FrameworkElement; + + Assert.IsNotNull(treeRoot, "Could not load XAML tree."); + + // Initialize Visual Tree + await SetTestContentAsync(treeRoot); + + var panel = treeRoot.FindChild("ConstrainedBox") as ConstrainedBox; + + Assert.IsNotNull(panel, "Could not find ConstrainedBox in tree."); + + // Force Layout calculations + panel.UpdateLayout(); + + var child = panel.Content as Border; + + Assert.IsNotNull(child, "Could not find inner Border"); + + // Check Size + Assert.AreEqual(100, child.ActualWidth, 0.01, "Actual width does not meet expected value of 100"); + Assert.AreEqual(200, child.ActualHeight, 0.01, "Actual height does not meet expected value of 200"); + }); + } + } +} diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_ConstrainedBox.Combined.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_ConstrainedBox.Combined.cs new file mode 100644 index 00000000000..8c5c34f3090 --- /dev/null +++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_ConstrainedBox.Combined.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Toolkit.Uwp; +using Microsoft.Toolkit.Uwp.UI; +using Microsoft.Toolkit.Uwp.UI.Controls; +using Microsoft.Toolkit.Uwp.UI.Helpers; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer; +using Windows.Foundation; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Markup; + +namespace UnitTests.UWP.UI.Controls +{ + public partial class Test_ConstrainedBox : VisualUITestBase + { + [TestCategory("ConstrainedBox")] + [TestMethod] + public async Task Test_ConstrainedBox_Combined_All() + { + await App.DispatcherQueue.EnqueueAsync(async () => + { + // We turn LayoutRounding off as we're doing between pixel calculation here to test. + var treeRoot = XamlReader.Load(@" + + + + + +") as FrameworkElement; + + Assert.IsNotNull(treeRoot, "Could not load XAML tree."); + + // Initialize Visual Tree + await SetTestContentAsync(treeRoot); + + var grid = treeRoot.FindChild("ParentGrid") as Grid; + + var panel = treeRoot.FindChild("ConstrainedBox") as ConstrainedBox; + + Assert.IsNotNull(panel, "Could not find ConstrainedBox in tree."); + + // Force Layout calculations + panel.UpdateLayout(); + + var child = panel.Content as Border; + + Assert.IsNotNull(child, "Could not find inner Border"); + + // Check Size + Assert.AreEqual(64, child.ActualWidth, 0.01, "Actual width does not meet expected value of 64"); + Assert.AreEqual(21.333, child.ActualHeight, 0.01, "Actual height does not meet expected value of 21.33"); + + // Check inner Positioning, we do this from the Grid as the ConstainedBox also modifies its own size + // and is hugging the child. + var position = grid.CoordinatesTo(child); + + Assert.AreEqual(18, position.X, 0.01, "X position does not meet expected value of 18"); + Assert.AreEqual(39.333, position.Y, 0.01, "Y position does not meet expected value of 39.33"); + + // Update Aspect Ratio and Re-check + panel.AspectRatio = new AspectRatio(1, 3); + + // Wait to ensure we've redone layout + await CompositionTargetHelper.ExecuteAfterCompositionRenderingAsync(() => + { + // Check Size + Assert.AreEqual(21.333, child.ActualWidth, 0.01, "Actual width does not meet expected value of 21.33"); + Assert.AreEqual(64, child.ActualHeight, 0.01, "Actual height does not meet expected value of 64"); + + // Check inner Positioning, we do this from the Grid as the ConstainedBox also modifies its own size + // and is hugging the child. + position = grid.CoordinatesTo(child); + + Assert.AreEqual(39.333, position.X, 0.01, "X position does not meet expected value of 39.33"); + Assert.AreEqual(18, position.Y, 0.01, "Y position does not meet expected value of 18"); + }); + }); + } + } +} diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_ConstrainedBox.Multiple.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_ConstrainedBox.Multiple.cs new file mode 100644 index 00000000000..da3250bb180 --- /dev/null +++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_ConstrainedBox.Multiple.cs @@ -0,0 +1,119 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Toolkit.Uwp; +using Microsoft.Toolkit.Uwp.UI; +using Microsoft.Toolkit.Uwp.UI.Controls; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer; +using Windows.Foundation; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Markup; + +namespace UnitTests.UWP.UI.Controls +{ + public partial class Test_ConstrainedBox : VisualUITestBase + { + [TestCategory("ConstrainedBox")] + [TestMethod] + public async Task Test_ConstrainedBox_Normal_MultipleX() + { + await App.DispatcherQueue.EnqueueAsync(async () => + { + var treeRoot = XamlReader.Load(@" + + + + + +") as FrameworkElement; + + Assert.IsNotNull(treeRoot, "Could not load XAML tree."); + + // Initialize Visual Tree + await SetTestContentAsync(treeRoot); + + var grid = treeRoot.FindChild("ParentGrid") as Grid; + + var panel = treeRoot.FindChild("ConstrainedBox") as ConstrainedBox; + + Assert.IsNotNull(panel, "Could not find ConstrainedBox in tree."); + + // Force Layout calculations + panel.UpdateLayout(); + + var child = panel.Content as Border; + + Assert.IsNotNull(child, "Could not find inner Border"); + + // Check Size + Assert.AreEqual(192, child.ActualWidth, 0.01, "Actual width does not meet expected value of 192"); + Assert.AreEqual(200, child.ActualHeight, 0.01, "Actual height does not meet expected value of 200"); + + // Check inner Positioning, we do this from the Grid as the ConstainedBox also modifies its own size + // and is hugging the child. + var position = grid.CoordinatesTo(child); + + Assert.AreEqual(4, position.X, 0.01, "X position does not meet expected value of 4"); + Assert.AreEqual(0, position.Y, 0.01, "Y position does not meet expected value of 0"); + }); + } + + [TestCategory("ConstrainedBox")] + [TestMethod] + public async Task Test_ConstrainedBox_Normal_MultipleY() + { + await App.DispatcherQueue.EnqueueAsync(async () => + { + var treeRoot = XamlReader.Load(@" + + + + + +") as FrameworkElement; + + Assert.IsNotNull(treeRoot, "Could not load XAML tree."); + + // Initialize Visual Tree + await SetTestContentAsync(treeRoot); + + var grid = treeRoot.FindChild("ParentGrid") as Grid; + + var panel = treeRoot.FindChild("ConstrainedBox") as ConstrainedBox; + + Assert.IsNotNull(panel, "Could not find ConstrainedBox in tree."); + + // Force Layout calculations + panel.UpdateLayout(); + + var child = panel.Content as Border; + + Assert.IsNotNull(child, "Could not find inner Border"); + + // Check Size + Assert.AreEqual(200, child.ActualWidth, 0.01, "Actual width does not meet expected value of 200"); + Assert.AreEqual(192, child.ActualHeight, 0.01, "Actual height does not meet expected value of 192"); + + // Check inner Positioning, we do this from the Grid as the ConstainedBox also modifies its own size + // and is hugging the child. + var position = grid.CoordinatesTo(child); + + Assert.AreEqual(0, position.X, 0.01, "X position does not meet expected value of 0"); + Assert.AreEqual(4, position.Y, 0.01, "Y position does not meet expected value of 4"); + }); + } + } +} diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_ConstrainedBox.Scale.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_ConstrainedBox.Scale.cs new file mode 100644 index 00000000000..ebafc231ad0 --- /dev/null +++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_ConstrainedBox.Scale.cs @@ -0,0 +1,119 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Toolkit.Uwp; +using Microsoft.Toolkit.Uwp.UI; +using Microsoft.Toolkit.Uwp.UI.Controls; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer; +using Windows.Foundation; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Markup; + +namespace UnitTests.UWP.UI.Controls +{ + public partial class Test_ConstrainedBox : VisualUITestBase + { + [TestCategory("ConstrainedBox")] + [TestMethod] + public async Task Test_ConstrainedBox_Normal_ScaleX() + { + await App.DispatcherQueue.EnqueueAsync(async () => + { + var treeRoot = XamlReader.Load(@" + + + + + +") as FrameworkElement; + + Assert.IsNotNull(treeRoot, "Could not load XAML tree."); + + // Initialize Visual Tree + await SetTestContentAsync(treeRoot); + + var grid = treeRoot.FindChild("ParentGrid") as Grid; + + var panel = treeRoot.FindChild("ConstrainedBox") as ConstrainedBox; + + Assert.IsNotNull(panel, "Could not find ConstrainedBox in tree."); + + // Force Layout calculations + panel.UpdateLayout(); + + var child = panel.Content as Border; + + Assert.IsNotNull(child, "Could not find inner Border"); + + // Check Size + Assert.AreEqual(100, child.ActualWidth, 0.01, "Actual width does not meet expected value of 100"); + Assert.AreEqual(200, child.ActualHeight, 0.01, "Actual height does not meet expected value of 200"); + + // Check inner Positioning, we do this from the Grid as the ConstainedBox also modifies its own size + // and is hugging the child. + var position = grid.CoordinatesTo(child); + + Assert.AreEqual(50, position.X, 0.01, "X position does not meet expected value of 50"); + Assert.AreEqual(0, position.Y, 0.01, "Y position does not meet expected value of 0"); + }); + } + + [TestCategory("ConstrainedBox")] + [TestMethod] + public async Task Test_ConstrainedBox_Normal_ScaleY() + { + await App.DispatcherQueue.EnqueueAsync(async () => + { + var treeRoot = XamlReader.Load(@" + + + + + +") as FrameworkElement; + + Assert.IsNotNull(treeRoot, "Could not load XAML tree."); + + // Initialize Visual Tree + await SetTestContentAsync(treeRoot); + + var grid = treeRoot.FindChild("ParentGrid") as Grid; + + var panel = treeRoot.FindChild("ConstrainedBox") as ConstrainedBox; + + Assert.IsNotNull(panel, "Could not find ConstrainedBox in tree."); + + // Force Layout calculations + panel.UpdateLayout(); + + var child = panel.Content as Border; + + Assert.IsNotNull(child, "Could not find inner Border"); + + // Check Size + Assert.AreEqual(200, child.ActualWidth, 0.01, "Actual width does not meet expected value of 200"); + Assert.AreEqual(100, child.ActualHeight, 0.01, "Actual height does not meet expected value of 100"); + + // Check inner Positioning, we do this from the Grid as the ConstainedBox also modifies its own size + // and is hugging the child. + var position = grid.CoordinatesTo(child); + + Assert.AreEqual(0, position.X, 0.01, "X position does not meet expected value of 0"); + Assert.AreEqual(50, position.Y, 0.01, "Y position does not meet expected value of 50"); + }); + } + } +} diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_InfiniteCanvas_Regression.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_InfiniteCanvas_Regression.cs index 0543f0ec283..187f9c96048 100644 --- a/UnitTests/UnitTests.UWP/UI/Controls/Test_InfiniteCanvas_Regression.cs +++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_InfiniteCanvas_Regression.cs @@ -11,7 +11,7 @@ using Windows.ApplicationModel.Core; using Windows.UI.Core; -namespace UnitTests.UI.Controls +namespace UnitTests.UWP.UI.Controls { [TestClass] public class Test_InfiniteCanvas_Regression diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_ListDetailsView.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_ListDetailsView.cs index 1e39265b4e8..13f1e938c76 100644 --- a/UnitTests/UnitTests.UWP/UI/Controls/Test_ListDetailsView.cs +++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_ListDetailsView.cs @@ -7,7 +7,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer; using System.Linq; -namespace UnitTests.UI.Controls +namespace UnitTests.UWP.UI.Controls { [TestClass] public class Test_ListDetailsView diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_RangeSelector.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_RangeSelector.cs index 24b06ac2262..89e78434d75 100644 --- a/UnitTests/UnitTests.UWP/UI/Controls/Test_RangeSelector.cs +++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_RangeSelector.cs @@ -7,7 +7,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Threading.Tasks; -namespace UnitTests.UI.Controls +namespace UnitTests.UWP.UI.Controls { [TestClass] public class Test_RangeSelector : VisualUITestBase diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_TextToolbar_Localization.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_TextToolbar_Localization.cs index 9cdcbef80f9..5c0c0523fa3 100644 --- a/UnitTests/UnitTests.UWP/UI/Controls/Test_TextToolbar_Localization.cs +++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_TextToolbar_Localization.cs @@ -15,7 +15,7 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Markup; -namespace UnitTests.UI.Controls +namespace UnitTests.UWP.UI.Controls { [TestClass] public class Test_TextToolbar_Localization diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_General.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_General.cs index 66ddf02c29f..6ea65407d7e 100644 --- a/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_General.cs +++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_General.cs @@ -9,7 +9,7 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Markup; -namespace UnitTests.UI.Controls +namespace UnitTests.UWP.UI.Controls { [TestClass] public class Test_TokenizingTextBox_General diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_InterspersedCollection.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_InterspersedCollection.cs index cd869afaef7..44f6fa5314f 100644 --- a/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_InterspersedCollection.cs +++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_InterspersedCollection.cs @@ -11,7 +11,7 @@ using System.Collections.Specialized; using System.Linq; -namespace UnitTests.UI.Controls +namespace UnitTests.UWP.UI.Controls { [TestClass] public class Test_TokenizingTextBox_InterspersedCollection diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_UniformGrid_AutoLayout.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_UniformGrid_AutoLayout.cs index 7cfbd82b7e5..57df97fc493 100644 --- a/UnitTests/UnitTests.UWP/UI/Controls/Test_UniformGrid_AutoLayout.cs +++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_UniformGrid_AutoLayout.cs @@ -12,7 +12,7 @@ using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Markup; -namespace UnitTests.UI.Controls +namespace UnitTests.UWP.UI.Controls { [TestClass] public class Test_UniformGrid_AutoLayout diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_UniformGrid_Dimensions.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_UniformGrid_Dimensions.cs index e74b55f1905..955491445d2 100644 --- a/UnitTests/UnitTests.UWP/UI/Controls/Test_UniformGrid_Dimensions.cs +++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_UniformGrid_Dimensions.cs @@ -10,7 +10,7 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Markup; -namespace UnitTests.UI.Controls +namespace UnitTests.UWP.UI.Controls { [TestClass] public class Test_UniformGrid_Dimensions diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_UniformGrid_FreeSpots.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_UniformGrid_FreeSpots.cs index 1b8be951a83..86734036c9f 100644 --- a/UnitTests/UnitTests.UWP/UI/Controls/Test_UniformGrid_FreeSpots.cs +++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_UniformGrid_FreeSpots.cs @@ -8,7 +8,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer; -namespace UnitTests.UI.Controls +namespace UnitTests.UWP.UI.Controls { [TestClass] public class Test_UniformGrid_FreeSpots diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_UniformGrid_RowColDefinitions.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_UniformGrid_RowColDefinitions.cs index 700a07ac74f..9cc42cd3b68 100644 --- a/UnitTests/UnitTests.UWP/UI/Controls/Test_UniformGrid_RowColDefinitions.cs +++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_UniformGrid_RowColDefinitions.cs @@ -9,7 +9,7 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Markup; -namespace UnitTests.UI.Controls +namespace UnitTests.UWP.UI.Controls { [TestClass] public class Test_UniformGrid_RowColDefinitions diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_WrapPanel_BasicLayout.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_WrapPanel_BasicLayout.cs index 226a016f0ab..e3e7860b4c0 100644 --- a/UnitTests/UnitTests.UWP/UI/Controls/Test_WrapPanel_BasicLayout.cs +++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_WrapPanel_BasicLayout.cs @@ -14,7 +14,7 @@ using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Markup; -namespace UnitTests.UI.Controls +namespace UnitTests.UWP.UI.Controls { [TestClass] public class Test_WrapPanel_BasicLayout : VisualUITestBase diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_WrapPanel_Visibility.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_WrapPanel_Visibility.cs index 50cd685d54e..c47780f0442 100644 --- a/UnitTests/UnitTests.UWP/UI/Controls/Test_WrapPanel_Visibility.cs +++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_WrapPanel_Visibility.cs @@ -14,7 +14,7 @@ using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Markup; -namespace UnitTests.UI.Controls +namespace UnitTests.UWP.UI.Controls { [TestClass] public class Test_WrapPanel_Visibility : VisualUITestBase diff --git a/UnitTests/UnitTests.UWP/UnitTestApp.xaml b/UnitTests/UnitTests.UWP/UnitTestApp.xaml index dee4f8c1209..87f176b9a0f 100644 --- a/UnitTests/UnitTests.UWP/UnitTestApp.xaml +++ b/UnitTests/UnitTests.UWP/UnitTestApp.xaml @@ -11,7 +11,7 @@ - + Cat @@ -43,7 +43,9 @@ -