Last year a new UWP API surfaced: Windows.UI.Composition. It’s a framework agnostic composition library that can be used on its own, to enrich your existing XAML layouts or to entirely replace the default Storyboard
implementation, among other uses. But it’s not limited to manipulating UI elements or animating them – you can even apply Direct2D effects/shaders (instantiated via the awesome Win2D library) to existing XAML elements in real time.
This post will be a brief explanation of a real world usage of the Windows Composition features: implementing a behavior that, when attached to a XAML control, makes it “tilt” when tapped/clicked, just like Windows Phone used for interactive UI elements.
Approach
The idea is to subclass a Behavior
and, when it is attached to a control (via the OnAttached
override), obtain a reference to the Windows Composition Visual
element of that control. Also, we will subscribe to the relevant Pointer*
events to check when the user is interacting and apply or reset the tilt effect.
Here is how our behavior is going to react to each input event:
PointerPressed
: apply the tilt effect.PointerMoved
: if the touch/mouse is currently pressed (PointerRoutedEventArgs.Point.IsInContact
is true), apply the tilt effect; reset it otherwise.PointerReleased
,PointerCanceled
,PointerExited
: reset the tilt effect.
Finally, the tilt effect will consist on applying a rotation to the Visual
element using its center as the pivot point, and with a rotation axis such that the plane of the control “looks” at the touch/mouse position.
This is how we set the center point of the Visual
– since it’s going to be in coordinates relative to the control, we just multiply both the width and the height retrieved from RenderSize
by half. The Z offset will make it look like as if it’s being pressed and sent to the background:
[code language=”csharp”]
// Set a center point for rotating the Visual.
// The Z value adds a bit of depth movement.
this.elementVisual.CenterPoint = new Vector3((float)(this.uiElement.RenderSize.Width * 0.5f), (float)(this.uiElement.RenderSize.Height * 0.5f), -10.0f);
[/code]
Now, we get the contact point (again in coordinates relative to the control) and use it to calculate the offset vector pointing from the control’s center to the point of contact, and then normalize it:
[code language=”csharp”]
// Get the contact point of the pointer, in coordinates relative to the Visual.
var contactPoint = e.GetCurrentPoint(uiElement).Position;
// Obtain an offset vector from the center and normalize it.
var contactVector = new Vector3((float)contactPoint.X, (float)contactPoint.Y, 0.0f) – this.elementVisual.CenterPoint;
contactVector = Vector3.Normalize(contactVector);
[/code]
The last step is setting both the rotation axis and the amount of rotation around it, in this case in degrees:
[code language=”csharp”]
// Swap vector coordinates so they point to the correct corner and the final rotation is correct.
this.elementVisual.RotationAxis = new Vector3(contactVector.Y, -contactVector.X, 0.0f);
// Rotate by a set amount of degrees.
this.elementVisual.RotationAngleInDegrees = 20.0f;
[/code]
When we want to reset the tilt effect, we just set its rotation to 0:
[code language=”csharp”]
this.elementVisual.RotationAngleInDegrees = 0.0f;
[/code]
Caveats
This has been done as a way of learning the basic bits of Windows Composition, and while trying to mimic the general behaviour of the old tilt effect, it doesn’t do it 100% accurately. For example, tapping on the center of the control should apply little to no rotation, and just send it to the back; also, the scaling looks a bit weird on too wide or too tall controls. And the current touch detection implementation only fires when the cursor/touch point is hitting the text of a TextBlock
control – it should work on its entire bounding box. However, if it suits your needs – feel free to use it on your projects!
What are you waiting for? Grab the source code on its GitHub repository.