PageFlipTracker.cs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. //-----------------------------------------------------------------------------
  2. // PageFlipTracker.cs
  3. //
  4. // Microsoft XNA Community Game Platform
  5. // Copyright (C) Microsoft Corporation. All rights reserved.
  6. //-----------------------------------------------------------------------------
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Text;
  10. using Microsoft.Xna.Framework.Input.Touch;
  11. using Microsoft.Xna.Framework;
  12. using System.Diagnostics;
  13. namespace UserInterfaceSample.Controls
  14. {
  15. /// <remarks>
  16. /// PageFlipTracker watches the touchpanel for drag and flick gestures, and computes the appropriate
  17. /// offsets for flipping horizontally through a multi-page display. It is used by PageFlipControl
  18. /// to handle the scroll logic. PageFlipTracker is broken out into a separate class so that XNA apps
  19. /// with their own scheme for UI controls can still use it to handle the scroll logic.
  20. ///
  21. /// Handling TouchPanel.EnabledGestures
  22. /// --------------------------
  23. /// This class watches for HorizontalDrag, DragComplete, and Flick gestures. However, it cannot just
  24. /// set TouchPanel.EnabledGestures, because that would most likely interfere with gestures needed
  25. /// elsewhere in the application. So it just exposes a const 'HandledGestures' field and relies on
  26. /// the client code to set TouchPanel.EnabledGestures appropriately.
  27. ///
  28. /// Handling screen rotation
  29. /// ------------------------
  30. /// This class uses TouchPanel.DisplayWidth to determine the width of the screen. DisplayWidth
  31. /// is automatically updated by the system when the orientation changes.
  32. /// </remarks>
  33. public class PageFlipTracker
  34. {
  35. public const GestureType GesturesNeeded = GestureType.Flick | GestureType.HorizontalDrag | GestureType.DragComplete;
  36. #region Tuning options
  37. public static TimeSpan FlipDuration = TimeSpan.FromSeconds(0.3);
  38. /// <summary>
  39. /// Exponent on curve to make page flips and springbacks start quickly and slow to a stop.
  40. ///
  41. /// Interpolation formula is (1-TransitionAlpha)^TransitionExponent, where
  42. /// TransitionAlpha animates uniformly from 0 to 1 over timespan FlipDuration.
  43. /// </summary>
  44. public static double FlipExponent = 3.0;
  45. /// <summary>
  46. /// By default, this many pixels of the next page will be visible
  47. /// on the right-hand edge of the screen, unless the current page's
  48. /// contentWidth is too large.
  49. /// </summary>
  50. public static int PreviewMargin = 20;
  51. /// <summary>
  52. /// How far (as a fraction of the total screen width) you have
  53. /// to drag a screen past its edge to trigger a flip by dragging.
  54. /// </summary>
  55. public static float DragToFlipTheshold = 1.0f / 3.0f;
  56. #endregion
  57. #region Private fields
  58. // Time stamp when transition started
  59. private DateTime flipStartTime;
  60. // Horizontal offset at start of current transition. Target offset is always 0.
  61. private float flipStartOffset;
  62. #endregion
  63. #region Properties
  64. // Current active page. If we're in a transition, this is the page we're transitioning TO.
  65. public int CurrentPage { get; private set; }
  66. // Offset in pixels to render currentPage at. If this is positive, other
  67. // pages may be visible to the left.
  68. //
  69. // This is always relative to the current page.
  70. public float CurrentPageOffset { get; private set; }
  71. public bool IsLeftPageVisible
  72. {
  73. get
  74. {
  75. return PageWidthList.Count >= 2 && CurrentPageOffset > 0;
  76. }
  77. }
  78. public bool IsRightPageVisible
  79. {
  80. get
  81. {
  82. return PageWidthList.Count >= 2 && CurrentPageOffset + EffectivePageWidth(CurrentPage) <= TouchPanel.DisplayWidth;
  83. }
  84. }
  85. // True if we're currently in a transition
  86. public bool InFlip { get; private set; }
  87. // Alpha value that animates from 0 to 1 during a spring. Will be 1 when not springing.
  88. public float FlipAlpha { get; private set; }
  89. // PageWidthList contains the width in pixels of each page. Pages can be added or removed at any time by
  90. // changing this list.
  91. public List<int> PageWidthList = new List<int>();
  92. #endregion
  93. public PageFlipTracker()
  94. {
  95. }
  96. public int EffectivePageWidth(int page)
  97. {
  98. int displayWidth = TouchPanel.DisplayWidth - PreviewMargin;
  99. return Math.Max(displayWidth, PageWidthList[page]);
  100. }
  101. // Update is called once per frame.
  102. public void Update()
  103. {
  104. if (InFlip)
  105. {
  106. TimeSpan transitionClock = DateTime.Now - flipStartTime;
  107. if (transitionClock >= FlipDuration)
  108. {
  109. EndFlip();
  110. }
  111. else
  112. {
  113. double f = transitionClock.TotalSeconds / FlipDuration.TotalSeconds;
  114. f = Math.Max(f, 0.0); // this shouldn't happen, but just in case time goes crazy
  115. FlipAlpha = (float)(1 - Math.Pow(1 - f, FlipExponent));
  116. CurrentPageOffset = flipStartOffset * (1 - FlipAlpha);
  117. }
  118. }
  119. }
  120. public void HandleInput(InputState input)
  121. {
  122. foreach (GestureSample sample in input.Gestures)
  123. {
  124. switch (sample.GestureType)
  125. {
  126. case GestureType.HorizontalDrag:
  127. CurrentPageOffset += sample.Delta.X;
  128. flipStartOffset = CurrentPageOffset;
  129. break;
  130. case GestureType.DragComplete:
  131. if (!InFlip)
  132. {
  133. if (CurrentPageOffset < -TouchPanel.DisplayWidth * DragToFlipTheshold)
  134. {
  135. // flip to next page
  136. BeginFlip(1);
  137. }
  138. else if (CurrentPageOffset + TouchPanel.DisplayWidth * (1 - DragToFlipTheshold) > EffectivePageWidth(CurrentPage))
  139. {
  140. // flip to previous page
  141. BeginFlip(-1);
  142. }
  143. else
  144. {
  145. // "snap back" effect when you drag a little and let go
  146. BeginFlip(0);
  147. }
  148. }
  149. break;
  150. case GestureType.Flick:
  151. // Only respond to mostly-horizontal flicks
  152. if (Math.Abs(sample.Delta.X) > Math.Abs(sample.Delta.Y))
  153. {
  154. if (sample.Delta.X > 0)
  155. {
  156. BeginFlip(-1);
  157. }
  158. else
  159. {
  160. BeginFlip(1);
  161. }
  162. }
  163. break;
  164. }
  165. }
  166. }
  167. void BeginFlip(int pageDelta)
  168. {
  169. if(PageWidthList.Count == 0)
  170. return;
  171. int pageFrom = CurrentPage;
  172. CurrentPage = (CurrentPage + pageDelta + PageWidthList.Count) % PageWidthList.Count;
  173. if (pageDelta > 0)
  174. {
  175. // going to next page; offset starts out large
  176. CurrentPageOffset += EffectivePageWidth(pageFrom);
  177. }
  178. else if(pageDelta < 0)
  179. {
  180. // going to previous page; offset starts out negative
  181. CurrentPageOffset -= EffectivePageWidth(CurrentPage);
  182. }
  183. InFlip = true;
  184. FlipAlpha = 0;
  185. flipStartOffset = CurrentPageOffset;
  186. flipStartTime = DateTime.Now;
  187. }
  188. // FIXME: private
  189. void EndFlip()
  190. {
  191. InFlip = false;
  192. FlipAlpha = 1;
  193. CurrentPageOffset = 0;
  194. }
  195. }
  196. }