/wp-toolkit/Microsoft.Phone.Controls.Toolkit/Effects/TiltEffect.cs
C# | 806 lines | 443 code | 101 blank | 262 comment | 73 complexity | df34526e9b4b0263ea5493201949b1f2 MD5 | raw file
- // (c) Copyright Microsoft Corporation.
- // This source is subject to the Microsoft Public License (Ms-PL).
- // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
- // All other rights reserved.
-
- using System;
- using System.Collections.Generic;
- using System.Diagnostics.CodeAnalysis;
- using System.Linq;
- using System.Windows;
- using System.Windows.Controls;
- using System.Windows.Controls.Primitives;
- using System.Windows.Input;
- using System.Windows.Media;
- using System.Windows.Media.Animation;
-
- #if WINDOWS_PHONE
-
- #endif
-
- namespace Microsoft.Phone.Controls
- {
- /// <summary>
- /// This code provides attached properties for adding a 'tilt' effect to all
- /// controls within a container.
- /// </summary>
- /// <QualityBand>Preview</QualityBand>
- [SuppressMessage("Microsoft.Design", "CA1052:StaticHolderTypesShouldBeSealed", Justification = "Cannot be static and derive from DependencyObject.")]
- public partial class TiltEffect : DependencyObject
- {
- /// <summary>
- /// Cache of previous cache modes. Not using weak references for now.
- /// </summary>
- private static Dictionary<DependencyObject, CacheMode> _originalCacheMode = new Dictionary<DependencyObject, CacheMode>();
-
- /// <summary>
- /// Maximum amount of tilt, in radians.
- /// </summary>
- private const double MaxAngle = 0.3;
-
- /// <summary>
- /// Maximum amount of depression, in pixels
- /// </summary>
- private const double MaxDepression = 25;
-
- /// <summary>
- /// Delay between releasing an element and the tilt release animation
- /// playing.
- /// </summary>
- private static readonly TimeSpan TiltReturnAnimationDelay = TimeSpan.FromMilliseconds(200);
-
- /// <summary>
- /// Duration of tilt release animation.
- /// </summary>
- private static readonly TimeSpan TiltReturnAnimationDuration = TimeSpan.FromMilliseconds(100);
-
- /// <summary>
- /// The control that is currently being tilted.
- /// </summary>
- private static FrameworkElement currentTiltElement;
-
- /// <summary>
- /// The single instance of a storyboard used for all tilts.
- /// </summary>
- private static Storyboard tiltReturnStoryboard;
-
- /// <summary>
- /// The single instance of an X rotation used for all tilts.
- /// </summary>
- private static DoubleAnimation tiltReturnXAnimation;
-
- /// <summary>
- /// The single instance of a Y rotation used for all tilts.
- /// </summary>
- private static DoubleAnimation tiltReturnYAnimation;
-
- /// <summary>
- /// The single instance of a Z depression used for all tilts.
- /// </summary>
- private static DoubleAnimation tiltReturnZAnimation;
-
- /// <summary>
- /// The center of the tilt element.
- /// </summary>
- private static Point currentTiltElementCenter;
-
- /// <summary>
- /// Whether the animation just completed was for a 'pause' or not.
- /// </summary>
- private static bool wasPauseAnimation = false;
-
- /// <summary>
- /// Whether to use a slightly more accurate (but slightly slower) tilt
- /// animation easing function.
- /// </summary>
- public static bool UseLogarithmicEase { get; set; }
-
- /// <summary>
- /// Default list of items that are tiltable.
- /// </summary>
- [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Tiltable", Justification = "By design.")]
- [SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Justification = "Keeping it simple.")]
- public static List<Type> TiltableItems { get; private set; }
-
- #region Constructor and Static Constructor
- /// <summary>
- /// This is not a constructable class, but it cannot be static because
- /// it derives from DependencyObject.
- /// </summary>
- private TiltEffect()
- {
- }
-
- /// <summary>
- /// Initialize the static properties
- /// </summary>
- [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Need to initialize the tiltable items property.")]
- static TiltEffect()
- {
- // The tiltable items list.
- TiltableItems = new List<Type>()
- {
- typeof(ButtonBase),
- typeof(ListBoxItem),
- typeof(ListPicker),
- typeof(MenuItem),
- typeof(LongListSelector)
- };
- }
-
- #endregion
-
- #region Dependency properties
-
- /// <summary>
- /// Whether the tilt effect is enabled on a container (and all its
- /// children).
- /// </summary>
- public static readonly DependencyProperty IsTiltEnabledProperty = DependencyProperty.RegisterAttached(
- "IsTiltEnabled",
- typeof(bool),
- typeof(TiltEffect),
- new PropertyMetadata(OnIsTiltEnabledChanged)
- );
-
- /// <summary>
- /// Gets the IsTiltEnabled dependency property from an object.
- /// </summary>
- /// <param name="source">The object to get the property from.</param>
- /// <returns>The property's value.</returns>
- [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")]
- public static bool GetIsTiltEnabled(DependencyObject source)
- {
- return (bool)source.GetValue(IsTiltEnabledProperty);
- }
-
- /// <summary>
- /// Sets the IsTiltEnabled dependency property on an object.
- /// </summary>
- /// <param name="source">The object to set the property on.</param>
- /// <param name="value">The value to set.</param>
- [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")]
- public static void SetIsTiltEnabled(DependencyObject source, bool value)
- {
- source.SetValue(IsTiltEnabledProperty, value);
- }
-
- /// <summary>
- /// Suppresses the tilt effect on a single control that would otherwise
- /// be tilted.
- /// </summary>
- public static readonly DependencyProperty SuppressTiltProperty = DependencyProperty.RegisterAttached(
- "SuppressTilt",
- typeof(bool),
- typeof(TiltEffect),
- null
- );
-
- /// <summary>
- /// Gets the SuppressTilt dependency property from an object.
- /// </summary>
- /// <param name="source">The object to get the property from.</param>
- /// <returns>The property's value.</returns>
- [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")]
- public static bool GetSuppressTilt(DependencyObject source)
- {
- return (bool)source.GetValue(SuppressTiltProperty);
- }
-
- /// <summary>
- /// Sets the SuppressTilt dependency property from an object.
- /// </summary>
- /// <param name="source">The object to get the property from.</param>
- /// <param name="value">The property's value.</param>
- [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")]
- public static void SetSuppressTilt(DependencyObject source, bool value)
- {
- source.SetValue(SuppressTiltProperty, value);
- }
-
- /// <summary>
- /// Property change handler for the IsTiltEnabled dependency property.
- /// </summary>
- /// <param name="target">The element that the property is atteched to.</param>
- /// <param name="args">Event arguments.</param>
- /// <remarks>
- /// Adds or removes event handlers from the element that has been
- /// (un)registered for tilting.
- /// </remarks>
- static void OnIsTiltEnabledChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
- {
- FrameworkElement fe = target as FrameworkElement;
- if (fe != null)
- {
- // Add / remove the event handler if necessary
- if ((bool)args.NewValue == true)
- {
- fe.ManipulationStarted += TiltEffect_ManipulationStarted;
- }
- else
- {
- fe.ManipulationStarted -= TiltEffect_ManipulationStarted;
- }
- }
- }
-
- #endregion
-
- #region Top-level manipulation event handlers
-
- /// <summary>
- /// Event handler for ManipulationStarted.
- /// </summary>
- /// <param name="sender">sender of the event - this will be the tilt
- /// container (eg, entire page).</param>
- /// <param name="e">Event arguments.</param>
- private static void TiltEffect_ManipulationStarted(object sender, ManipulationStartedEventArgs e)
- {
- TryStartTiltEffect(sender as FrameworkElement, e);
- }
-
- /// <summary>
- /// Event handler for ManipulationDelta
- /// </summary>
- /// <param name="sender">sender of the event - this will be the tilting
- /// object (eg a button).</param>
- /// <param name="e">Event arguments.</param>
- private static void TiltEffect_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
- {
- ContinueTiltEffect(sender as FrameworkElement, e);
- }
-
- /// <summary>
- /// Event handler for ManipulationCompleted.
- /// </summary>
- /// <param name="sender">sender of the event - this will be the tilting
- /// object (eg a button).</param>
- /// <param name="e">Event arguments.</param>
- static void TiltEffect_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
- {
- EndTiltEffect(currentTiltElement);
- }
-
- #endregion
-
- #region Core tilt logic
-
- /// <summary>
- /// Checks if the manipulation should cause a tilt, and if so starts the
- /// tilt effect.
- /// </summary>
- /// <param name="source">The source of the manipulation (the tilt
- /// container, eg entire page).</param>
- /// <param name="e">The args from the ManipulationStarted event.</param>
- static void TryStartTiltEffect(FrameworkElement source, ManipulationStartedEventArgs e)
- {
- foreach (FrameworkElement ancestor in (e.OriginalSource as FrameworkElement).GetVisualAncestors())
- {
- foreach (Type t in TiltableItems)
- {
- if (t.IsAssignableFrom(ancestor.GetType()))
- {
- FrameworkElement elementSuppressingTilt = null;
-
- // Look up the tree to find either an explicit DO or DO NOT suppress.
- if (ancestor.ReadLocalValue(SuppressTiltProperty) is bool)
- {
- elementSuppressingTilt = ancestor;
- }
- else
- {
- elementSuppressingTilt = ancestor.GetVisualAncestors().FirstOrDefault(x => x.ReadLocalValue(SuppressTiltProperty) is bool);
- }
-
- if (elementSuppressingTilt != null && (bool)elementSuppressingTilt.ReadLocalValue(SuppressTiltProperty) == true)
- {
- continue;
- }
- else
- {
- #if WP8
- if (t == typeof(LongListSelector))
- {
- StartTiltEffectOnLLS((LongListSelector)ancestor, e);
- }
- else
- {
- #endif
- // Use first child of the control, so that we can add transforms and not
- // impact any transforms on the control itself.
- FrameworkElement element = VisualTreeHelper.GetChild(ancestor, 0) as FrameworkElement;
- FrameworkElement container = e.ManipulationContainer as FrameworkElement;
-
- if (element == null || container == null)
- return;
-
- // Touch point relative to the element being tilted.
- Point tiltTouchPoint = container.TransformToVisual(element).Transform(e.ManipulationOrigin);
-
- // Center of the element being tilted.
- Point elementCenter = new Point(element.ActualWidth / 2, element.ActualHeight / 2);
-
- // Camera adjustment.
- Point centerToCenterDelta = GetCenterToCenterDelta(element, source);
-
- BeginTiltEffect(element, tiltTouchPoint, elementCenter, centerToCenterDelta);
- #if WP8
- }
- #endif
-
- return;
- }
- }
- }
- }
- }
-
-
- #if WP8
- /// <summary>
- /// Starts the tilt effect on LLS items or sticky header.
- /// </summary>
- private static void StartTiltEffectOnLLS(LongListSelector lls, ManipulationStartedEventArgs e)
- {
- FrameworkElement parent = (FrameworkElement)e.OriginalSource;
- ContentPresenter[] cps = new ContentPresenter[2];
- ContentPresenter cp;
- int cpCount = 0;
-
- // Starts from OriginalSource and goes up to ViewportControl
- // while keeping trace of the last 2 ContentPresenter
- do
- {
- cp = parent as ContentPresenter;
- if (cp != null)
- {
- cps[cpCount++ % 2] = cp;
- }
-
- parent = parent.GetVisualParent();
- }
- while (parent != lls && parent.GetType() != typeof(ViewportControl));
-
- // Makes sure we found a ViewportControl and at least 2 content presenters in our way.
- if (parent != lls && cpCount >= 2)
- {
- // The tilted element is the child of the ContentPresenter that is the farthest from ViewportControl.
- FrameworkElement element = cps[cpCount % 2].GetVisualChildren().FirstOrDefault() as FrameworkElement;
-
- // Starts tilt only if we found an element on which tilt is not suppressed.
- if(element != null)
- {
- object suppressTilt = element.ReadLocalValue(SuppressTiltProperty);
-
- if((suppressTilt is bool) == false || (bool)suppressTilt == false)
- {
- FrameworkElement container = e.ManipulationContainer as FrameworkElement;
-
- // Touch point relative to the element being tilted.
- Point tiltTouchPoint = container.TransformToVisual(element).Transform(e.ManipulationOrigin);
-
- // Center of the element being tilted.
- Point elementCenter = new Point(element.ActualWidth / 2, element.ActualHeight / 2);
-
- BeginTiltEffect(element, tiltTouchPoint, elementCenter, new Point(0, 0));
- }
- }
- }
- }
- #endif
-
- /// <summary>
- /// Computes the delta between the centre of an element and its
- /// container.
- /// </summary>
- /// <param name="element">The element to compare.</param>
- /// <param name="container">The element to compare against.</param>
- /// <returns>A point that represents the delta between the two centers.</returns>
- static Point GetCenterToCenterDelta(FrameworkElement element, FrameworkElement container)
- {
- Point elementCenter = new Point(element.ActualWidth / 2, element.ActualHeight / 2);
- Point containerCenter;
-
- #if WINDOWS_PHONE
-
- // Need to special-case the frame to handle different orientations.
- PhoneApplicationFrame frame = container as PhoneApplicationFrame;
- if (frame != null)
- {
- // Switch width and height in landscape mode
- if ((frame.Orientation & PageOrientation.Landscape) == PageOrientation.Landscape)
- {
- containerCenter = new Point(container.ActualHeight / 2, container.ActualWidth / 2);
- }
- else
- {
- containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2);
- }
- }
- else
- {
- containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2);
- }
- #else
- containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2);
- #endif
-
- Point transformedElementCenter = element.TransformToVisual(container).Transform(elementCenter);
- Point result = new Point(containerCenter.X - transformedElementCenter.X, containerCenter.Y - transformedElementCenter.Y);
-
- return result;
- }
-
- /// <summary>
- /// Begins the tilt effect by preparing the control and doing the
- /// initial animation.
- /// </summary>
- /// <param name="element">The element to tilt.</param>
- /// <param name="touchPoint">The touch point, in element coordinates.</param>
- /// <param name="centerPoint">The center point of the element in element
- /// coordinates.</param>
- /// <param name="centerDelta">The delta between the
- /// <paramref name="element"/>'s center and the container's center.</param>
- static void BeginTiltEffect(FrameworkElement element, Point touchPoint, Point centerPoint, Point centerDelta)
- {
- if (tiltReturnStoryboard != null)
- {
- StopTiltReturnStoryboardAndCleanup();
- }
-
- if (PrepareControlForTilt(element, centerDelta) == false)
- {
- return;
- }
-
- currentTiltElement = element;
- currentTiltElementCenter = centerPoint;
- PrepareTiltReturnStoryboard(element);
-
- ApplyTiltEffect(currentTiltElement, touchPoint, currentTiltElementCenter);
- }
-
- /// <summary>
- /// Prepares a control to be tilted by setting up a plane projection and
- /// some event handlers.
- /// </summary>
- /// <param name="element">The control that is to be tilted.</param>
- /// <param name="centerDelta">Delta between the element's center and the
- /// tilt container's.</param>
- /// <returns>true if successful; false otherwise.</returns>
- /// <remarks>
- /// This method is conservative; it will fail any attempt to tilt a
- /// control that already has a projection on it.
- /// </remarks>
- static bool PrepareControlForTilt(FrameworkElement element, Point centerDelta)
- {
- // Prevents interference with any existing transforms
- if (element.Projection != null || (element.RenderTransform != null && element.RenderTransform.GetType() != typeof(MatrixTransform)))
- {
- return false;
- }
-
- _originalCacheMode[element] = element.CacheMode;
- element.CacheMode = new BitmapCache();
-
- TranslateTransform transform = new TranslateTransform();
- transform.X = centerDelta.X;
- transform.Y = centerDelta.Y;
- element.RenderTransform = transform;
-
- PlaneProjection projection = new PlaneProjection();
- projection.GlobalOffsetX = -1 * centerDelta.X;
- projection.GlobalOffsetY = -1 * centerDelta.Y;
- element.Projection = projection;
-
- element.ManipulationDelta += TiltEffect_ManipulationDelta;
- element.ManipulationCompleted += TiltEffect_ManipulationCompleted;
-
- return true;
- }
-
- /// <summary>
- /// Removes modifications made by PrepareControlForTilt.
- /// </summary>
- /// <param name="element">THe control to be un-prepared.</param>
- /// <remarks>
- /// This method is basic; it does not do anything to detect if the
- /// control being un-prepared was previously prepared.
- /// </remarks>
- private static void RevertPrepareControlForTilt(FrameworkElement element)
- {
- element.ManipulationDelta -= TiltEffect_ManipulationDelta;
- element.ManipulationCompleted -= TiltEffect_ManipulationCompleted;
- element.Projection = null;
- element.RenderTransform = null;
-
- CacheMode original;
- if (_originalCacheMode.TryGetValue(element, out original))
- {
- element.CacheMode = original;
- _originalCacheMode.Remove(element);
- }
- else
- {
- element.CacheMode = null;
- }
- }
-
- /// <summary>
- /// Creates the tilt return storyboard (if not already created) and
- /// targets it to the projection.
- /// </summary>
- /// <param name="element">The framework element to prepare for
- /// projection.</param>
- static void PrepareTiltReturnStoryboard(FrameworkElement element)
- {
- if (tiltReturnStoryboard == null)
- {
- tiltReturnStoryboard = new Storyboard();
- tiltReturnStoryboard.Completed += TiltReturnStoryboard_Completed;
-
- tiltReturnXAnimation = new DoubleAnimation();
- Storyboard.SetTargetProperty(tiltReturnXAnimation, new PropertyPath(PlaneProjection.RotationXProperty));
- tiltReturnXAnimation.BeginTime = TiltReturnAnimationDelay;
- tiltReturnXAnimation.To = 0;
- tiltReturnXAnimation.Duration = TiltReturnAnimationDuration;
-
- tiltReturnYAnimation = new DoubleAnimation();
- Storyboard.SetTargetProperty(tiltReturnYAnimation, new PropertyPath(PlaneProjection.RotationYProperty));
- tiltReturnYAnimation.BeginTime = TiltReturnAnimationDelay;
- tiltReturnYAnimation.To = 0;
- tiltReturnYAnimation.Duration = TiltReturnAnimationDuration;
-
- tiltReturnZAnimation = new DoubleAnimation();
- Storyboard.SetTargetProperty(tiltReturnZAnimation, new PropertyPath(PlaneProjection.GlobalOffsetZProperty));
- tiltReturnZAnimation.BeginTime = TiltReturnAnimationDelay;
- tiltReturnZAnimation.To = 0;
- tiltReturnZAnimation.Duration = TiltReturnAnimationDuration;
-
- if (UseLogarithmicEase)
- {
- tiltReturnXAnimation.EasingFunction = new LogarithmicEase();
- tiltReturnYAnimation.EasingFunction = new LogarithmicEase();
- tiltReturnZAnimation.EasingFunction = new LogarithmicEase();
- }
-
- tiltReturnStoryboard.Children.Add(tiltReturnXAnimation);
- tiltReturnStoryboard.Children.Add(tiltReturnYAnimation);
- tiltReturnStoryboard.Children.Add(tiltReturnZAnimation);
- }
-
- Storyboard.SetTarget(tiltReturnXAnimation, element.Projection);
- Storyboard.SetTarget(tiltReturnYAnimation, element.Projection);
- Storyboard.SetTarget(tiltReturnZAnimation, element.Projection);
- }
-
- /// <summary>
- /// Continues a tilt effect that is currently applied to an element,
- /// presumably because the user moved their finger.
- /// </summary>
- /// <param name="element">The element being tilted.</param>
- /// <param name="e">The manipulation event args.</param>
- static void ContinueTiltEffect(FrameworkElement element, ManipulationDeltaEventArgs e)
- {
- FrameworkElement container = e.ManipulationContainer as FrameworkElement;
- if (container == null || element == null)
- {
- return;
- }
-
- Point tiltTouchPoint = container.TransformToVisual(element).Transform(e.ManipulationOrigin);
-
- // If touch moved outside bounds of element, then pause the tilt
- // (but don't cancel it)
- if (new Rect(0, 0, currentTiltElement.ActualWidth, currentTiltElement.ActualHeight).Contains(tiltTouchPoint) != true)
- {
- PauseTiltEffect();
- }
- else
- {
- // Apply the updated tilt effect
- ApplyTiltEffect(currentTiltElement, tiltTouchPoint, currentTiltElementCenter);
- }
- }
-
- /// <summary>
- /// Ends the tilt effect by playing the animation.
- /// </summary>
- /// <param name="element">The element being tilted.</param>
- private static void EndTiltEffect(FrameworkElement element)
- {
- if (element != null)
- {
- element.ManipulationCompleted -= TiltEffect_ManipulationCompleted;
- element.ManipulationDelta -= TiltEffect_ManipulationDelta;
- }
-
- if (tiltReturnStoryboard != null)
- {
- wasPauseAnimation = false;
- if (tiltReturnStoryboard.GetCurrentState() != ClockState.Active)
- {
- tiltReturnStoryboard.Begin();
- }
- }
- else
- {
- StopTiltReturnStoryboardAndCleanup();
- }
- }
-
- /// <summary>
- /// Handler for the storyboard complete event.
- /// </summary>
- /// <param name="sender">sender of the event.</param>
- /// <param name="e">event args.</param>
- private static void TiltReturnStoryboard_Completed(object sender, EventArgs e)
- {
- if (wasPauseAnimation)
- {
- ResetTiltEffect(currentTiltElement);
- }
- else
- {
- StopTiltReturnStoryboardAndCleanup();
- }
- }
-
- /// <summary>
- /// Resets the tilt effect on the control, making it appear 'normal'
- /// again.
- /// </summary>
- /// <param name="element">The element to reset the tilt on.</param>
- /// <remarks>
- /// This method doesn't turn off the tilt effect or cancel any current
- /// manipulation; it just temporarily cancels the effect.
- /// </remarks>
- private static void ResetTiltEffect(FrameworkElement element)
- {
- PlaneProjection projection = element.Projection as PlaneProjection;
- projection.RotationY = 0;
- projection.RotationX = 0;
- projection.GlobalOffsetZ = 0;
- }
-
- /// <summary>
- /// Stops the tilt effect and release resources applied to the currently
- /// tilted control.
- /// </summary>
- private static void StopTiltReturnStoryboardAndCleanup()
- {
- if (tiltReturnStoryboard != null)
- {
- tiltReturnStoryboard.Stop();
- }
-
- if (currentTiltElement != null)
- {
- RevertPrepareControlForTilt(currentTiltElement);
- currentTiltElement = null;
- }
- }
-
- /// <summary>
- /// Pauses the tilt effect so that the control returns to the 'at rest'
- /// position, but doesn't stop the tilt effect (handlers are still
- /// attached).
- /// </summary>
- private static void PauseTiltEffect()
- {
- if ((tiltReturnStoryboard != null) && !wasPauseAnimation)
- {
- tiltReturnStoryboard.Stop();
- wasPauseAnimation = true;
- tiltReturnStoryboard.Begin();
- }
- }
-
- /// <summary>
- /// Resets the storyboard to not running.
- /// </summary>
- private static void ResetTiltReturnStoryboard()
- {
- tiltReturnStoryboard.Stop();
- wasPauseAnimation = false;
- }
-
- /// <summary>
- /// Applies the tilt effect to the control.
- /// </summary>
- /// <param name="element">the control to tilt.</param>
- /// <param name="touchPoint">The touch point, in the container's
- /// coordinates.</param>
- /// <param name="centerPoint">The center point of the container.</param>
- private static void ApplyTiltEffect(FrameworkElement element, Point touchPoint, Point centerPoint)
- {
- // Stop any active animation
- ResetTiltReturnStoryboard();
-
- // Get relative point of the touch in percentage of container size
- Point normalizedPoint = new Point(
- Math.Min(Math.Max(touchPoint.X / (centerPoint.X * 2), 0), 1),
- Math.Min(Math.Max(touchPoint.Y / (centerPoint.Y * 2), 0), 1));
-
- if (double.IsNaN(normalizedPoint.X) || double.IsNaN(normalizedPoint.Y))
- {
- return;
- }
-
- // Shell values
- double xMagnitude = Math.Abs(normalizedPoint.X - 0.5);
- double yMagnitude = Math.Abs(normalizedPoint.Y - 0.5);
- double xDirection = -Math.Sign(normalizedPoint.X - 0.5);
- double yDirection = Math.Sign(normalizedPoint.Y - 0.5);
- double angleMagnitude = xMagnitude + yMagnitude;
- double xAngleContribution = xMagnitude + yMagnitude > 0 ? xMagnitude / (xMagnitude + yMagnitude) : 0;
-
- double angle = angleMagnitude * MaxAngle * 180 / Math.PI;
- double depression = (1 - angleMagnitude) * MaxDepression;
-
- // RotationX and RotationY are the angles of rotations about the x-
- // or y-*axis*; to achieve a rotation in the x- or y-*direction*, we
- // need to swap the two. That is, a rotation to the left about the
- // y-axis is a rotation to the left in the x-direction, and a
- // rotation up about the x-axis is a rotation up in the y-direction.
- PlaneProjection projection = element.Projection as PlaneProjection;
- projection.RotationY = angle * xAngleContribution * xDirection;
- projection.RotationX = angle * (1 - xAngleContribution) * yDirection;
- projection.GlobalOffsetZ = -depression;
- }
-
- #endregion
-
- #region Custom easing function
-
- /// <summary>
- /// Provides an easing function for the tilt return.
- /// </summary>
- private class LogarithmicEase : EasingFunctionBase
- {
- /// <summary>
- /// Computes the easing function.
- /// </summary>
- /// <param name="normalizedTime">The time.</param>
- /// <returns>The eased value.</returns>
- protected override double EaseInCore(double normalizedTime)
- {
- // ln(t + 1) / ln(2)
- return Math.Log(normalizedTime + 1) / 0.693147181;
- }
- }
-
- #endregion
- }
-
- /// <summary>
- /// Couple of simple helpers for walking the visual tree.
- /// </summary>
- static class TreeHelpers
- {
- /// <summary>
- /// Gets the ancestors of the element, up to the root.
- /// </summary>
- /// <param name="node">The element to start from.</param>
- /// <returns>An enumerator of the ancestors.</returns>
- public static IEnumerable<FrameworkElement> GetVisualAncestors(this FrameworkElement node)
- {
- FrameworkElement parent = node.GetVisualParent();
- while (parent != null)
- {
- yield return parent;
- parent = parent.GetVisualParent();
- }
- }
-
- /// <summary>
- /// Gets the visual parent of the element.
- /// </summary>
- /// <param name="node">The element to check.</param>
- /// <returns>The visual parent.</returns>
- public static FrameworkElement GetVisualParent(this FrameworkElement node)
- {
- return VisualTreeHelper.GetParent(node) as FrameworkElement;
- }
- }
- }