From 3c3459aa53f870ea74f02a4e201b66ac681d2d09 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 21 Apr 2021 22:33:53 +0200 Subject: [PATCH 1/4] Added VisualExtensions.Translation property --- .../Extensions/VisualExtensions.cs | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/Microsoft.Toolkit.Uwp.UI/Extensions/VisualExtensions.cs b/Microsoft.Toolkit.Uwp.UI/Extensions/VisualExtensions.cs index c4f2c57b366..0ab01a6d991 100644 --- a/Microsoft.Toolkit.Uwp.UI/Extensions/VisualExtensions.cs +++ b/Microsoft.Toolkit.Uwp.UI/Extensions/VisualExtensions.cs @@ -114,6 +114,36 @@ public static void SetOffset(DependencyObject obj, string value) obj.SetValue(OffsetProperty, value); } + /// + /// Gets the . property of a in a form. + /// + /// The instance. + /// A representation of the . property. + public static string GetTranslation(DependencyObject obj) + { + if (!DesignTimeHelpers.IsRunningInLegacyDesignerMode && obj is UIElement element) + { + return GetTranslationForElement(element); + } + + return (string)obj.GetValue(TranslationProperty); + } + + /// + /// Sets the . property of a in a form. + /// + /// The instance. + /// The representation of the to be set. + public static void SetTranslation(DependencyObject obj, string value) + { + if (!DesignTimeHelpers.IsRunningInLegacyDesignerMode && obj is UIElement element) + { + SetTranslationForElement(value, element); + } + + obj.SetValue(TranslationProperty, value); + } + /// /// Gets the of a UIElement /// @@ -334,6 +364,12 @@ public static void SetNormalizedCenterPoint(DependencyObject obj, string value) public static readonly DependencyProperty OffsetProperty = DependencyProperty.RegisterAttached("Offset", typeof(string), typeof(VisualExtensions), new PropertyMetadata(null, OnOffsetChanged)); + /// + /// Identifies the Translation attached property. + /// + public static readonly DependencyProperty TranslationProperty = + DependencyProperty.RegisterAttached("Translation", typeof(string), typeof(VisualExtensions), new PropertyMetadata(null, OnTranslationChanged)); + /// /// Identifies the Opacity attached property. /// @@ -400,6 +436,14 @@ private static void OnOffsetChanged(DependencyObject d, DependencyPropertyChange } } + private static void OnTranslationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (e.NewValue is string str) + { + SetTranslation(d, str); + } + } + private static void OnOpacityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (e.NewValue is double dbl) @@ -503,6 +547,23 @@ private static void SetOffsetForElement(string value, UIElement element) visual.Offset = value.ToVector3(); } + private static string GetTranslationForElement(UIElement element) + { + return GetVisual(element).TransformMatrix.Translation.ToString(); + } + + private static void SetTranslationForElement(string value, UIElement element) + { + ElementCompositionPreview.SetIsTranslationEnabled(element, true); + + Visual visual = GetVisual(element); + Matrix4x4 transform = visual.TransformMatrix; + + transform.Translation = value.ToVector3(); + + visual.TransformMatrix = transform; + } + private static double GetOpacityForElement(UIElement element) { var visual = GetVisual(element); From 319b7354a0b0ffcf685047c343c7556ede5fc928 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 21 Apr 2021 22:52:51 +0200 Subject: [PATCH 2/4] Added unit test for VisualExtensions.Translation property --- .../UI/Extensions/Test_VisualExtensions.cs | 38 +++++++++++++++++++ UnitTests/UnitTests.UWP/UnitTests.UWP.csproj | 1 + 2 files changed, 39 insertions(+) create mode 100644 UnitTests/UnitTests.UWP/UI/Extensions/Test_VisualExtensions.cs diff --git a/UnitTests/UnitTests.UWP/UI/Extensions/Test_VisualExtensions.cs b/UnitTests/UnitTests.UWP/UI/Extensions/Test_VisualExtensions.cs new file mode 100644 index 00000000000..e09cb7e9332 --- /dev/null +++ b/UnitTests/UnitTests.UWP/UI/Extensions/Test_VisualExtensions.cs @@ -0,0 +1,38 @@ +// 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.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.Toolkit.Uwp; +using Windows.UI.Xaml.Controls; +using Microsoft.Toolkit.Uwp.UI; +using System.Numerics; + +namespace UnitTests.UWP.UI +{ + [TestClass] + [TestCategory("Test_VisualExtensions")] + public class Test_VisualExtensions : VisualUITestBase + { + [TestMethod] + public async Task SetAndGetTranslation() + { + await App.DispatcherQueue.EnqueueAsync(async () => + { + var button = new Button(); + var grid = new Grid() { Children = { button } }; + + VisualExtensions.SetTranslation(button, "80, 20, 0"); + + await SetTestContentAsync(grid); + + Assert.AreEqual(button.GetVisual().TransformMatrix.Translation, new Vector3(80, 20, 0)); + + string translation = VisualExtensions.GetTranslation(button); + + Assert.AreEqual(translation, new Vector3(80, 20, 0).ToString()); + }); + } + } +} \ No newline at end of file diff --git a/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj b/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj index ff55da9ad64..d03e8e97983 100644 --- a/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj +++ b/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj @@ -211,6 +211,7 @@ + From c47b47bba5a733cbea1e51c31f7ec2abd21199fb Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 22 Apr 2021 13:36:19 +0200 Subject: [PATCH 3/4] Fixed VisualExtensions.Translation underlying target property --- .../Extensions/VisualExtensions.cs | 37 ++++++++++---- .../UI/Extensions/Test_VisualExtensions.cs | 51 +++++++++++++++++-- 2 files changed, 74 insertions(+), 14 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI/Extensions/VisualExtensions.cs b/Microsoft.Toolkit.Uwp.UI/Extensions/VisualExtensions.cs index 0ab01a6d991..cf1ddb360e6 100644 --- a/Microsoft.Toolkit.Uwp.UI/Extensions/VisualExtensions.cs +++ b/Microsoft.Toolkit.Uwp.UI/Extensions/VisualExtensions.cs @@ -2,6 +2,7 @@ // 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.Globalization; using System.Numerics; using Windows.UI.Composition; using Windows.UI.Xaml; @@ -115,10 +116,10 @@ public static void SetOffset(DependencyObject obj, string value) } /// - /// Gets the . property of a in a form. + /// Gets the "Translation" property of the underlying object for a , in form. /// /// The instance. - /// A representation of the . property. + /// The representation of the "Translation" property property. public static string GetTranslation(DependencyObject obj) { if (!DesignTimeHelpers.IsRunningInLegacyDesignerMode && obj is UIElement element) @@ -130,10 +131,10 @@ public static string GetTranslation(DependencyObject obj) } /// - /// Sets the . property of a in a form. + /// Sets the "Translation" property of the underlying object for a , in form. /// /// The instance. - /// The representation of the to be set. + /// The representation of the "Translation" property property to be set. public static void SetTranslation(DependencyObject obj, string value) { if (!DesignTimeHelpers.IsRunningInLegacyDesignerMode && obj is UIElement element) @@ -549,19 +550,33 @@ private static void SetOffsetForElement(string value, UIElement element) private static string GetTranslationForElement(UIElement element) { - return GetVisual(element).TransformMatrix.Translation.ToString(); + CompositionGetValueStatus result = GetVisual(element).Properties.TryGetVector3("Translation", out Vector3 translation); + + return result switch + { + // The ("G", CultureInfo.InvariantCulture) combination produces a string with the default numeric + // formatting style, and using ',' as component separator, so that the resulting text can safely + // be parsed back if needed with the StringExtensions.ToVector3(string) extension, which uses + // the invariant culture mode by default so that the syntax will always match that from XAML. + CompositionGetValueStatus.Succeeded => translation.ToString("G", CultureInfo.InvariantCulture), + _ => "<0, 0, 0>" + }; } private static void SetTranslationForElement(string value, UIElement element) { ElementCompositionPreview.SetIsTranslationEnabled(element, true); - Visual visual = GetVisual(element); - Matrix4x4 transform = visual.TransformMatrix; - - transform.Translation = value.ToVector3(); - - visual.TransformMatrix = transform; + // The "Translation" attached property refers to the "hidden" property that is enabled + // through "ElementCompositionPreview.SetIsTranslationEnabled". The value for this property + // is not available directly on the Visual class and can only be accessed through its property + // set. Note that this "Translation" value is not the same as Visual.TransformMatrix.Translation. + // In fact, the latter doesn't require to be explicitly enabled and is actually combined with + // this at runtime (ie. the whole transform matrix is combined with the additional translation + // from the "Translation" property, if any), and the two can be set and animated independently. + // In this case we're just interested in the "Translation" property, which is more commonly used + // as it can also be animated directly with a Vector3 animation instead of a Matrix4x4 one. + GetVisual(element).Properties.InsertVector3("Translation", value.ToVector3()); } private static double GetOpacityForElement(UIElement element) diff --git a/UnitTests/UnitTests.UWP/UI/Extensions/Test_VisualExtensions.cs b/UnitTests/UnitTests.UWP/UI/Extensions/Test_VisualExtensions.cs index e09cb7e9332..244cfaba0b3 100644 --- a/UnitTests/UnitTests.UWP/UI/Extensions/Test_VisualExtensions.cs +++ b/UnitTests/UnitTests.UWP/UI/Extensions/Test_VisualExtensions.cs @@ -8,6 +8,8 @@ using Windows.UI.Xaml.Controls; using Microsoft.Toolkit.Uwp.UI; using System.Numerics; +using Windows.UI.Composition; +using Microsoft.Toolkit.Uwp.UI.Animations; namespace UnitTests.UWP.UI { @@ -15,6 +17,19 @@ namespace UnitTests.UWP.UI [TestCategory("Test_VisualExtensions")] public class Test_VisualExtensions : VisualUITestBase { + [TestMethod] + public async Task GetDefaultTranslation() + { + await App.DispatcherQueue.EnqueueAsync(() => + { + var button = new Button(); + + string text = VisualExtensions.GetTranslation(button); + + Assert.AreEqual(text, "<0, 0, 0>"); + }); + } + [TestMethod] public async Task SetAndGetTranslation() { @@ -27,11 +42,41 @@ await App.DispatcherQueue.EnqueueAsync(async () => await SetTestContentAsync(grid); - Assert.AreEqual(button.GetVisual().TransformMatrix.Translation, new Vector3(80, 20, 0)); + var success = button.GetVisual().Properties.TryGetVector3("Translation", out Vector3 translation); + + Assert.AreEqual(success, CompositionGetValueStatus.Succeeded); + Assert.AreEqual(translation, new Vector3(80, 20, 0)); + + string text = VisualExtensions.GetTranslation(button); + + Assert.AreEqual(text, new Vector3(80, 20, 0).ToString()); + }); + } + + [TestMethod] + public async Task SetAndAnimateTranslation() + { + await App.DispatcherQueue.EnqueueAsync(async () => + { + var button = new Button(); + var grid = new Grid() { Children = { button } }; + + VisualExtensions.SetTranslation(button, "80, 20, 0"); + + await SetTestContentAsync(grid); + + await AnimationBuilder.Create() + .Translation(to: new Vector3(11, 22, 0)) + .StartAsync(button); + + var success = button.GetVisual().Properties.TryGetVector3("Translation", out Vector3 translation); + + Assert.AreEqual(success, CompositionGetValueStatus.Succeeded); + Assert.AreEqual(translation, new Vector3(11, 22, 0)); - string translation = VisualExtensions.GetTranslation(button); + string text = VisualExtensions.GetTranslation(button); - Assert.AreEqual(translation, new Vector3(80, 20, 0).ToString()); + Assert.AreEqual(text, new Vector3(11, 22, 0).ToString()); }); } } From 129ab97322256f67997a1daacd5890ba062a91f3 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 23 Apr 2021 15:41:06 +0200 Subject: [PATCH 4/4] Added VisualExtensions.Translation samples --- .../VisualExtensionsCode.bind | 40 +++++++++++++++++-- .../VisualExtensionsPage.xaml | 29 ++++++++++++-- 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Visual Extensions/VisualExtensionsCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Visual Extensions/VisualExtensionsCode.bind index deba75d104e..2ba6632624a 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Visual Extensions/VisualExtensionsCode.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Visual Extensions/VisualExtensionsCode.bind @@ -5,15 +5,49 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:ui="using:Microsoft.Toolkit.Uwp.UI" + xmlns:interactivity="using:Microsoft.Xaml.Interactivity" + xmlns:interactions="using:Microsoft.Xaml.Interactions.Core" + xmlns:ani="using:Microsoft.Toolkit.Uwp.UI.Animations" + xmlns:behaviors="using:Microsoft.Toolkit.Uwp.UI.Behaviors" mc:Ignorable="d"> - + + + - + ui:VisualExtensions.NormalizedCenterPoint="0.5" + ui:VisualExtensions.Translation="20,12,0"/> + + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Visual Extensions/VisualExtensionsPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Visual Extensions/VisualExtensionsPage.xaml index 50f0f87c579..a8bf12a5a4f 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Visual Extensions/VisualExtensionsPage.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Visual Extensions/VisualExtensionsPage.xaml @@ -3,11 +3,15 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ui="using:Microsoft.Toolkit.Uwp.UI" + xmlns:interactivity="using:Microsoft.Xaml.Interactivity" + xmlns:interactions="using:Microsoft.Xaml.Interactions.Core" + xmlns:ani="using:Microsoft.Toolkit.Uwp.UI.Animations" + xmlns:behaviors="using:Microsoft.Toolkit.Uwp.UI.Behaviors" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> - + - + ui:VisualExtensions.NormalizedCenterPoint="0.5" + ui:VisualExtensions.Translation="20,12,0"/> + +