Advertisement
Guest User

TiltEffect for Windows Metro XAML apps

a guest
Mar 1st, 2012
628
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics.CodeAnalysis;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. using Windows.Foundation;
  8. using Windows.UI.Input;
  9. using Windows.UI.Xaml;
  10. using Windows.UI.Xaml.Controls;
  11. using Windows.UI.Xaml.Controls.Primitives;
  12. using Windows.UI.Xaml.Input;
  13. using Windows.UI.Xaml.Media;
  14. using Windows.UI.Xaml.Media.Animation;
  15.  
  16. namespace SharpGIS.Metro.Controls
  17. {
  18.     /// <summary>
  19.     /// This code provides attached properties for adding a 'tilt' effect to all
  20.     /// controls within a container.
  21.     /// </summary>
  22.     /// <QualityBand>Preview</QualityBand>
  23.     [SuppressMessage("Microsoft.Design", "CA1052:StaticHolderTypesShouldBeSealed", Justification = "Cannot be static and derive from DependencyObject.")]
  24.     public partial class TiltEffect : DependencyObject
  25.     {
  26.         /// <summary>
  27.         /// Cache of previous cache modes. Not using weak references for now.
  28.         /// </summary>
  29.         private static Dictionary<DependencyObject, CacheMode> _originalCacheMode = new Dictionary<DependencyObject, CacheMode>();
  30.  
  31.         /// <summary>
  32.         /// Maximum amount of tilt, in radians.
  33.         /// </summary>
  34.         private const double MaxAngle = 0.3;
  35.  
  36.         /// <summary>
  37.         /// Maximum amount of depression, in pixels
  38.         /// </summary>
  39.         private const double MaxDepression = 25;
  40.  
  41.         /// <summary>
  42.         /// Delay between releasing an element and the tilt release animation
  43.         /// playing.
  44.         /// </summary>
  45.         private static readonly TimeSpan TiltReturnAnimationDelay = TimeSpan.FromMilliseconds(200);
  46.  
  47.         /// <summary>
  48.         /// Duration of tilt release animation.
  49.         /// </summary>
  50.         private static readonly TimeSpan TiltReturnAnimationDuration = TimeSpan.FromMilliseconds(100);
  51.  
  52.         /// <summary>
  53.         /// The control that is currently being tilted.
  54.         /// </summary>
  55.         private static FrameworkElement currentTiltElement;
  56.  
  57.         /// <summary>
  58.         /// The single instance of a storyboard used for all tilts.
  59.         /// </summary>
  60.         private static Storyboard tiltReturnStoryboard;
  61.  
  62.         /// <summary>
  63.         /// The single instance of an X rotation used for all tilts.
  64.         /// </summary>
  65.         private static DoubleAnimation tiltReturnXAnimation;
  66.  
  67.         /// <summary>
  68.         /// The single instance of a Y rotation used for all tilts.
  69.         /// </summary>
  70.         private static DoubleAnimation tiltReturnYAnimation;
  71.  
  72.         /// <summary>
  73.         /// The single instance of a Z depression used for all tilts.
  74.         /// </summary>
  75.         private static DoubleAnimation tiltReturnZAnimation;
  76.  
  77.         /// <summary>
  78.         /// The center of the tilt element.
  79.         /// </summary>
  80.         private static Point currentTiltElementCenter;
  81.  
  82.         /// <summary>
  83.         /// Whether the animation just completed was for a 'pause' or not.
  84.         /// </summary>
  85.         private static bool wasPauseAnimation = false;
  86.  
  87.         /// <summary>
  88.         /// Default list of items that are tiltable.
  89.         /// </summary>
  90.         [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Tiltable", Justification = "By design.")]
  91.         [SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Justification = "Keeping it simple.")]
  92.         public static List<Type> TiltableItems { get; private set; }
  93.  
  94.         #region Constructor and Static Constructor
  95.         /// <summary>
  96.         /// This is not a constructable class, but it cannot be static because
  97.         /// it derives from DependencyObject.
  98.         /// </summary>
  99.         private TiltEffect()
  100.         {
  101.         }
  102.  
  103.         /// <summary>
  104.         /// Initialize the static properties
  105.         /// </summary>
  106.         [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Need to initialize the tiltable items property.")]
  107.         static TiltEffect()
  108.         {
  109.             // The tiltable items list.
  110.             TiltableItems = new List<Type>()
  111.             {
  112.                 typeof(ButtonBase),
  113.                 typeof(ListBoxItem),
  114.                 //typeof(MenuItem),
  115.             };
  116.         }
  117.  
  118.         #endregion
  119.  
  120.         #region Dependency properties
  121.  
  122.         /// <summary>
  123.         /// Whether the tilt effect is enabled on a container (and all its
  124.         /// children).
  125.         /// </summary>
  126.         public static readonly DependencyProperty IsTiltEnabledProperty = DependencyProperty.RegisterAttached(
  127.           "IsTiltEnabled",
  128.           typeof(bool),
  129.           typeof(TiltEffect),
  130.           new PropertyMetadata(false, OnIsTiltEnabledChanged)
  131.           );
  132.  
  133.         /// <summary>
  134.         /// Gets the IsTiltEnabled dependency property from an object.
  135.         /// </summary>
  136.         /// <param name="source">The object to get the property from.</param>
  137.         /// <returns>The property's value.</returns>
  138.         [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")]
  139.         public static bool GetIsTiltEnabled(DependencyObject source)
  140.         {
  141.             return (bool)source.GetValue(IsTiltEnabledProperty);
  142.         }
  143.  
  144.         /// <summary>
  145.         /// Sets the IsTiltEnabled dependency property on an object.
  146.         /// </summary>
  147.         /// <param name="source">The object to set the property on.</param>
  148.         /// <param name="value">The value to set.</param>
  149.         [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")]
  150.         public static void SetIsTiltEnabled(DependencyObject source, bool value)
  151.         {
  152.             source.SetValue(IsTiltEnabledProperty, value);
  153.         }
  154.  
  155.         /// <summary>
  156.         /// Suppresses the tilt effect on a single control that would otherwise
  157.         /// be tilted.
  158.         /// </summary>
  159.         public static readonly DependencyProperty SuppressTiltProperty = DependencyProperty.RegisterAttached(
  160.           "SuppressTilt",
  161.           typeof(bool),
  162.           typeof(TiltEffect),
  163.           null
  164.           );
  165.  
  166.         /// <summary>
  167.         /// Gets the SuppressTilt dependency property from an object.
  168.         /// </summary>
  169.         /// <param name="source">The object to get the property from.</param>
  170.         /// <returns>The property's value.</returns>
  171.         [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")]
  172.         public static bool GetSuppressTilt(DependencyObject source)
  173.         {
  174.             return (bool)source.GetValue(SuppressTiltProperty);
  175.         }
  176.  
  177.         /// <summary>
  178.         /// Sets the SuppressTilt dependency property from an object.
  179.         /// </summary>
  180.         /// <param name="source">The object to get the property from.</param>
  181.         /// <param name="value">The property's value.</param>
  182.         [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")]
  183.         public static void SetSuppressTilt(DependencyObject source, bool value)
  184.         {
  185.             source.SetValue(SuppressTiltProperty, value);
  186.         }
  187.  
  188.         /// <summary>
  189.         /// Property change handler for the IsTiltEnabled dependency property.
  190.         /// </summary>
  191.         /// <param name="target">The element that the property is atteched to.</param>
  192.         /// <param name="args">Event arguments.</param>
  193.         /// <remarks>
  194.         /// Adds or removes event handlers from the element that has been
  195.         /// (un)registered for tilting.
  196.         /// </remarks>
  197.         static void OnIsTiltEnabledChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
  198.         {
  199.             FrameworkElement fe = target as FrameworkElement;
  200.             if (fe != null)
  201.             {
  202.                 // Add / remove the event handler if necessary
  203.                 if ((bool)args.NewValue == true)
  204.                 {
  205.                     fe.PointerPressed += TiltEffect_PointerPressed;
  206.                 }
  207.                 else
  208.                 {
  209.                     fe.PointerPressed -= TiltEffect_PointerPressed;
  210.                 }
  211.             }
  212.         }
  213.  
  214.  
  215.         #endregion
  216.  
  217.         #region Top-level manipulation event handlers
  218.  
  219.         /// <summary>
  220.         /// Event handler for ManipulationStarted.
  221.         /// </summary>
  222.         /// <param name="sender">sender of the event - this will be the tilt
  223.         /// container (eg, entire page).</param>
  224.         /// <param name="e">Event arguments.</param>
  225.         private static void TiltEffect_PointerPressed(object sender, PointerEventArgs e)
  226.         {
  227.             TryStartTiltEffect(sender as FrameworkElement, e);
  228.         }
  229.  
  230.         /// <summary>
  231.         /// Event handler for ManipulationDelta
  232.         /// </summary>
  233.         /// <param name="sender">sender of the event - this will be the tilting
  234.         /// object (eg a button).</param>
  235.         /// <param name="e">Event arguments.</param>
  236.         private static void TiltEffect_PointerMoved(object sender, PointerEventArgs e)
  237.         {
  238.             ContinueTiltEffect(sender as FrameworkElement, e);
  239.         }
  240.  
  241.         /// <summary>
  242.         /// Event handler for ManipulationCompleted.
  243.         /// </summary>
  244.         /// <param name="sender">sender of the event - this will be the tilting
  245.         /// object (eg a button).</param>
  246.         /// <param name="e">Event arguments.</param>
  247.         static void TiltEffect_PointerReleased(object sender, PointerEventArgs e)
  248.         {
  249.             EndTiltEffect(currentTiltElement);
  250.         }
  251.  
  252.         #endregion
  253.  
  254.         #region Core tilt logic
  255.  
  256.         /// <summary>
  257.         /// Checks if the manipulation should cause a tilt, and if so starts the
  258.         /// tilt effect.
  259.         /// </summary>
  260.         /// <param name="source">The source of the manipulation (the tilt
  261.         /// container, eg entire page).</param>
  262.         /// <param name="e">The args from the ManipulationStarted event.</param>
  263.         static void TryStartTiltEffect(FrameworkElement source, PointerEventArgs e)
  264.         {
  265.             FrameworkElement element = source; // VisualTreeHelper.GetChild(ancestor, 0) as FrameworkElement;
  266.             FrameworkElement container = source;// e.Container as FrameworkElement;
  267.  
  268.             if (element == null || container == null)
  269.                 return;
  270.  
  271.             // Touch point relative to the element being tilted.
  272.             Point tiltTouchPoint = e.GetCurrentPoint(element).Position; // container.TransformToVisual(element).TransformPoint(e.GetCurrentPoint(element));
  273.  
  274.             // Center of the element being tilted.
  275.             Point elementCenter = new Point(element.ActualWidth / 2, element.ActualHeight / 2);
  276.  
  277.             // Camera adjustment.
  278.             Point centerToCenterDelta = GetCenterToCenterDelta(element, source);
  279.  
  280.             BeginTiltEffect(element, tiltTouchPoint, elementCenter, centerToCenterDelta, e.Pointer);
  281.             return;
  282.  
  283.  
  284.         }
  285.  
  286.         /// <summary>
  287.         /// Computes the delta between the centre of an element and its
  288.         /// container.
  289.         /// </summary>
  290.         /// <param name="element">The element to compare.</param>
  291.         /// <param name="container">The element to compare against.</param>
  292.         /// <returns>A point that represents the delta between the two centers.</returns>
  293.         static Point GetCenterToCenterDelta(FrameworkElement element, FrameworkElement container)
  294.         {
  295.             Point elementCenter = new Point(element.ActualWidth / 2, element.ActualHeight / 2);
  296.             Point containerCenter;
  297.  
  298. #if WINDOWS_PHONE
  299.  
  300.             // Need to special-case the frame to handle different orientations.
  301.             PhoneApplicationFrame frame = container as PhoneApplicationFrame;
  302.             if (frame != null)
  303.             {
  304.                 // Switch width and height in landscape mode
  305.                 if ((frame.Orientation & PageOrientation.Landscape) == PageOrientation.Landscape)
  306.                 {
  307.                     containerCenter = new Point(container.ActualHeight / 2, container.ActualWidth / 2);
  308.                 }
  309.                 else
  310.                 {
  311.                     containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2);
  312.                 }
  313.             }
  314.             else
  315.             {
  316.                 containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2);
  317.             }
  318. #else
  319.             containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2);
  320. #endif
  321.  
  322.             Point transformedElementCenter = element.TransformToVisual(container).TransformPoint(elementCenter);
  323.             Point result = new Point(containerCenter.X - transformedElementCenter.X, containerCenter.Y - transformedElementCenter.Y);
  324.  
  325.             return result;
  326.         }
  327.  
  328.         /// <summary>
  329.         /// Begins the tilt effect by preparing the control and doing the
  330.         /// initial animation.
  331.         /// </summary>
  332.         /// <param name="element">The element to tilt.</param>
  333.         /// <param name="touchPoint">The touch point, in element coordinates.</param>
  334.         /// <param name="centerPoint">The center point of the element in element
  335.         /// coordinates.</param>
  336.         /// <param name="centerDelta">The delta between the
  337.         /// <paramref name="element"/>'s center and the container's center.</param>
  338.         static void BeginTiltEffect(FrameworkElement element, Point touchPoint, Point centerPoint, Point centerDelta, Pointer p)
  339.         {
  340.             if (tiltReturnStoryboard != null)
  341.             {
  342.                 StopTiltReturnStoryboardAndCleanup();
  343.             }
  344.  
  345.             if (PrepareControlForTilt(element, centerDelta, p) == false)
  346.             {
  347.                 return;
  348.             }
  349.  
  350.             currentTiltElement = element;
  351.             currentTiltElementCenter = centerPoint;
  352.             PrepareTiltReturnStoryboard(element);
  353.  
  354.             ApplyTiltEffect(currentTiltElement, touchPoint, currentTiltElementCenter);
  355.         }
  356.  
  357.         /// <summary>
  358.         /// Prepares a control to be tilted by setting up a plane projection and
  359.         /// some event handlers.
  360.         /// </summary>
  361.         /// <param name="element">The control that is to be tilted.</param>
  362.         /// <param name="centerDelta">Delta between the element's center and the
  363.         /// tilt container's.</param>
  364.         /// <returns>true if successful; false otherwise.</returns>
  365.         /// <remarks>
  366.         /// This method is conservative; it will fail any attempt to tilt a
  367.         /// control that already has a projection on it.
  368.         /// </remarks>
  369.         static bool PrepareControlForTilt(FrameworkElement element, Point centerDelta, Pointer p)
  370.         {
  371.             // Prevents interference with any existing transforms
  372.             if (element.Projection != null || (element.RenderTransform != null && element.RenderTransform.GetType() != typeof(MatrixTransform)))
  373.             {
  374.                 return false;
  375.             }
  376.  
  377.             _originalCacheMode[element] = element.CacheMode;
  378.             element.CacheMode = new BitmapCache();
  379.  
  380.             TranslateTransform transform = new TranslateTransform();
  381.             transform.X = centerDelta.X;
  382.             transform.Y = centerDelta.Y;
  383.             element.RenderTransform = transform;
  384.  
  385.             PlaneProjection projection = new PlaneProjection();
  386.             projection.GlobalOffsetX = -1 * centerDelta.X;
  387.             projection.GlobalOffsetY = -1 * centerDelta.Y;
  388.             element.Projection = projection;
  389.  
  390.             element.PointerMoved += TiltEffect_PointerMoved;
  391.             element.PointerReleased += TiltEffect_PointerReleased;
  392.             element.CapturePointer(p);
  393.             return true;
  394.         }
  395.  
  396.         /// <summary>
  397.         /// Removes modifications made by PrepareControlForTilt.
  398.         /// </summary>
  399.         /// <param name="element">THe control to be un-prepared.</param>
  400.         /// <remarks>
  401.         /// This method is basic; it does not do anything to detect if the
  402.         /// control being un-prepared was previously prepared.
  403.         /// </remarks>
  404.         private static void RevertPrepareControlForTilt(FrameworkElement element)
  405.         {
  406.             element.PointerMoved -= TiltEffect_PointerMoved;
  407.             element.PointerReleased -= TiltEffect_PointerReleased;
  408.             element.Projection = null;
  409.             element.RenderTransform = null;
  410.  
  411.             CacheMode original;
  412.             if (_originalCacheMode.TryGetValue(element, out original))
  413.             {
  414.                 element.CacheMode = original;
  415.                 _originalCacheMode.Remove(element);
  416.             }
  417.             else
  418.             {
  419.                 element.CacheMode = null;
  420.             }
  421.         }
  422.  
  423.         /// <summary>
  424.         /// Creates the tilt return storyboard (if not already created) and
  425.         /// targets it to the projection.
  426.         /// </summary>
  427.         /// <param name="element">The framework element to prepare for
  428.         /// projection.</param>
  429.         static void PrepareTiltReturnStoryboard(FrameworkElement element)
  430.         {
  431.             if (tiltReturnStoryboard == null)
  432.             {
  433.                 tiltReturnStoryboard = new Storyboard();
  434.                 tiltReturnStoryboard.Completed += TiltReturnStoryboard_Completed;
  435.                 tiltReturnXAnimation = new DoubleAnimation();
  436.                 Storyboard.SetTargetProperty(tiltReturnXAnimation, "RotationX");
  437.                 tiltReturnXAnimation.BeginTime = TiltReturnAnimationDelay;
  438.                 tiltReturnXAnimation.To = 0;
  439.                 tiltReturnXAnimation.Duration = TiltReturnAnimationDuration;
  440.  
  441.                 tiltReturnYAnimation = new DoubleAnimation();
  442.                 Storyboard.SetTargetProperty(tiltReturnYAnimation, "RotationY");
  443.                 tiltReturnYAnimation.BeginTime = TiltReturnAnimationDelay;
  444.                 tiltReturnYAnimation.To = 0;
  445.                 tiltReturnYAnimation.Duration = TiltReturnAnimationDuration;
  446.  
  447.                 tiltReturnZAnimation = new DoubleAnimation();
  448.                 Storyboard.SetTargetProperty(tiltReturnZAnimation, "GlobalOffsetZ");
  449.                 tiltReturnZAnimation.BeginTime = TiltReturnAnimationDelay;
  450.                 tiltReturnZAnimation.To = 0;
  451.                 tiltReturnZAnimation.Duration = TiltReturnAnimationDuration;
  452.  
  453.                 tiltReturnStoryboard.Children.Add(tiltReturnXAnimation);
  454.                 tiltReturnStoryboard.Children.Add(tiltReturnYAnimation);
  455.                 tiltReturnStoryboard.Children.Add(tiltReturnZAnimation);
  456.             }
  457.  
  458.             Storyboard.SetTarget(tiltReturnXAnimation, element.Projection);
  459.             Storyboard.SetTarget(tiltReturnYAnimation, element.Projection);
  460.             Storyboard.SetTarget(tiltReturnZAnimation, element.Projection);
  461.         }
  462.  
  463.         /// <summary>
  464.         /// Continues a tilt effect that is currently applied to an element,
  465.         /// presumably because the user moved their finger.
  466.         /// </summary>
  467.         /// <param name="element">The element being tilted.</param>
  468.         /// <param name="e">The manipulation event args.</param>
  469.         static void ContinueTiltEffect(FrameworkElement element, PointerEventArgs e)
  470.         {
  471.             FrameworkElement container = element;
  472.             if (container == null || element == null)
  473.             {
  474.                 return;
  475.             }
  476.  
  477.             Point tiltTouchPoint =  e.GetCurrentPoint(element).Position;
  478.  
  479.             // If touch moved outside bounds of element, then pause the tilt
  480.             // (but don't cancel it)
  481.             if (new Rect(0, 0, currentTiltElement.ActualWidth, currentTiltElement.ActualHeight).Contains(tiltTouchPoint) != true)
  482.             {
  483.                 PauseTiltEffect();
  484.             }
  485.             else
  486.             {
  487.                 // Apply the updated tilt effect
  488.                 ApplyTiltEffect(currentTiltElement, tiltTouchPoint, currentTiltElementCenter);
  489.             }
  490.         }
  491.  
  492.         /// <summary>
  493.         /// Ends the tilt effect by playing the animation.
  494.         /// </summary>
  495.         /// <param name="element">The element being tilted.</param>
  496.         private static void EndTiltEffect(FrameworkElement element)
  497.         {
  498.             if (element != null)
  499.             {
  500.                 element.PointerReleased -= TiltEffect_PointerPressed;
  501.                 element.PointerMoved -= TiltEffect_PointerMoved;
  502.             }
  503.  
  504.             if (tiltReturnStoryboard != null)
  505.             {
  506.                 wasPauseAnimation = false;
  507.                 if (tiltReturnStoryboard.GetCurrentState() != ClockState.Active)
  508.                 {
  509.                     tiltReturnStoryboard.Begin();
  510.                 }
  511.             }
  512.             else
  513.             {
  514.                 StopTiltReturnStoryboardAndCleanup();
  515.             }
  516.         }
  517.  
  518.         /// <summary>
  519.         /// Handler for the storyboard complete event.
  520.         /// </summary>
  521.         /// <param name="sender">sender of the event.</param>
  522.         /// <param name="e">event args.</param>
  523.         private static void TiltReturnStoryboard_Completed(object sender, object e)
  524.         {
  525.             if (wasPauseAnimation)
  526.             {
  527.                 ResetTiltEffect(currentTiltElement);
  528.             }
  529.             else
  530.             {
  531.                 StopTiltReturnStoryboardAndCleanup();
  532.             }
  533.         }
  534.  
  535.         /// <summary>
  536.         /// Resets the tilt effect on the control, making it appear 'normal'
  537.         /// again.
  538.         /// </summary>
  539.         /// <param name="element">The element to reset the tilt on.</param>
  540.         /// <remarks>
  541.         /// This method doesn't turn off the tilt effect or cancel any current
  542.         /// manipulation; it just temporarily cancels the effect.
  543.         /// </remarks>
  544.         private static void ResetTiltEffect(FrameworkElement element)
  545.         {
  546.             PlaneProjection projection = element.Projection as PlaneProjection;
  547.             projection.RotationY = 0;
  548.             projection.RotationX = 0;
  549.             projection.GlobalOffsetZ = 0;
  550.         }
  551.  
  552.         /// <summary>
  553.         /// Stops the tilt effect and release resources applied to the currently
  554.         /// tilted control.
  555.         /// </summary>
  556.         private static void StopTiltReturnStoryboardAndCleanup()
  557.         {
  558.             if (tiltReturnStoryboard != null)
  559.             {
  560.                 tiltReturnStoryboard.Stop();
  561.             }
  562.  
  563.             RevertPrepareControlForTilt(currentTiltElement);
  564.         }
  565.  
  566.         /// <summary>
  567.         /// Pauses the tilt effect so that the control returns to the 'at rest'
  568.         /// position, but doesn't stop the tilt effect (handlers are still
  569.         /// attached).
  570.         /// </summary>
  571.         private static void PauseTiltEffect()
  572.         {
  573.             if ((tiltReturnStoryboard != null) && !wasPauseAnimation)
  574.             {
  575.                 tiltReturnStoryboard.Stop();
  576.                 wasPauseAnimation = true;
  577.                 tiltReturnStoryboard.Begin();
  578.             }
  579.         }
  580.  
  581.         /// <summary>
  582.         /// Resets the storyboard to not running.
  583.         /// </summary>
  584.         private static void ResetTiltReturnStoryboard()
  585.         {
  586.             tiltReturnStoryboard.Stop();
  587.             wasPauseAnimation = false;
  588.         }
  589.  
  590.         /// <summary>
  591.         /// Applies the tilt effect to the control.
  592.         /// </summary>
  593.         /// <param name="element">the control to tilt.</param>
  594.         /// <param name="touchPoint">The touch point, in the container's
  595.         /// coordinates.</param>
  596.         /// <param name="centerPoint">The center point of the container.</param>
  597.         private static void ApplyTiltEffect(FrameworkElement element, Point touchPoint, Point centerPoint)
  598.         {
  599.             // Stop any active animation
  600.             ResetTiltReturnStoryboard();
  601.  
  602.             // Get relative point of the touch in percentage of container size
  603.             Point normalizedPoint = new Point(
  604.                 Math.Min(Math.Max(touchPoint.X / (centerPoint.X * 2), 0), 1),
  605.                 Math.Min(Math.Max(touchPoint.Y / (centerPoint.Y * 2), 0), 1));
  606.  
  607.             if (double.IsNaN(normalizedPoint.X) || double.IsNaN(normalizedPoint.Y))
  608.             {
  609.                 return;
  610.             }
  611.  
  612.             // Shell values
  613.             double xMagnitude = Math.Abs(normalizedPoint.X - 0.5);
  614.             double yMagnitude = Math.Abs(normalizedPoint.Y - 0.5);
  615.             double xDirection = -Math.Sign(normalizedPoint.X - 0.5);
  616.             double yDirection = Math.Sign(normalizedPoint.Y - 0.5);
  617.             double angleMagnitude = xMagnitude + yMagnitude;
  618.             double xAngleContribution = xMagnitude + yMagnitude > 0 ? xMagnitude / (xMagnitude + yMagnitude) : 0;
  619.  
  620.             double angle = angleMagnitude * MaxAngle * 180 / Math.PI;
  621.             double depression = (1 - angleMagnitude) * MaxDepression;
  622.  
  623.             // RotationX and RotationY are the angles of rotations about the x-
  624.             // or y-*axis*; to achieve a rotation in the x- or y-*direction*, we
  625.             // need to swap the two. That is, a rotation to the left about the
  626.             // y-axis is a rotation to the left in the x-direction, and a
  627.             // rotation up about the x-axis is a rotation up in the y-direction.
  628.             PlaneProjection projection = element.Projection as PlaneProjection;
  629.             projection.RotationY = angle * xAngleContribution * xDirection;
  630.             projection.RotationX = angle * (1 - xAngleContribution) * yDirection;
  631.             projection.GlobalOffsetZ = -depression;
  632.         }
  633.  
  634.         #endregion
  635.     }
  636.  
  637.     /// <summary>
  638.     /// Couple of simple helpers for walking the visual tree.
  639.     /// </summary>
  640.     static class TreeHelpers
  641.     {
  642.         /// <summary>
  643.         /// Gets the ancestors of the element, up to the root.
  644.         /// </summary>
  645.         /// <param name="node">The element to start from.</param>
  646.         /// <returns>An enumerator of the ancestors.</returns>
  647.         public static IEnumerable<FrameworkElement> GetVisualAncestors(this FrameworkElement node)
  648.         {
  649.             FrameworkElement parent = node.GetVisualParent();
  650.             while (parent != null)
  651.             {
  652.                 yield return parent;
  653.                 parent = parent.GetVisualParent();
  654.             }
  655.         }
  656.  
  657.         /// <summary>
  658.         /// Gets the visual parent of the element.
  659.         /// </summary>
  660.         /// <param name="node">The element to check.</param>
  661.         /// <returns>The visual parent.</returns>
  662.         public static FrameworkElement GetVisualParent(this FrameworkElement node)
  663.         {
  664.             return VisualTreeHelper.GetParent(node) as FrameworkElement;
  665.         }
  666.     }
  667. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement