ScrollTracker.cs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. //-----------------------------------------------------------------------------
  2. // ScrollTracker.cs
  3. //
  4. // Microsoft XNA Community Game Platform
  5. // Copyright (C) Microsoft Corporation. All rights reserved.
  6. //-----------------------------------------------------------------------------
  7. using System;
  8. using Microsoft.Xna.Framework;
  9. using Microsoft.Xna.Framework.Input.Touch;
  10. namespace UserInterfaceSample.Controls
  11. {
  12. /// <remarks>
  13. /// ScrollTracker watches the touchpanel for drag and flick gestures, and computes the appropriate
  14. /// position and scale for a viewport within a larger canvas to emulate the behavior of the Silverlight
  15. /// scrolling controls.
  16. ///
  17. /// This class only handles computation of the view rectangle; how that rectangle is used for rendering
  18. /// is up tot the client code.
  19. /// </remarks>
  20. public class ScrollTracker
  21. {
  22. /// Handling TouchPanel.EnabledGestures
  23. /// --------------------------
  24. /// This class watches for HorizontalDrag, DragComplete, and Flick gestures. However, it cannot just
  25. /// set TouchPanel.EnabledGestures, because that would most likely interfere with gestures needed
  26. /// elsewhere in the application. So it just exposes a const 'HandledGestures' field and relies on
  27. /// the client code to set TouchPanel.EnabledGestures appropriately.
  28. public const GestureType GesturesNeeded = GestureType.Flick | GestureType.VerticalDrag | GestureType.DragComplete;
  29. #region Tuning constants
  30. // How far the user is allowed to drag past the "real" border
  31. const float SpringMaxDrag = 400;
  32. // How far the display moves when dragged to 'SpringMaxDrag'
  33. const float SpringMaxOffset = SpringMaxDrag / 3;
  34. const float SpringReturnRate = 0.1f;
  35. const float SpringReturnMin = 2.0f;
  36. const float Deceleration = 500.0f; // pixels/second^2
  37. const float MaxVelocity = 2000.0f; // pixels/second
  38. #endregion
  39. /// <summary>
  40. /// A rectangle (set by the client code) giving the area of the canvas we want to scroll around in.
  41. /// Normally taller or wider than the viewport.
  42. /// </summary>
  43. public Rectangle CanvasRect;
  44. /// <summary>
  45. /// A rectangle describing the currently visible view area. Normally the caller will set this once
  46. /// to set the viewport size and initial position, and from then on let ScrollTracker move it around;
  47. /// however, you can set it at any time to change the position or size of the viewport.
  48. /// </summary>
  49. public Rectangle ViewRect;
  50. /// <summary>
  51. /// FullCanvasRect is the same as CanvasRect, except it's extended to be at least as large as ViewRect.
  52. /// The is the true canvas area that we scroll around in.
  53. /// </summary>
  54. public Rectangle FullCanvasRect
  55. {
  56. get
  57. {
  58. Rectangle value = CanvasRect;
  59. if (value.Width < ViewRect.Width)
  60. value.Width = ViewRect.Width;
  61. if (value.Height < ViewRect.Height)
  62. value.Height = ViewRect.Height;
  63. return value;
  64. }
  65. }
  66. // Current flick velocity.
  67. Vector2 Velocity;
  68. // What the view offset would be if we didn't do any clamping
  69. Vector2 ViewOrigin;
  70. // What the ViewOrigin would be if we didn't do any clamping
  71. Vector2 UnclampedViewOrigin;
  72. // True if we're currently tracking a drag gesture
  73. public bool IsTracking { get; private set; }
  74. public bool IsMoving
  75. {
  76. get { return IsTracking || Velocity.X != 0 || Velocity.Y != 0 || !FullCanvasRect.Contains(ViewRect); }
  77. }
  78. public ScrollTracker()
  79. {
  80. ViewRect = new Rectangle { Width = TouchPanel.DisplayWidth, Height = TouchPanel.DisplayHeight };
  81. CanvasRect = ViewRect;
  82. }
  83. // This must be called manually each tick that the ScrollTracker is active.
  84. public void Update(GameTime gametime)
  85. {
  86. // Apply velocity and clamping
  87. float dt = (float)gametime.ElapsedGameTime.TotalSeconds;
  88. Vector2 viewMin = new Vector2 { X = 0, Y = 0 };
  89. Vector2 viewMax = new Vector2 { X = CanvasRect.Width - ViewRect.Width, Y = CanvasRect.Height - ViewRect.Height };
  90. viewMax.X = Math.Max(viewMin.X, viewMax.X);
  91. viewMax.Y = Math.Max(viewMin.Y, viewMax.Y);
  92. if (IsTracking)
  93. {
  94. // ViewOrigin is a soft-clamped version of UnclampedOffset
  95. ViewOrigin.X = SoftClamp(UnclampedViewOrigin.X, viewMin.X, viewMax.X);
  96. ViewOrigin.Y = SoftClamp(UnclampedViewOrigin.Y, viewMin.Y, viewMax.Y);
  97. }
  98. else
  99. {
  100. // Apply velocity
  101. ApplyVelocity(dt, ref ViewOrigin.X, ref Velocity.X, viewMin.X, viewMax.X);
  102. ApplyVelocity(dt, ref ViewOrigin.Y, ref Velocity.Y, viewMin.Y, viewMax.Y);
  103. }
  104. ViewRect.X = (int)ViewOrigin.X;
  105. ViewRect.Y = (int)ViewOrigin.Y;
  106. }
  107. // This must be called manually each tick that the ScrollTracker is active.
  108. public void HandleInput(InputState input)
  109. {
  110. // Turn on tracking as soon as we seen any kind of touch. We can't use gestures for this
  111. // because no gesture data is returned on the initial touch. We have to be careful to
  112. // pick out only 'Pressed' locations, because TouchState can return other events a frame
  113. // *after* we've seen GestureType.Flick or GestureType.DragComplete.
  114. if (!IsTracking)
  115. {
  116. for (int i = 0; i < input.TouchState.Count; i++)
  117. {
  118. if (input.TouchState[i].State == TouchLocationState.Pressed)
  119. {
  120. Velocity = Vector2.Zero;
  121. UnclampedViewOrigin = ViewOrigin;
  122. IsTracking = true;
  123. break;
  124. }
  125. }
  126. }
  127. foreach (GestureSample sample in input.Gestures)
  128. {
  129. switch (sample.GestureType)
  130. {
  131. case GestureType.VerticalDrag:
  132. UnclampedViewOrigin.Y -= sample.Delta.Y;
  133. break;
  134. case GestureType.Flick:
  135. // Only respond to mostly-vertical flicks
  136. if (Math.Abs(sample.Delta.X) < Math.Abs(sample.Delta.Y))
  137. {
  138. IsTracking = false;
  139. Velocity = -sample.Delta;
  140. }
  141. break;
  142. case GestureType.DragComplete:
  143. IsTracking = false;
  144. break;
  145. }
  146. }
  147. }
  148. // If x is within the range (min,max), just return x. Otherwise return a value outside of (min,max)
  149. // but only partway to where x is. This is used to get the partial-overdrag effect at the edges
  150. // of the list.
  151. static float SoftClamp(float x, float min, float max)
  152. {
  153. if (x < min)
  154. {
  155. return Math.Max(x - min, -SpringMaxDrag) * SpringMaxOffset / SpringMaxDrag + min;
  156. }
  157. if (x > max)
  158. {
  159. return Math.Min(x - max, SpringMaxDrag) * SpringMaxOffset / SpringMaxDrag + max;
  160. }
  161. return x;
  162. }
  163. // Integrate the given position and velocity over a timespan. Min and max give the
  164. // soft limits that the position is allowed to move to.
  165. void ApplyVelocity(float dt, ref float x, ref float v, float min, float max)
  166. {
  167. float x0 = x;
  168. x += v * dt;
  169. // Apply deceleration to gradually reduce velocity
  170. v = MathHelper.Clamp(v, -MaxVelocity, MaxVelocity);
  171. v = Math.Max(Math.Abs(v) - dt * Deceleration, 0.0f) * Math.Sign(v);
  172. // If we've scrolled past the edge, gradually reset to edge
  173. if (x < min)
  174. {
  175. x = Math.Min(x + (min - x) * SpringReturnRate + SpringReturnMin, min);
  176. v = 0;
  177. }
  178. if (x > max)
  179. {
  180. x = Math.Max(x - (x - max) * SpringReturnRate - SpringReturnMin, max);
  181. v = 0;
  182. }
  183. }
  184. }
  185. }