PageRenderTime 59ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-toolkit/Microsoft.Phone.Controls.Toolkit/Effects/TiltEffect.cs

https://bitbucket.org/jeremejevs/milk-manager
C# | 806 lines | 443 code | 101 blank | 262 comment | 73 complexity | df34526e9b4b0263ea5493201949b1f2 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.Collections.Generic;
  7. using System.Diagnostics.CodeAnalysis;
  8. using System.Linq;
  9. using System.Windows;
  10. using System.Windows.Controls;
  11. using System.Windows.Controls.Primitives;
  12. using System.Windows.Input;
  13. using System.Windows.Media;
  14. using System.Windows.Media.Animation;
  15. #if WINDOWS_PHONE
  16. #endif
  17. namespace Microsoft.Phone.Controls
  18. {
  19. /// <summary>
  20. /// This code provides attached properties for adding a 'tilt' effect to all
  21. /// controls within a container.
  22. /// </summary>
  23. /// <QualityBand>Preview</QualityBand>
  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. typeof(ListPicker),
  107. typeof(MenuItem),
  108. typeof(LongListSelector)
  109. };
  110. }
  111. #endregion
  112. #region Dependency properties
  113. /// <summary>
  114. /// Whether the tilt effect is enabled on a container (and all its
  115. /// children).
  116. /// </summary>
  117. public static readonly DependencyProperty IsTiltEnabledProperty = DependencyProperty.RegisterAttached(
  118. "IsTiltEnabled",
  119. typeof(bool),
  120. typeof(TiltEffect),
  121. new PropertyMetadata(OnIsTiltEnabledChanged)
  122. );
  123. /// <summary>
  124. /// Gets the IsTiltEnabled dependency property from an object.
  125. /// </summary>
  126. /// <param name="source">The object to get the property from.</param>
  127. /// <returns>The property's value.</returns>
  128. [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")]
  129. public static bool GetIsTiltEnabled(DependencyObject source)
  130. {
  131. return (bool)source.GetValue(IsTiltEnabledProperty);
  132. }
  133. /// <summary>
  134. /// Sets the IsTiltEnabled dependency property on an object.
  135. /// </summary>
  136. /// <param name="source">The object to set the property on.</param>
  137. /// <param name="value">The value to set.</param>
  138. [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")]
  139. public static void SetIsTiltEnabled(DependencyObject source, bool value)
  140. {
  141. source.SetValue(IsTiltEnabledProperty, value);
  142. }
  143. /// <summary>
  144. /// Suppresses the tilt effect on a single control that would otherwise
  145. /// be tilted.
  146. /// </summary>
  147. public static readonly DependencyProperty SuppressTiltProperty = DependencyProperty.RegisterAttached(
  148. "SuppressTilt",
  149. typeof(bool),
  150. typeof(TiltEffect),
  151. null
  152. );
  153. /// <summary>
  154. /// Gets the SuppressTilt dependency property from an object.
  155. /// </summary>
  156. /// <param name="source">The object to get the property from.</param>
  157. /// <returns>The property's value.</returns>
  158. [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")]
  159. public static bool GetSuppressTilt(DependencyObject source)
  160. {
  161. return (bool)source.GetValue(SuppressTiltProperty);
  162. }
  163. /// <summary>
  164. /// Sets the SuppressTilt dependency property from an object.
  165. /// </summary>
  166. /// <param name="source">The object to get the property from.</param>
  167. /// <param name="value">The property's value.</param>
  168. [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")]
  169. public static void SetSuppressTilt(DependencyObject source, bool value)
  170. {
  171. source.SetValue(SuppressTiltProperty, value);
  172. }
  173. /// <summary>
  174. /// Property change handler for the IsTiltEnabled dependency property.
  175. /// </summary>
  176. /// <param name="target">The element that the property is atteched to.</param>
  177. /// <param name="args">Event arguments.</param>
  178. /// <remarks>
  179. /// Adds or removes event handlers from the element that has been
  180. /// (un)registered for tilting.
  181. /// </remarks>
  182. static void OnIsTiltEnabledChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
  183. {
  184. FrameworkElement fe = target as FrameworkElement;
  185. if (fe != null)
  186. {
  187. // Add / remove the event handler if necessary
  188. if ((bool)args.NewValue == true)
  189. {
  190. fe.ManipulationStarted += TiltEffect_ManipulationStarted;
  191. }
  192. else
  193. {
  194. fe.ManipulationStarted -= TiltEffect_ManipulationStarted;
  195. }
  196. }
  197. }
  198. #endregion
  199. #region Top-level manipulation event handlers
  200. /// <summary>
  201. /// Event handler for ManipulationStarted.
  202. /// </summary>
  203. /// <param name="sender">sender of the event - this will be the tilt
  204. /// container (eg, entire page).</param>
  205. /// <param name="e">Event arguments.</param>
  206. private static void TiltEffect_ManipulationStarted(object sender, ManipulationStartedEventArgs e)
  207. {
  208. TryStartTiltEffect(sender as FrameworkElement, e);
  209. }
  210. /// <summary>
  211. /// Event handler for ManipulationDelta
  212. /// </summary>
  213. /// <param name="sender">sender of the event - this will be the tilting
  214. /// object (eg a button).</param>
  215. /// <param name="e">Event arguments.</param>
  216. private static void TiltEffect_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
  217. {
  218. ContinueTiltEffect(sender as FrameworkElement, e);
  219. }
  220. /// <summary>
  221. /// Event handler for ManipulationCompleted.
  222. /// </summary>
  223. /// <param name="sender">sender of the event - this will be the tilting
  224. /// object (eg a button).</param>
  225. /// <param name="e">Event arguments.</param>
  226. static void TiltEffect_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
  227. {
  228. EndTiltEffect(currentTiltElement);
  229. }
  230. #endregion
  231. #region Core tilt logic
  232. /// <summary>
  233. /// Checks if the manipulation should cause a tilt, and if so starts the
  234. /// tilt effect.
  235. /// </summary>
  236. /// <param name="source">The source of the manipulation (the tilt
  237. /// container, eg entire page).</param>
  238. /// <param name="e">The args from the ManipulationStarted event.</param>
  239. static void TryStartTiltEffect(FrameworkElement source, ManipulationStartedEventArgs e)
  240. {
  241. foreach (FrameworkElement ancestor in (e.OriginalSource as FrameworkElement).GetVisualAncestors())
  242. {
  243. foreach (Type t in TiltableItems)
  244. {
  245. if (t.IsAssignableFrom(ancestor.GetType()))
  246. {
  247. FrameworkElement elementSuppressingTilt = null;
  248. // Look up the tree to find either an explicit DO or DO NOT suppress.
  249. if (ancestor.ReadLocalValue(SuppressTiltProperty) is bool)
  250. {
  251. elementSuppressingTilt = ancestor;
  252. }
  253. else
  254. {
  255. elementSuppressingTilt = ancestor.GetVisualAncestors().FirstOrDefault(x => x.ReadLocalValue(SuppressTiltProperty) is bool);
  256. }
  257. if (elementSuppressingTilt != null && (bool)elementSuppressingTilt.ReadLocalValue(SuppressTiltProperty) == true)
  258. {
  259. continue;
  260. }
  261. else
  262. {
  263. #if WP8
  264. if (t == typeof(LongListSelector))
  265. {
  266. StartTiltEffectOnLLS((LongListSelector)ancestor, e);
  267. }
  268. else
  269. {
  270. #endif
  271. // Use first child of the control, so that we can add transforms and not
  272. // impact any transforms on the control itself.
  273. FrameworkElement element = VisualTreeHelper.GetChild(ancestor, 0) as FrameworkElement;
  274. FrameworkElement container = e.ManipulationContainer as FrameworkElement;
  275. if (element == null || container == null)
  276. return;
  277. // Touch point relative to the element being tilted.
  278. Point tiltTouchPoint = container.TransformToVisual(element).Transform(e.ManipulationOrigin);
  279. // Center of the element being tilted.
  280. Point elementCenter = new Point(element.ActualWidth / 2, element.ActualHeight / 2);
  281. // Camera adjustment.
  282. Point centerToCenterDelta = GetCenterToCenterDelta(element, source);
  283. BeginTiltEffect(element, tiltTouchPoint, elementCenter, centerToCenterDelta);
  284. #if WP8
  285. }
  286. #endif
  287. return;
  288. }
  289. }
  290. }
  291. }
  292. }
  293. #if WP8
  294. /// <summary>
  295. /// Starts the tilt effect on LLS items or sticky header.
  296. /// </summary>
  297. private static void StartTiltEffectOnLLS(LongListSelector lls, ManipulationStartedEventArgs e)
  298. {
  299. FrameworkElement parent = (FrameworkElement)e.OriginalSource;
  300. ContentPresenter[] cps = new ContentPresenter[2];
  301. ContentPresenter cp;
  302. int cpCount = 0;
  303. // Starts from OriginalSource and goes up to ViewportControl
  304. // while keeping trace of the last 2 ContentPresenter
  305. do
  306. {
  307. cp = parent as ContentPresenter;
  308. if (cp != null)
  309. {
  310. cps[cpCount++ % 2] = cp;
  311. }
  312. parent = parent.GetVisualParent();
  313. }
  314. while (parent != lls && parent.GetType() != typeof(ViewportControl));
  315. // Makes sure we found a ViewportControl and at least 2 content presenters in our way.
  316. if (parent != lls && cpCount >= 2)
  317. {
  318. // The tilted element is the child of the ContentPresenter that is the farthest from ViewportControl.
  319. FrameworkElement element = cps[cpCount % 2].GetVisualChildren().FirstOrDefault() as FrameworkElement;
  320. // Starts tilt only if we found an element on which tilt is not suppressed.
  321. if(element != null)
  322. {
  323. object suppressTilt = element.ReadLocalValue(SuppressTiltProperty);
  324. if((suppressTilt is bool) == false || (bool)suppressTilt == false)
  325. {
  326. FrameworkElement container = e.ManipulationContainer as FrameworkElement;
  327. // Touch point relative to the element being tilted.
  328. Point tiltTouchPoint = container.TransformToVisual(element).Transform(e.ManipulationOrigin);
  329. // Center of the element being tilted.
  330. Point elementCenter = new Point(element.ActualWidth / 2, element.ActualHeight / 2);
  331. BeginTiltEffect(element, tiltTouchPoint, elementCenter, new Point(0, 0));
  332. }
  333. }
  334. }
  335. }
  336. #endif
  337. /// <summary>
  338. /// Computes the delta between the centre of an element and its
  339. /// container.
  340. /// </summary>
  341. /// <param name="element">The element to compare.</param>
  342. /// <param name="container">The element to compare against.</param>
  343. /// <returns>A point that represents the delta between the two centers.</returns>
  344. static Point GetCenterToCenterDelta(FrameworkElement element, FrameworkElement container)
  345. {
  346. Point elementCenter = new Point(element.ActualWidth / 2, element.ActualHeight / 2);
  347. Point containerCenter;
  348. #if WINDOWS_PHONE
  349. // Need to special-case the frame to handle different orientations.
  350. PhoneApplicationFrame frame = container as PhoneApplicationFrame;
  351. if (frame != null)
  352. {
  353. // Switch width and height in landscape mode
  354. if ((frame.Orientation & PageOrientation.Landscape) == PageOrientation.Landscape)
  355. {
  356. containerCenter = new Point(container.ActualHeight / 2, container.ActualWidth / 2);
  357. }
  358. else
  359. {
  360. containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2);
  361. }
  362. }
  363. else
  364. {
  365. containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2);
  366. }
  367. #else
  368. containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2);
  369. #endif
  370. Point transformedElementCenter = element.TransformToVisual(container).Transform(elementCenter);
  371. Point result = new Point(containerCenter.X - transformedElementCenter.X, containerCenter.Y - transformedElementCenter.Y);
  372. return result;
  373. }
  374. /// <summary>
  375. /// Begins the tilt effect by preparing the control and doing the
  376. /// initial animation.
  377. /// </summary>
  378. /// <param name="element">The element to tilt.</param>
  379. /// <param name="touchPoint">The touch point, in element coordinates.</param>
  380. /// <param name="centerPoint">The center point of the element in element
  381. /// coordinates.</param>
  382. /// <param name="centerDelta">The delta between the
  383. /// <paramref name="element"/>'s center and the container's center.</param>
  384. static void BeginTiltEffect(FrameworkElement element, Point touchPoint, Point centerPoint, Point centerDelta)
  385. {
  386. if (tiltReturnStoryboard != null)
  387. {
  388. StopTiltReturnStoryboardAndCleanup();
  389. }
  390. if (PrepareControlForTilt(element, centerDelta) == false)
  391. {
  392. return;
  393. }
  394. currentTiltElement = element;
  395. currentTiltElementCenter = centerPoint;
  396. PrepareTiltReturnStoryboard(element);
  397. ApplyTiltEffect(currentTiltElement, touchPoint, currentTiltElementCenter);
  398. }
  399. /// <summary>
  400. /// Prepares a control to be tilted by setting up a plane projection and
  401. /// some event handlers.
  402. /// </summary>
  403. /// <param name="element">The control that is to be tilted.</param>
  404. /// <param name="centerDelta">Delta between the element's center and the
  405. /// tilt container's.</param>
  406. /// <returns>true if successful; false otherwise.</returns>
  407. /// <remarks>
  408. /// This method is conservative; it will fail any attempt to tilt a
  409. /// control that already has a projection on it.
  410. /// </remarks>
  411. static bool PrepareControlForTilt(FrameworkElement element, Point centerDelta)
  412. {
  413. // Prevents interference with any existing transforms
  414. if (element.Projection != null || (element.RenderTransform != null && element.RenderTransform.GetType() != typeof(MatrixTransform)))
  415. {
  416. return false;
  417. }
  418. _originalCacheMode[element] = element.CacheMode;
  419. element.CacheMode = new BitmapCache();
  420. TranslateTransform transform = new TranslateTransform();
  421. transform.X = centerDelta.X;
  422. transform.Y = centerDelta.Y;
  423. element.RenderTransform = transform;
  424. PlaneProjection projection = new PlaneProjection();
  425. projection.GlobalOffsetX = -1 * centerDelta.X;
  426. projection.GlobalOffsetY = -1 * centerDelta.Y;
  427. element.Projection = projection;
  428. element.ManipulationDelta += TiltEffect_ManipulationDelta;
  429. element.ManipulationCompleted += TiltEffect_ManipulationCompleted;
  430. return true;
  431. }
  432. /// <summary>
  433. /// Removes modifications made by PrepareControlForTilt.
  434. /// </summary>
  435. /// <param name="element">THe control to be un-prepared.</param>
  436. /// <remarks>
  437. /// This method is basic; it does not do anything to detect if the
  438. /// control being un-prepared was previously prepared.
  439. /// </remarks>
  440. private static void RevertPrepareControlForTilt(FrameworkElement element)
  441. {
  442. element.ManipulationDelta -= TiltEffect_ManipulationDelta;
  443. element.ManipulationCompleted -= TiltEffect_ManipulationCompleted;
  444. element.Projection = null;
  445. element.RenderTransform = null;
  446. CacheMode original;
  447. if (_originalCacheMode.TryGetValue(element, out original))
  448. {
  449. element.CacheMode = original;
  450. _originalCacheMode.Remove(element);
  451. }
  452. else
  453. {
  454. element.CacheMode = null;
  455. }
  456. }
  457. /// <summary>
  458. /// Creates the tilt return storyboard (if not already created) and
  459. /// targets it to the projection.
  460. /// </summary>
  461. /// <param name="element">The framework element to prepare for
  462. /// projection.</param>
  463. static void PrepareTiltReturnStoryboard(FrameworkElement element)
  464. {
  465. if (tiltReturnStoryboard == null)
  466. {
  467. tiltReturnStoryboard = new Storyboard();
  468. tiltReturnStoryboard.Completed += TiltReturnStoryboard_Completed;
  469. tiltReturnXAnimation = new DoubleAnimation();
  470. Storyboard.SetTargetProperty(tiltReturnXAnimation, new PropertyPath(PlaneProjection.RotationXProperty));
  471. tiltReturnXAnimation.BeginTime = TiltReturnAnimationDelay;
  472. tiltReturnXAnimation.To = 0;
  473. tiltReturnXAnimation.Duration = TiltReturnAnimationDuration;
  474. tiltReturnYAnimation = new DoubleAnimation();
  475. Storyboard.SetTargetProperty(tiltReturnYAnimation, new PropertyPath(PlaneProjection.RotationYProperty));
  476. tiltReturnYAnimation.BeginTime = TiltReturnAnimationDelay;
  477. tiltReturnYAnimation.To = 0;
  478. tiltReturnYAnimation.Duration = TiltReturnAnimationDuration;
  479. tiltReturnZAnimation = new DoubleAnimation();
  480. Storyboard.SetTargetProperty(tiltReturnZAnimation, new PropertyPath(PlaneProjection.GlobalOffsetZProperty));
  481. tiltReturnZAnimation.BeginTime = TiltReturnAnimationDelay;
  482. tiltReturnZAnimation.To = 0;
  483. tiltReturnZAnimation.Duration = TiltReturnAnimationDuration;
  484. if (UseLogarithmicEase)
  485. {
  486. tiltReturnXAnimation.EasingFunction = new LogarithmicEase();
  487. tiltReturnYAnimation.EasingFunction = new LogarithmicEase();
  488. tiltReturnZAnimation.EasingFunction = new LogarithmicEase();
  489. }
  490. tiltReturnStoryboard.Children.Add(tiltReturnXAnimation);
  491. tiltReturnStoryboard.Children.Add(tiltReturnYAnimation);
  492. tiltReturnStoryboard.Children.Add(tiltReturnZAnimation);
  493. }
  494. Storyboard.SetTarget(tiltReturnXAnimation, element.Projection);
  495. Storyboard.SetTarget(tiltReturnYAnimation, element.Projection);
  496. Storyboard.SetTarget(tiltReturnZAnimation, element.Projection);
  497. }
  498. /// <summary>
  499. /// Continues a tilt effect that is currently applied to an element,
  500. /// presumably because the user moved their finger.
  501. /// </summary>
  502. /// <param name="element">The element being tilted.</param>
  503. /// <param name="e">The manipulation event args.</param>
  504. static void ContinueTiltEffect(FrameworkElement element, ManipulationDeltaEventArgs e)
  505. {
  506. FrameworkElement container = e.ManipulationContainer as FrameworkElement;
  507. if (container == null || element == null)
  508. {
  509. return;
  510. }
  511. Point tiltTouchPoint = container.TransformToVisual(element).Transform(e.ManipulationOrigin);
  512. // If touch moved outside bounds of element, then pause the tilt
  513. // (but don't cancel it)
  514. if (new Rect(0, 0, currentTiltElement.ActualWidth, currentTiltElement.ActualHeight).Contains(tiltTouchPoint) != true)
  515. {
  516. PauseTiltEffect();
  517. }
  518. else
  519. {
  520. // Apply the updated tilt effect
  521. ApplyTiltEffect(currentTiltElement, tiltTouchPoint, currentTiltElementCenter);
  522. }
  523. }
  524. /// <summary>
  525. /// Ends the tilt effect by playing the animation.
  526. /// </summary>
  527. /// <param name="element">The element being tilted.</param>
  528. private static void EndTiltEffect(FrameworkElement element)
  529. {
  530. if (element != null)
  531. {
  532. element.ManipulationCompleted -= TiltEffect_ManipulationCompleted;
  533. element.ManipulationDelta -= TiltEffect_ManipulationDelta;
  534. }
  535. if (tiltReturnStoryboard != null)
  536. {
  537. wasPauseAnimation = false;
  538. if (tiltReturnStoryboard.GetCurrentState() != ClockState.Active)
  539. {
  540. tiltReturnStoryboard.Begin();
  541. }
  542. }
  543. else
  544. {
  545. StopTiltReturnStoryboardAndCleanup();
  546. }
  547. }
  548. /// <summary>
  549. /// Handler for the storyboard complete event.
  550. /// </summary>
  551. /// <param name="sender">sender of the event.</param>
  552. /// <param name="e">event args.</param>
  553. private static void TiltReturnStoryboard_Completed(object sender, EventArgs e)
  554. {
  555. if (wasPauseAnimation)
  556. {
  557. ResetTiltEffect(currentTiltElement);
  558. }
  559. else
  560. {
  561. StopTiltReturnStoryboardAndCleanup();
  562. }
  563. }
  564. /// <summary>
  565. /// Resets the tilt effect on the control, making it appear 'normal'
  566. /// again.
  567. /// </summary>
  568. /// <param name="element">The element to reset the tilt on.</param>
  569. /// <remarks>
  570. /// This method doesn't turn off the tilt effect or cancel any current
  571. /// manipulation; it just temporarily cancels the effect.
  572. /// </remarks>
  573. private static void ResetTiltEffect(FrameworkElement element)
  574. {
  575. PlaneProjection projection = element.Projection as PlaneProjection;
  576. projection.RotationY = 0;
  577. projection.RotationX = 0;
  578. projection.GlobalOffsetZ = 0;
  579. }
  580. /// <summary>
  581. /// Stops the tilt effect and release resources applied to the currently
  582. /// tilted control.
  583. /// </summary>
  584. private static void StopTiltReturnStoryboardAndCleanup()
  585. {
  586. if (tiltReturnStoryboard != null)
  587. {
  588. tiltReturnStoryboard.Stop();
  589. }
  590. if (currentTiltElement != null)
  591. {
  592. RevertPrepareControlForTilt(currentTiltElement);
  593. currentTiltElement = null;
  594. }
  595. }
  596. /// <summary>
  597. /// Pauses the tilt effect so that the control returns to the 'at rest'
  598. /// position, but doesn't stop the tilt effect (handlers are still
  599. /// attached).
  600. /// </summary>
  601. private static void PauseTiltEffect()
  602. {
  603. if ((tiltReturnStoryboard != null) && !wasPauseAnimation)
  604. {
  605. tiltReturnStoryboard.Stop();
  606. wasPauseAnimation = true;
  607. tiltReturnStoryboard.Begin();
  608. }
  609. }
  610. /// <summary>
  611. /// Resets the storyboard to not running.
  612. /// </summary>
  613. private static void ResetTiltReturnStoryboard()
  614. {
  615. tiltReturnStoryboard.Stop();
  616. wasPauseAnimation = false;
  617. }
  618. /// <summary>
  619. /// Applies the tilt effect to the control.
  620. /// </summary>
  621. /// <param name="element">the control to tilt.</param>
  622. /// <param name="touchPoint">The touch point, in the container's
  623. /// coordinates.</param>
  624. /// <param name="centerPoint">The center point of the container.</param>
  625. private static void ApplyTiltEffect(FrameworkElement element, Point touchPoint, Point centerPoint)
  626. {
  627. // Stop any active animation
  628. ResetTiltReturnStoryboard();
  629. // Get relative point of the touch in percentage of container size
  630. Point normalizedPoint = new Point(
  631. Math.Min(Math.Max(touchPoint.X / (centerPoint.X * 2), 0), 1),
  632. Math.Min(Math.Max(touchPoint.Y / (centerPoint.Y * 2), 0), 1));
  633. if (double.IsNaN(normalizedPoint.X) || double.IsNaN(normalizedPoint.Y))
  634. {
  635. return;
  636. }
  637. // Shell values
  638. double xMagnitude = Math.Abs(normalizedPoint.X - 0.5);
  639. double yMagnitude = Math.Abs(normalizedPoint.Y - 0.5);
  640. double xDirection = -Math.Sign(normalizedPoint.X - 0.5);
  641. double yDirection = Math.Sign(normalizedPoint.Y - 0.5);
  642. double angleMagnitude = xMagnitude + yMagnitude;
  643. double xAngleContribution = xMagnitude + yMagnitude > 0 ? xMagnitude / (xMagnitude + yMagnitude) : 0;
  644. double angle = angleMagnitude * MaxAngle * 180 / Math.PI;
  645. double depression = (1 - angleMagnitude) * MaxDepression;
  646. // RotationX and RotationY are the angles of rotations about the x-
  647. // or y-*axis*; to achieve a rotation in the x- or y-*direction*, we
  648. // need to swap the two. That is, a rotation to the left about the
  649. // y-axis is a rotation to the left in the x-direction, and a
  650. // rotation up about the x-axis is a rotation up in the y-direction.
  651. PlaneProjection projection = element.Projection as PlaneProjection;
  652. projection.RotationY = angle * xAngleContribution * xDirection;
  653. projection.RotationX = angle * (1 - xAngleContribution) * yDirection;
  654. projection.GlobalOffsetZ = -depression;
  655. }
  656. #endregion
  657. #region Custom easing function
  658. /// <summary>
  659. /// Provides an easing function for the tilt return.
  660. /// </summary>
  661. private class LogarithmicEase : EasingFunctionBase
  662. {
  663. /// <summary>
  664. /// Computes the easing function.
  665. /// </summary>
  666. /// <param name="normalizedTime">The time.</param>
  667. /// <returns>The eased value.</returns>
  668. protected override double EaseInCore(double normalizedTime)
  669. {
  670. // ln(t + 1) / ln(2)
  671. return Math.Log(normalizedTime + 1) / 0.693147181;
  672. }
  673. }
  674. #endregion
  675. }
  676. /// <summary>
  677. /// Couple of simple helpers for walking the visual tree.
  678. /// </summary>
  679. static class TreeHelpers
  680. {
  681. /// <summary>
  682. /// Gets the ancestors of the element, up to the root.
  683. /// </summary>
  684. /// <param name="node">The element to start from.</param>
  685. /// <returns>An enumerator of the ancestors.</returns>
  686. public static IEnumerable<FrameworkElement> GetVisualAncestors(this FrameworkElement node)
  687. {
  688. FrameworkElement parent = node.GetVisualParent();
  689. while (parent != null)
  690. {
  691. yield return parent;
  692. parent = parent.GetVisualParent();
  693. }
  694. }
  695. /// <summary>
  696. /// Gets the visual parent of the element.
  697. /// </summary>
  698. /// <param name="node">The element to check.</param>
  699. /// <returns>The visual parent.</returns>
  700. public static FrameworkElement GetVisualParent(this FrameworkElement node)
  701. {
  702. return VisualTreeHelper.GetParent(node) as FrameworkElement;
  703. }
  704. }
  705. }