PageRenderTime 45ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://bitbucket.org/jeremejevs/milk-manager
C# | 663 lines | 354 code | 95 blank | 214 comment | 45 complexity | c8ee938eececd7858ea0fee0c34fa99a 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.Windows;
  9. using System.Windows.Controls;
  10. using System.Windows.Input;
  11. using System.Windows.Media;
  12. using System.Windows.Media.Animation;
  13. namespace Microsoft.Phone.Controls
  14. {
  15. /// <summary>
  16. /// Provides attached properties to make FrameworkElements inside
  17. /// PivotItems responsive to SelectionChanged events by Pivots.
  18. /// The result is a 'slide in' effect added to the selected elements.
  19. /// </summary>
  20. /// <QualityBand>Preview</QualityBand>
  21. public partial class SlideInEffect : DependencyObject
  22. {
  23. /// <summary>
  24. /// The proportional offset by which each line index
  25. /// gets translated on the X-axis.
  26. /// </summary>
  27. private const double ProportionalOffset = 50.0;
  28. /// <summary>
  29. /// The percentage of the total translation that plays
  30. /// using the exponential interpolation.
  31. /// </summary>
  32. private const double ExponentialInterpolationWeight = 0.90;
  33. /// <summary>
  34. /// Time in milliseconds at which the linear translation begins.
  35. /// </summary>
  36. private const double BeginTime = 350.0;
  37. /// <summary>
  38. /// Time in milliseconds at which the storyboard's interpolation
  39. /// changes from linear to exponential.
  40. /// </summary>
  41. private const double BreakTime = 420.0;
  42. /// <summary>
  43. /// Time in milliseconds at which the exponential translation ends.
  44. /// </summary>
  45. private const double EndTime = 1050.0;
  46. /// <summary>
  47. /// The easing function that defines the exponential interpolation
  48. /// of the storyboard.
  49. /// </summary>
  50. private static readonly ExponentialEase SlideInExponentialEase = new ExponentialEase() { EasingMode = EasingMode.EaseOut, Exponent = 5 };
  51. /// <summary>
  52. /// The property path used to map the animation's target property
  53. /// to the X property of a translate transform.
  54. /// </summary>
  55. private static readonly PropertyPath XPropertyPath = new PropertyPath("X");
  56. /// <summary>
  57. /// Identifies whether there was a SelectionChanged event
  58. /// triggered by a pivot.
  59. /// </summary>
  60. private static bool _selectionChanged;
  61. /// <summary>
  62. /// Identifies whether there was a ManipulationStarted event
  63. /// triggered by a pivot.
  64. /// </summary>
  65. private static bool _manipulatedStarted;
  66. /// <summary>
  67. /// Private manager that represents a correlation between Pivots
  68. /// and the number of indexed elements it contains.
  69. /// </summary>
  70. private static Dictionary<Pivot, int> _pivotsToElementCounters = new Dictionary<Pivot, int>();
  71. /// <summary>
  72. /// Private manager that represents a correlation between PivotItems
  73. /// and the indexed elements it contains.
  74. /// </summary>
  75. private static Dictionary<PivotItem, List<FrameworkElement>> _pivotItemsToElements = new Dictionary<PivotItem, List<FrameworkElement>>();
  76. #region LineIndex DependencyProperty
  77. /// <summary>
  78. /// Gets the line index of the specified dependency object.
  79. /// </summary>
  80. /// <param name="obj">The dependency object.</param>
  81. /// <returns>The line index.</returns>
  82. [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")]
  83. public static int GetLineIndex(DependencyObject obj)
  84. {
  85. return (int)obj.GetValue(LineIndexProperty);
  86. }
  87. /// <summary>
  88. /// Sets the line index of the specified dependency object.
  89. /// </summary>
  90. /// <param name="obj">The dependency object.</param>
  91. /// <param name="value">The line index.</param>
  92. [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")]
  93. public static void SetLineIndex(DependencyObject obj, int value)
  94. {
  95. obj.SetValue(LineIndexProperty, value);
  96. }
  97. /// <summary>
  98. /// Identifies the line index of the current element,
  99. /// which is proportional to its initial offset before sliding in.
  100. /// </summary>
  101. public static readonly DependencyProperty LineIndexProperty =
  102. DependencyProperty.RegisterAttached("LineIndex", typeof(int), typeof(SlideInEffect), new PropertyMetadata(-1, OnLineIndexPropertyChanged));
  103. /// <summary>
  104. /// Modify the subscription of the dependency object
  105. /// to the private managers based on the line index value.
  106. /// </summary>
  107. /// <param name="obj">The dependency object.</param>
  108. /// <param name="e">The event arguments.</param>
  109. private static void OnLineIndexPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
  110. {
  111. FrameworkElement target = obj as FrameworkElement;
  112. if (target == null)
  113. {
  114. throw new InvalidOperationException("The dependency object must be a framework element.");
  115. }
  116. int index = (int)e.NewValue;
  117. if (index < 0)
  118. {
  119. // Dettach event handlers.
  120. if (SlideInEffect.GetHasEventsAttached(target))
  121. {
  122. target.Loaded -= Target_Loaded;
  123. target.Unloaded -= Target_Unloaded;
  124. SlideInEffect.SetHasEventsAttached(target, false);
  125. }
  126. UnsubscribeFrameworkElement(target);
  127. }
  128. else
  129. {
  130. // Attach event handlers.
  131. if (!SlideInEffect.GetHasEventsAttached(target))
  132. {
  133. target.Loaded += Target_Loaded;
  134. target.Unloaded += Target_Unloaded;
  135. SlideInEffect.SetHasEventsAttached(target, true);
  136. }
  137. SubscribeFrameworkElement(target);
  138. }
  139. }
  140. #endregion
  141. #region ParentPivot DependencyProperty
  142. /// <summary>
  143. /// Gets the parent pivot of the specified dependency object.
  144. /// </summary>
  145. /// <param name="obj">The dependency object.</param>
  146. /// <returns>The pivot.</returns>
  147. private static Pivot GetParentPivot(DependencyObject obj)
  148. {
  149. return (Pivot)obj.GetValue(ParentPivotProperty);
  150. }
  151. /// <summary>
  152. /// Sets the parent pivot of the specified dependency object.
  153. /// </summary>
  154. /// <param name="obj">The dependency object.</param>
  155. /// <param name="value">The pivot.</param>
  156. private static void SetParentPivot(DependencyObject obj, Pivot value)
  157. {
  158. obj.SetValue(ParentPivotProperty, value);
  159. }
  160. /// <summary>
  161. /// Identifies the ParentPivot dependency property.
  162. /// </summary>
  163. private static readonly DependencyProperty ParentPivotProperty =
  164. DependencyProperty.RegisterAttached("ParentPivot", typeof(Pivot), typeof(SlideInEffect), new PropertyMetadata(null, OnParentPivotPropertyChanged));
  165. /// <summary>
  166. /// Manages subscription to a pivot.
  167. /// </summary>
  168. /// <param name="obj">The dependency object.</param>
  169. /// <param name="e">The event arguments.</param>
  170. private static void OnParentPivotPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
  171. {
  172. Pivot oldPivot = (Pivot)e.OldValue;
  173. Pivot newPivot = (Pivot)e.NewValue;
  174. if (newPivot != null)
  175. {
  176. if (!_pivotsToElementCounters.ContainsKey(newPivot))
  177. {
  178. // Attach event handlers to the parent Pivot.
  179. newPivot.SelectionChanged += Pivot_SelectionChanged;
  180. newPivot.ManipulationStarted += Pivot_ManipulationStarted;
  181. newPivot.ManipulationCompleted += Pivot_ManipulationCompleted;
  182. _pivotsToElementCounters.Add(newPivot, 1);
  183. }
  184. else
  185. {
  186. _pivotsToElementCounters[newPivot]++;
  187. }
  188. }
  189. else
  190. {
  191. if (_pivotsToElementCounters.ContainsKey(oldPivot))
  192. {
  193. int count = --(_pivotsToElementCounters[oldPivot]);
  194. if (count == 0)
  195. {
  196. // Dettach event handlers from the parent Pivot.
  197. oldPivot.SelectionChanged -= Pivot_SelectionChanged;
  198. oldPivot.ManipulationStarted -= Pivot_ManipulationStarted;
  199. oldPivot.ManipulationCompleted -= Pivot_ManipulationCompleted;
  200. _pivotsToElementCounters.Remove(oldPivot);
  201. }
  202. }
  203. }
  204. }
  205. #endregion
  206. #region ParentPivotItem DependencyProperty
  207. /// <summary>
  208. /// Gets the parent pivot item of the specified dependency object.
  209. /// </summary>
  210. /// <param name="obj">The dependency object.</param>
  211. /// <returns>The pivot item.</returns>
  212. private static PivotItem GetParentPivotItem(DependencyObject obj)
  213. {
  214. return (PivotItem)obj.GetValue(ParentPivotItemProperty);
  215. }
  216. /// <summary>
  217. /// Sets the parent pivot item of the specified dependency object.
  218. /// </summary>
  219. /// <param name="obj">The depedency object.</param>
  220. /// <param name="value">The pivot item.</param>
  221. private static void SetParentPivotItem(DependencyObject obj, PivotItem value)
  222. {
  223. obj.SetValue(ParentPivotItemProperty, value);
  224. }
  225. /// <summary>
  226. /// Identifies the ParentPivotItem dependency property.
  227. /// </summary>
  228. private static readonly DependencyProperty ParentPivotItemProperty =
  229. DependencyProperty.RegisterAttached("ParentPivotItem", typeof(PivotItem), typeof(SlideInEffect), new PropertyMetadata(null, OnParentPivotItemPropertyChanged));
  230. /// <summary>
  231. /// Manages subscription to a pivot item.
  232. /// </summary>
  233. /// <param name="obj">The dependency object.</param>
  234. /// <param name="e">The event arguments.</param>
  235. private static void OnParentPivotItemPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
  236. {
  237. FrameworkElement target = (FrameworkElement)obj;
  238. PivotItem oldPivotItem = (PivotItem)e.OldValue;
  239. PivotItem newPivotItem = (PivotItem)e.NewValue;
  240. List<FrameworkElement> elements;
  241. if (newPivotItem != null)
  242. {
  243. if(!_pivotItemsToElements.TryGetValue(newPivotItem, out elements))
  244. {
  245. elements = new List<FrameworkElement>();
  246. _pivotItemsToElements.Add(newPivotItem, elements);
  247. }
  248. elements.Add(target);
  249. }
  250. else
  251. {
  252. if(_pivotItemsToElements.TryGetValue(oldPivotItem, out elements))
  253. {
  254. if (elements.Contains(target))
  255. {
  256. elements.Remove(target);
  257. }
  258. if (elements.Count == 0)
  259. {
  260. _pivotItemsToElements.Remove(oldPivotItem);
  261. }
  262. }
  263. }
  264. }
  265. #endregion
  266. #region AttachedTransform DependencyProperty
  267. /// <summary>
  268. /// Gets the attached transform of the specified dependency object.
  269. /// </summary>
  270. /// <param name="obj">The dependency object.</param>
  271. /// <returns>The attached transform.</returns>
  272. private static TranslateTransform GetAttachedTransform(DependencyObject obj)
  273. {
  274. return (TranslateTransform)obj.GetValue(AttachedTransformProperty);
  275. }
  276. /// <summary>
  277. /// Sets the attached transform of the specified dependency object.
  278. /// </summary>
  279. /// <param name="obj">The dependency object.</param>
  280. /// <param name="value">The attached transform.</param>
  281. private static void SetAttachedTransform(DependencyObject obj, TranslateTransform value)
  282. {
  283. obj.SetValue(AttachedTransformProperty, value);
  284. }
  285. /// <summary>
  286. /// Identifies the AttachedTransform dependency property.
  287. /// </summary>
  288. private static readonly DependencyProperty AttachedTransformProperty =
  289. DependencyProperty.RegisterAttached("AttachedTransform", typeof(TranslateTransform), typeof(SlideInEffect), new PropertyMetadata(null));
  290. #endregion
  291. #region IsSubscribed DependencyProperty
  292. /// <summary>
  293. /// Gets whether the specified dependency object
  294. /// is subscribed to the private managers or not.
  295. /// </summary>
  296. /// <param name="obj">The dependency object.</param>
  297. /// <returns>The value.</returns>
  298. private static bool GetIsSubscribed(DependencyObject obj)
  299. {
  300. return (bool)obj.GetValue(IsSubscribedProperty);
  301. }
  302. /// <summary>
  303. /// Sets whether the specified dependency object
  304. /// is subscribed to the private managers or not.
  305. /// </summary>
  306. /// <param name="obj">The dependency object.</param>
  307. /// <param name="value">The value.</param>
  308. private static void SetIsSubscribed(DependencyObject obj, bool value)
  309. {
  310. obj.SetValue(IsSubscribedProperty, value);
  311. }
  312. /// <summary>
  313. /// Identifies the IsSubscribed dependency property.
  314. /// </summary>
  315. private static readonly DependencyProperty IsSubscribedProperty =
  316. DependencyProperty.RegisterAttached("IsSubscribed", typeof(bool), typeof(SlideInEffect), new PropertyMetadata(false, null));
  317. #endregion
  318. #region HasEventsAttached DependencyProperty
  319. /// <summary>
  320. /// Gets whether the specified dependency object
  321. /// has events attached to it or not.
  322. /// </summary>
  323. /// <param name="obj">The dependency object.</param>
  324. /// <returns>The value.</returns>
  325. private static bool GetHasEventsAttached(DependencyObject obj)
  326. {
  327. return (bool)obj.GetValue(HasEventsAttachedProperty);
  328. }
  329. /// <summary>
  330. /// Sets whether the specified dependency object
  331. /// has events attached to it or not.
  332. /// </summary>
  333. /// <param name="obj">The dependency object.</param>
  334. /// <param name="value">The value.</param>
  335. private static void SetHasEventsAttached(DependencyObject obj, bool value)
  336. {
  337. obj.SetValue(HasEventsAttachedProperty, value);
  338. }
  339. /// <summary>
  340. /// Identifies the HasEventsAttached dependency property.
  341. /// </summary>
  342. private static readonly DependencyProperty HasEventsAttachedProperty =
  343. DependencyProperty.RegisterAttached("HasEventsAttached", typeof(bool), typeof(SlideInEffect), new PropertyMetadata(false));
  344. #endregion
  345. /// <summary>
  346. /// Called when an element gets loaded.
  347. /// </summary>
  348. /// <param name="sender">The event sender.</param>
  349. /// <param name="e">The event information.</param>
  350. private static void Target_Loaded(object sender, RoutedEventArgs e)
  351. {
  352. SubscribeFrameworkElement((FrameworkElement)sender);
  353. }
  354. /// <summary>
  355. /// Called when an element gets unloaded.
  356. /// </summary>
  357. /// <param name="sender">The event sender.</param>
  358. /// <param name="e">The event information.</param>
  359. private static void Target_Unloaded(object sender, RoutedEventArgs e)
  360. {
  361. UnsubscribeFrameworkElement((FrameworkElement)sender);
  362. }
  363. /// <summary>
  364. /// Sets a flag indicating that a SelectionChanged event ocurred.
  365. /// </summary>
  366. /// <param name="sender">The event sender.</param>
  367. /// <param name="e">The event information.</param>
  368. private static void Pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
  369. {
  370. _selectionChanged = true;
  371. }
  372. /// <summary>
  373. /// Sets a flag indicating that a ManipulationStarted event ocurred.
  374. /// </summary>
  375. /// <param name="sender">The event sender.</param>
  376. /// <param name="e">The event information.</param>
  377. private static void Pivot_ManipulationStarted(object sender, ManipulationStartedEventArgs e)
  378. {
  379. _manipulatedStarted = true;
  380. }
  381. /// <summary>
  382. /// Animates the corresponding elements.
  383. /// </summary>
  384. /// <param name="sender">The event sender.</param>
  385. /// <param name="e">The event information.</param>
  386. private static void Pivot_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
  387. {
  388. if (_selectionChanged && _manipulatedStarted)
  389. {
  390. Pivot pivot = (Pivot)sender;
  391. PivotItem pivotItem = pivot.ItemContainerGenerator.ContainerFromItem(pivot.SelectedItem) as PivotItem;
  392. if (pivotItem == null)
  393. {
  394. return;
  395. }
  396. List<FrameworkElement> elements;
  397. if(!_pivotItemsToElements.TryGetValue(pivotItem, out elements))
  398. {
  399. return;
  400. }
  401. Storyboard storyboard = new Storyboard();
  402. foreach (FrameworkElement target in elements)
  403. {
  404. if (target != null)
  405. {
  406. if (IsOnScreen(target))
  407. {
  408. bool fromRight = (e.TotalManipulation.Translation.X <= 0);
  409. ComposeStoryboard(target, fromRight, ref storyboard);
  410. }
  411. }
  412. }
  413. storyboard.Completed += (s1, e1) =>
  414. {
  415. storyboard.Stop();
  416. };
  417. storyboard.Begin();
  418. }
  419. _selectionChanged = _manipulatedStarted = false;
  420. }
  421. /// <summary>
  422. /// Subscribes an element to the private managers.
  423. /// </summary>
  424. /// <param name="target">The framework element.</param>
  425. private static void SubscribeFrameworkElement(FrameworkElement target)
  426. {
  427. if (!SlideInEffect.GetIsSubscribed(target))
  428. {
  429. // Find the parent Pivot and PivotItem.
  430. Pivot pivot, pTemp;
  431. PivotItem pivotItem, iTemp;
  432. DependencyObject parent = VisualTreeHelper.GetParent(target);
  433. pTemp = null;
  434. pivotItem = iTemp = null;
  435. while ((pTemp == null) && (parent != null))
  436. {
  437. pTemp = parent as Pivot;
  438. iTemp = parent as PivotItem;
  439. if (iTemp != null)
  440. {
  441. pivotItem = iTemp;
  442. }
  443. parent = VisualTreeHelper.GetParent(parent as DependencyObject);
  444. }
  445. if (parent == null || pivotItem == null)
  446. {
  447. return;
  448. }
  449. else
  450. {
  451. pivot = pTemp;
  452. }
  453. AttachTransform(target);
  454. SlideInEffect.SetParentPivot(target, pivot);
  455. SlideInEffect.SetParentPivotItem(target, pivotItem);
  456. SlideInEffect.SetIsSubscribed(target, true);
  457. }
  458. }
  459. /// <summary>
  460. /// Unsubscribes an element from the private managers.
  461. /// </summary>
  462. /// <param name="target">The framework element.</param>
  463. private static void UnsubscribeFrameworkElement(FrameworkElement target)
  464. {
  465. // If element is subscribed, unsubscribe.
  466. if (SlideInEffect.GetIsSubscribed(target))
  467. {
  468. SlideInEffect.SetParentPivot(target, null);
  469. SlideInEffect.SetParentPivotItem(target, null);
  470. SlideInEffect.SetIsSubscribed(target, false);
  471. }
  472. }
  473. /// <summary>
  474. /// Adds an animation corresponding to an specific framework element.
  475. /// Thus, the storyboard can be composed piece by piece.
  476. /// </summary>
  477. /// <param name="element">The framework element.</param>
  478. /// <param name="leftToRight">
  479. /// Indicates whether the animation should go
  480. /// from left to right or viceversa.
  481. /// </param>
  482. /// <param name="storyboard">A reference to the storyboard.</param>
  483. private static void ComposeStoryboard(FrameworkElement element, bool leftToRight, ref Storyboard storyboard)
  484. {
  485. double xPosition = SlideInEffect.GetLineIndex(element) * ProportionalOffset;
  486. double from = leftToRight ? xPosition : -xPosition;
  487. TranslateTransform translateTransform = SlideInEffect.GetAttachedTransform(element);
  488. DoubleAnimationUsingKeyFrames animation = new DoubleAnimationUsingKeyFrames();
  489. LinearDoubleKeyFrame keyFrame1 = new LinearDoubleKeyFrame();
  490. keyFrame1.KeyTime = TimeSpan.Zero;
  491. keyFrame1.Value = from;
  492. animation.KeyFrames.Add(keyFrame1);
  493. LinearDoubleKeyFrame keyFrame2 = new LinearDoubleKeyFrame();
  494. keyFrame2.KeyTime = TimeSpan.FromMilliseconds(BeginTime);
  495. keyFrame2.Value = from;
  496. animation.KeyFrames.Add(keyFrame2);
  497. LinearDoubleKeyFrame keyFrame3 = new LinearDoubleKeyFrame();
  498. keyFrame3.KeyTime = TimeSpan.FromMilliseconds(BreakTime);
  499. keyFrame3.Value = from * ExponentialInterpolationWeight;
  500. animation.KeyFrames.Add(keyFrame3);
  501. EasingDoubleKeyFrame keyFrame4 = new EasingDoubleKeyFrame();
  502. keyFrame4.KeyTime = TimeSpan.FromMilliseconds(EndTime);
  503. keyFrame4.Value = 0.0;
  504. keyFrame4.EasingFunction = SlideInExponentialEase;
  505. animation.KeyFrames.Add(keyFrame4);
  506. Storyboard.SetTarget(animation, translateTransform);
  507. Storyboard.SetTargetProperty(animation, XPropertyPath);
  508. storyboard.Children.Add(animation);
  509. }
  510. /// <summary>
  511. /// Indicates whether the specified framework element
  512. /// is within the bounds of the application's root visual.
  513. /// </summary>
  514. /// <param name="element">The framework element.</param>
  515. /// <returns>
  516. /// True if the rectangular bounds of the framework element
  517. /// are completely outside the bounds of the application's root visual.
  518. /// </returns>
  519. private static bool IsOnScreen(FrameworkElement element)
  520. {
  521. PhoneApplicationFrame root = Application.Current.RootVisual as PhoneApplicationFrame;
  522. if (root == null)
  523. {
  524. return false;
  525. }
  526. GeneralTransform generalTransform;
  527. double height = root.ActualHeight;
  528. try
  529. {
  530. generalTransform = element.TransformToVisual(root);
  531. }
  532. catch (ArgumentException)
  533. {
  534. return false;
  535. }
  536. Rect bounds = new Rect(
  537. generalTransform.Transform(new Point(0, 0)),
  538. generalTransform.Transform(new Point(element.ActualWidth, element.ActualHeight)));
  539. return (bounds.Bottom > 0 && bounds.Top < height);
  540. }
  541. /// <summary>
  542. /// Attach the translate transform that is used
  543. /// for the slide in effect to a framework element.
  544. /// </summary>
  545. /// <param name="element">The framework element.</param>
  546. private static void AttachTransform(FrameworkElement element)
  547. {
  548. Transform originalTransform = element.RenderTransform;
  549. TranslateTransform translateTransform = SlideInEffect.GetAttachedTransform(element);
  550. if (translateTransform == null)
  551. {
  552. translateTransform = new TranslateTransform() { X = 0 };
  553. if (originalTransform == null)
  554. {
  555. element.RenderTransform = translateTransform;
  556. }
  557. else
  558. {
  559. TransformGroup transformGroup = new TransformGroup();
  560. transformGroup.Children.Add(originalTransform);
  561. transformGroup.Children.Add(translateTransform);
  562. element.RenderTransform = transformGroup;
  563. }
  564. SlideInEffect.SetAttachedTransform(element, translateTransform);
  565. }
  566. }
  567. }
  568. }