Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -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">

<Grid>
<StackPanel Spacing="120" VerticalAlignment="Center">

<!--This is a static element, with some Visual properties being modified through
the VisualExtensions class. You can modify their values to see how the position,
orientation and alignment of the Border element changes.-->
<Border Height="100"
Width="100"
Background="Purple"
ui:VisualExtensions.CenterPoint="50,50,0"
ui:VisualExtensions.Offset="50"
ui:VisualExtensions.Opacity="0.5"
ui:VisualExtensions.RotationAngleInDegrees="80"
ui:VisualExtensions.Scale="2, 0.5, 1"
ui:VisualExtensions.NormalizedCenterPoint="0.5" />
</Grid>
ui:VisualExtensions.NormalizedCenterPoint="0.5"
ui:VisualExtensions.Translation="20,12,0"/>

<!--This Button demonstrates the VisualExtensions.Translation property in combination with a translation
animation. The Translation property is set to indicate the starting position of the element (relative
to its offset), and the animation will modify that to reach the specified translation value. Note how
the animation doesn't have an explicit starting value, as it will just start animating the translation
from the current value set via the VisualExtensions.Translation attached property.-->
<Button Height="120"
Width="360"
Background="Green"
Content="Click me!"
FontSize="32"
ui:VisualExtensions.Translation="20,-40,0">
<ani:Explicit.Animations>
<ani:AnimationSet x:Name="MoveAnimation">
<ani:TranslationAnimation To="480,80,0" Duration="0:0:2"/>
</ani:AnimationSet>
</ani:Explicit.Animations>
<interactivity:Interaction.Behaviors>
<interactions:EventTriggerBehavior EventName="Click">
<behaviors:StartAnimationAction Animation="{Binding ElementName=MoveAnimation}"/>
</interactions:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Button>
</StackPanel>
</Page>
Original file line number Diff line number Diff line change
Expand Up @@ -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">

<Grid>
<StackPanel Spacing="120" VerticalAlignment="Center">
<Border Height="100"
Width="100"
Background="Purple"
Expand All @@ -16,6 +20,25 @@
ui:VisualExtensions.Opacity="0.5"
ui:VisualExtensions.RotationAngleInDegrees="80"
ui:VisualExtensions.Scale="2, 0.5, 1"
ui:VisualExtensions.NormalizedCenterPoint="0.5"/>
</Grid>
ui:VisualExtensions.NormalizedCenterPoint="0.5"
ui:VisualExtensions.Translation="20,12,0"/>
<Button
Height="120"
Width="360"
Background="Green"
Content="Click me!"
FontSize="32"
ui:VisualExtensions.Translation="20,-40,0">
<ani:Explicit.Animations>
<ani:AnimationSet x:Name="MoveAnimation">
<ani:TranslationAnimation To="480,80,0" Duration="0:0:2"/>
</ani:AnimationSet>
</ani:Explicit.Animations>
<interactivity:Interaction.Behaviors>
<interactions:EventTriggerBehavior EventName="Click">
<behaviors:StartAnimationAction Animation="{Binding ElementName=MoveAnimation}"/>
</interactions:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Button>
</StackPanel>
</Page>
76 changes: 76 additions & 0 deletions Microsoft.Toolkit.Uwp.UI/Extensions/VisualExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -114,6 +115,36 @@ public static void SetOffset(DependencyObject obj, string value)
obj.SetValue(OffsetProperty, value);
}

/// <summary>
/// Gets the <c>"Translation"</c> property of the underlying <see cref="Visual"/> object for a <see cref="UIElement"/>, in <see cref="string"/> form.
/// </summary>
/// <param name="obj">The <see cref="DependencyObject"/> instance.</param>
/// <returns>The <see cref="string"/> representation of the <c>"Translation"</c> property property.</returns>
public static string GetTranslation(DependencyObject obj)
{
if (!DesignTimeHelpers.IsRunningInLegacyDesignerMode && obj is UIElement element)
{
return GetTranslationForElement(element);
}

return (string)obj.GetValue(TranslationProperty);
}

/// <summary>
/// Sets the <c>"Translation"</c> property of the underlying <see cref="Visual"/> object for a <see cref="UIElement"/>, in <see cref="string"/> form.
/// </summary>
/// <param name="obj">The <see cref="DependencyObject"/> instance.</param>
/// <param name="value">The <see cref="string"/> representation of the <c>"Translation"</c> property property to be set.</param>
public static void SetTranslation(DependencyObject obj, string value)
{
if (!DesignTimeHelpers.IsRunningInLegacyDesignerMode && obj is UIElement element)
{
SetTranslationForElement(value, element);
}

obj.SetValue(TranslationProperty, value);
}

/// <summary>
/// Gets the <see cref="Visual.Opacity"/> of a UIElement
/// </summary>
Expand Down Expand Up @@ -334,6 +365,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));

/// <summary>
/// Identifies the Translation attached property.
/// </summary>
public static readonly DependencyProperty TranslationProperty =
DependencyProperty.RegisterAttached("Translation", typeof(string), typeof(VisualExtensions), new PropertyMetadata(null, OnTranslationChanged));

/// <summary>
/// Identifies the Opacity attached property.
/// </summary>
Expand Down Expand Up @@ -400,6 +437,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)
Expand Down Expand Up @@ -503,6 +548,37 @@ private static void SetOffsetForElement(string value, UIElement element)
visual.Offset = value.ToVector3();
}

private static string GetTranslationForElement(UIElement element)
{
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);

// 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)
{
var visual = GetVisual(element);
Expand Down
83 changes: 83 additions & 0 deletions UnitTests/UnitTests.UWP/UI/Extensions/Test_VisualExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// 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;
using Windows.UI.Composition;
using Microsoft.Toolkit.Uwp.UI.Animations;

namespace UnitTests.UWP.UI
{
[TestClass]
[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()
{
await App.DispatcherQueue.EnqueueAsync(async () =>
{
var button = new Button();
var grid = new Grid() { Children = { button } };

VisualExtensions.SetTranslation(button, "80, 20, 0");

await SetTestContentAsync(grid);

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 text = VisualExtensions.GetTranslation(button);

Assert.AreEqual(text, new Vector3(11, 22, 0).ToString());
});
}
}
}
1 change: 1 addition & 0 deletions UnitTests/UnitTests.UWP/UnitTests.UWP.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@
<Compile Include="UI\Controls\Test_UniformGrid_Dimensions.cs" />
<Compile Include="UI\Controls\Test_WrapPanel_Visibility.cs" />
<Compile Include="UI\Controls\Test_WrapPanel_BasicLayout.cs" />
<Compile Include="UI\Extensions\Test_VisualExtensions.cs" />
<Compile Include="UI\Person.cs" />
<Compile Include="UI\Test_AdvancedCollectionView.cs" />
<Compile Include="UnitTestApp.xaml.cs">
Expand Down