PageRenderTime 49ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/toolkit/Microsoft.Phone.Controls.Toolkit/Tilt/TiltEffect.cs

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