Tilt effect for Windows Phone controls

[UPDATE August 16, 2010: Please use the updated version of the tilt code at http://blogs.msdn.com/b/ptorr/archive/2010/08/11/updated-tilt-effect.aspx]

One of the effects that can be seen on the Windows Phone 7 Series videos is the “tilt” effect on buttons and other controls. This effect isn’t built-in to the Silverlight controls (after all, maybe you don’t want your buttons to tilt) but it is trivial to add it. As promised, attached is a simple project that contains a helper library and a sample application that shows how to apply the tilt effect to your application. Please note that this effect is not the final “blessed” effect and the actual calculations used to determine how much to depress / tilt the controls may change. This is just a sample to show how it’s done.

Tilt image

A quick explanation: The tilt effect is added to an element using the TiltEffect.IsTiltEnabled attached property. Typically you would just add this to your application’s main frame (as done in the demo) to enable it anywhere in your application, but if you want a more scoped application of tilt you could choose to put it only on specific pages or other containers such as a Grid.

Adding this attached property binds a MouseLeftButtonDown handler to the element (using the handledEventsToo parameter to get all events, not just those that are unhandled), and the mouse down handler does a couple of things:

  1. It searches the visual tree upwards, looking for a control that is supposed to tilt (right now, that’s just controls that are ButtonBase or ListBoxItem)
  2. Once it finds a control, it runs a storyboard against that control, using a plane projection and some computed values to make the projection relative to where you touched the control

The calculation of the tilt is pretty basic:

double halfWidth = element.ActualWidth / 2;
double halfHeight = element.ActualHeight / 2;

double xAngle = Math.Asin((point.Y – halfHeight) / halfHeight) * RadiansToDegrees;
double yAngle = Math.Acos((point.X – halfWidth) / halfWidth) * RadiansToDegrees;

This “cheap” tilt is not actually what you would see with a real-world object; it is a combination of an X rotation and a Y rotation that overlap in subtle ways. A real tilt would first rotate the object around the Z axis and then perform a single plane rotation, but that requires more effort (including the injection of a dummy container in order to rotate the control and then counter-rotate it back again) and I doubt anyone can actually tell the difference in normal usage. Astute readers might note that another solution for a “correct” tilt would be to use a MatrixTransform, but (i) I’ve forgotten all my high school math, and (ii) you can’t animate MatrixProjection so it would require manual updating via CompositionTarget.Rendering… ugh.

So in summary there is no real rocket science here; it’s just a couple of standard Silverlight features put to use. This could probably also be exposed as a Blend Behavior, but I didn’t get around to doing that.

The TiltEffect exposes several attached properties:

  • IsTiltEnabled: Set to true to enable tilt on all contained controls
  • SuppressTilt: Set to true to suppress tilt on an individual control that would otherwise be tilted
  • TiltStrength: Set between 0 and 1 to specify the amount of projection; default can be set on the root container, and individual elements can have overridden values
  • PressStrength: Set between 0 and 1 to specify the amount of depression; default can be set on the root container, and individual elements can have overridden values

Play around and let me know what you think!


Comments (2)

  1. mdtauk says:

    This is great to know, but there is one difference between this and the behaviour of the native controls.  When you hold your finger down on the native controls, and then drag your finger over the control and off the control, the tilt continues to change as you move your finger around.  Is there a way to update this code to take into account a MouseDown with MouseMove behaviour?

  2. ptorr says:

    I have coded up that behaviour in the past, but I believe it is not how the final effect will work. Basically you handle the MouseMove as well and update the projection appropriately.