ScrollingPanel.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. using System;
  2. using System.Windows;
  3. using System.Windows.Controls;
  4. using System.Windows.Controls.Primitives;
  5. using System.Windows.Media;
  6. using System.Windows.Media.Animation;
  7. namespace MediaBrowser.UI.Controls
  8. {
  9. /// <summary>
  10. /// This started from:
  11. /// http://www.switchonthecode.com/tutorials/wpf-tutorial-implementing-iscrollinfo
  12. /// Then, after implementing this, content was being displayed in stack panel like manner.
  13. /// I then reviewed the source code of ScrollContentPresenter and updated MeasureOverride and ArrangeOverride to match.
  14. /// </summary>
  15. public class ScrollingPanel : Grid, IScrollInfo
  16. {
  17. /// <summary>
  18. /// The infinite size
  19. /// </summary>
  20. private static Size InfiniteSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
  21. /// <summary>
  22. /// The line size
  23. /// </summary>
  24. private const double LineSize = 16;
  25. /// <summary>
  26. /// The wheel size
  27. /// </summary>
  28. private const double WheelSize = 3 * LineSize;
  29. /// <summary>
  30. /// The _ offset
  31. /// </summary>
  32. private Vector _Offset;
  33. /// <summary>
  34. /// The _ extent
  35. /// </summary>
  36. private Size _Extent;
  37. /// <summary>
  38. /// The _ viewport
  39. /// </summary>
  40. private Size _Viewport;
  41. /// <summary>
  42. /// The _ animation length
  43. /// </summary>
  44. private TimeSpan _AnimationLength = TimeSpan.FromMilliseconds(125);
  45. /// <summary>
  46. /// When overridden in a derived class, measures the size in layout required for child elements and determines a size for the <see cref="T:System.Windows.FrameworkElement" />-derived class.
  47. /// </summary>
  48. /// <param name="availableSize">The available size that this element can give to child elements. Infinity can be specified as a value to indicate that the element will size to whatever content is available.</param>
  49. /// <returns>The size that this element determines it needs during layout, based on its calculations of child element sizes.</returns>
  50. protected override Size MeasureOverride(Size availableSize)
  51. {
  52. if (Children == null || Children.Count == 0)
  53. {
  54. return availableSize;
  55. }
  56. var constraint2 = availableSize;
  57. if (CanHorizontallyScroll)
  58. {
  59. constraint2.Width = double.PositiveInfinity;
  60. }
  61. if (CanVerticallyScroll)
  62. {
  63. constraint2.Height = double.PositiveInfinity;
  64. }
  65. var uiElement = Children[0];
  66. uiElement.Measure(constraint2);
  67. var size = uiElement.DesiredSize;
  68. VerifyScrollData(availableSize, size);
  69. size.Width = Math.Min(availableSize.Width, size.Width);
  70. size.Height = Math.Min(availableSize.Height, size.Height);
  71. return size;
  72. }
  73. /// <summary>
  74. /// Arranges the content of a <see cref="T:System.Windows.Controls.Grid" /> element.
  75. /// </summary>
  76. /// <param name="arrangeSize">Specifies the size this <see cref="T:System.Windows.Controls.Grid" /> element should use to arrange its child elements.</param>
  77. /// <returns><see cref="T:System.Windows.Size" /> that represents the arranged size of this Grid element and its children.</returns>
  78. protected override Size ArrangeOverride(Size arrangeSize)
  79. {
  80. this.VerifyScrollData(arrangeSize, _Extent);
  81. if (this.Children == null || this.Children.Count == 0)
  82. {
  83. return arrangeSize;
  84. }
  85. TranslateTransform trans = null;
  86. var uiElement = Children[0];
  87. var finalRect = new Rect(uiElement.DesiredSize);
  88. // ScrollContentPresenter sets these to 0 - current offset
  89. // We need to set it to zero in order to make the animation work
  90. finalRect.X = 0;
  91. finalRect.Y = 0;
  92. finalRect.Width = Math.Max(finalRect.Width, arrangeSize.Width);
  93. finalRect.Height = Math.Max(finalRect.Height, arrangeSize.Height);
  94. trans = uiElement.RenderTransform as TranslateTransform;
  95. if (trans == null)
  96. {
  97. uiElement.RenderTransformOrigin = new Point(0, 0);
  98. trans = new TranslateTransform();
  99. uiElement.RenderTransform = trans;
  100. }
  101. uiElement.Arrange(finalRect);
  102. trans.BeginAnimation(TranslateTransform.XProperty,
  103. GetAnimation(0 - HorizontalOffset),
  104. HandoffBehavior.Compose);
  105. trans.BeginAnimation(TranslateTransform.YProperty,
  106. GetAnimation(0 - VerticalOffset),
  107. HandoffBehavior.Compose);
  108. return arrangeSize;
  109. }
  110. /// <summary>
  111. /// Gets the animation.
  112. /// </summary>
  113. /// <param name="toValue">To value.</param>
  114. /// <returns>DoubleAnimation.</returns>
  115. private DoubleAnimation GetAnimation(double toValue)
  116. {
  117. var animation = new DoubleAnimation(toValue, _AnimationLength);
  118. animation.EasingFunction = new ExponentialEase { EasingMode = EasingMode.EaseInOut };
  119. return animation;
  120. }
  121. #region Movement Methods
  122. /// <summary>
  123. /// Scrolls down within content by one logical unit.
  124. /// </summary>
  125. public void LineDown()
  126. { SetVerticalOffset(VerticalOffset + LineSize); }
  127. /// <summary>
  128. /// Scrolls up within content by one logical unit.
  129. /// </summary>
  130. public void LineUp()
  131. { SetVerticalOffset(VerticalOffset - LineSize); }
  132. /// <summary>
  133. /// Scrolls left within content by one logical unit.
  134. /// </summary>
  135. public void LineLeft()
  136. { SetHorizontalOffset(HorizontalOffset - LineSize); }
  137. /// <summary>
  138. /// Scrolls right within content by one logical unit.
  139. /// </summary>
  140. public void LineRight()
  141. { SetHorizontalOffset(HorizontalOffset + LineSize); }
  142. /// <summary>
  143. /// Scrolls down within content after a user clicks the wheel button on a mouse.
  144. /// </summary>
  145. public void MouseWheelDown()
  146. { SetVerticalOffset(VerticalOffset + WheelSize); }
  147. /// <summary>
  148. /// Scrolls up within content after a user clicks the wheel button on a mouse.
  149. /// </summary>
  150. public void MouseWheelUp()
  151. { SetVerticalOffset(VerticalOffset - WheelSize); }
  152. /// <summary>
  153. /// Scrolls left within content after a user clicks the wheel button on a mouse.
  154. /// </summary>
  155. public void MouseWheelLeft()
  156. { SetHorizontalOffset(HorizontalOffset - WheelSize); }
  157. /// <summary>
  158. /// Scrolls right within content after a user clicks the wheel button on a mouse.
  159. /// </summary>
  160. public void MouseWheelRight()
  161. { SetHorizontalOffset(HorizontalOffset + WheelSize); }
  162. /// <summary>
  163. /// Scrolls down within content by one page.
  164. /// </summary>
  165. public void PageDown()
  166. { SetVerticalOffset(VerticalOffset + ViewportHeight); }
  167. /// <summary>
  168. /// Scrolls up within content by one page.
  169. /// </summary>
  170. public void PageUp()
  171. { SetVerticalOffset(VerticalOffset - ViewportHeight); }
  172. /// <summary>
  173. /// Scrolls left within content by one page.
  174. /// </summary>
  175. public void PageLeft()
  176. { SetHorizontalOffset(HorizontalOffset - ViewportWidth); }
  177. /// <summary>
  178. /// Scrolls right within content by one page.
  179. /// </summary>
  180. public void PageRight()
  181. { SetHorizontalOffset(HorizontalOffset + ViewportWidth); }
  182. #endregion
  183. /// <summary>
  184. /// Gets or sets a <see cref="T:System.Windows.Controls.ScrollViewer" /> element that controls scrolling behavior.
  185. /// </summary>
  186. /// <value>The scroll owner.</value>
  187. /// <returns>A <see cref="T:System.Windows.Controls.ScrollViewer" /> element that controls scrolling behavior. This property has no default value.</returns>
  188. public ScrollViewer ScrollOwner { get; set; }
  189. /// <summary>
  190. /// Gets or sets a value that indicates whether scrolling on the horizontal axis is possible.
  191. /// </summary>
  192. /// <value><c>true</c> if this instance can horizontally scroll; otherwise, <c>false</c>.</value>
  193. /// <returns>true if scrolling is possible; otherwise, false. This property has no default value.</returns>
  194. public bool CanHorizontallyScroll { get; set; }
  195. /// <summary>
  196. /// Gets or sets a value that indicates whether scrolling on the vertical axis is possible.
  197. /// </summary>
  198. /// <value><c>true</c> if this instance can vertically scroll; otherwise, <c>false</c>.</value>
  199. /// <returns>true if scrolling is possible; otherwise, false. This property has no default value.</returns>
  200. public bool CanVerticallyScroll { get; set; }
  201. /// <summary>
  202. /// Gets the vertical size of the extent.
  203. /// </summary>
  204. /// <value>The height of the extent.</value>
  205. /// <returns>A <see cref="T:System.Double" /> that represents, in device independent pixels, the vertical size of the extent.This property has no default value.</returns>
  206. public double ExtentHeight
  207. { get { return _Extent.Height; } }
  208. /// <summary>
  209. /// Gets the horizontal size of the extent.
  210. /// </summary>
  211. /// <value>The width of the extent.</value>
  212. /// <returns>A <see cref="T:System.Double" /> that represents, in device independent pixels, the horizontal size of the extent. This property has no default value.</returns>
  213. public double ExtentWidth
  214. { get { return _Extent.Width; } }
  215. /// <summary>
  216. /// Gets the horizontal offset of the scrolled content.
  217. /// </summary>
  218. /// <value>The horizontal offset.</value>
  219. /// <returns>A <see cref="T:System.Double" /> that represents, in device independent pixels, the horizontal offset. This property has no default value.</returns>
  220. public double HorizontalOffset
  221. { get { return _Offset.X; } }
  222. /// <summary>
  223. /// Gets the vertical offset of the scrolled content.
  224. /// </summary>
  225. /// <value>The vertical offset.</value>
  226. /// <returns>A <see cref="T:System.Double" /> that represents, in device independent pixels, the vertical offset of the scrolled content. Valid values are between zero and the <see cref="P:System.Windows.Controls.Primitives.IScrollInfo.ExtentHeight" /> minus the <see cref="P:System.Windows.Controls.Primitives.IScrollInfo.ViewportHeight" />. This property has no default value.</returns>
  227. public double VerticalOffset
  228. { get { return _Offset.Y; } }
  229. /// <summary>
  230. /// Gets the vertical size of the viewport for this content.
  231. /// </summary>
  232. /// <value>The height of the viewport.</value>
  233. /// <returns>A <see cref="T:System.Double" /> that represents, in device independent pixels, the vertical size of the viewport for this content. This property has no default value.</returns>
  234. public double ViewportHeight
  235. { get { return _Viewport.Height; } }
  236. /// <summary>
  237. /// Gets the horizontal size of the viewport for this content.
  238. /// </summary>
  239. /// <value>The width of the viewport.</value>
  240. /// <returns>A <see cref="T:System.Double" /> that represents, in device independent pixels, the horizontal size of the viewport for this content. This property has no default value.</returns>
  241. public double ViewportWidth
  242. { get { return _Viewport.Width; } }
  243. /// <summary>
  244. /// Forces content to scroll until the coordinate space of a <see cref="T:System.Windows.Media.Visual" /> object is visible.
  245. /// </summary>
  246. /// <param name="visual">A <see cref="T:System.Windows.Media.Visual" /> that becomes visible.</param>
  247. /// <param name="rectangle">A bounding rectangle that identifies the coordinate space to make visible.</param>
  248. /// <returns>A <see cref="T:System.Windows.Rect" /> that is visible.</returns>
  249. public Rect MakeVisible(Visual visual, Rect rectangle)
  250. {
  251. if (rectangle.IsEmpty || visual == null
  252. || visual == this || !base.IsAncestorOf(visual))
  253. { return Rect.Empty; }
  254. rectangle = visual.TransformToAncestor(this).TransformBounds(rectangle);
  255. //rectangle.Inflate(50, 50);
  256. rectangle.Scale(1.2, 1.2);
  257. Rect viewRect = new Rect(HorizontalOffset,
  258. VerticalOffset, ViewportWidth, ViewportHeight);
  259. rectangle.X += viewRect.X;
  260. rectangle.Y += viewRect.Y;
  261. viewRect.X = CalculateNewScrollOffset(viewRect.Left,
  262. viewRect.Right, rectangle.Left, rectangle.Right);
  263. viewRect.Y = CalculateNewScrollOffset(viewRect.Top,
  264. viewRect.Bottom, rectangle.Top, rectangle.Bottom);
  265. SetHorizontalOffset(viewRect.X);
  266. SetVerticalOffset(viewRect.Y);
  267. rectangle.Intersect(viewRect);
  268. rectangle.X -= viewRect.X;
  269. rectangle.Y -= viewRect.Y;
  270. return rectangle;
  271. }
  272. /// <summary>
  273. /// Calculates the new scroll offset.
  274. /// </summary>
  275. /// <param name="topView">The top view.</param>
  276. /// <param name="bottomView">The bottom view.</param>
  277. /// <param name="topChild">The top child.</param>
  278. /// <param name="bottomChild">The bottom child.</param>
  279. /// <returns>System.Double.</returns>
  280. private static double CalculateNewScrollOffset(double topView,
  281. double bottomView, double topChild, double bottomChild)
  282. {
  283. bool offBottom = topChild < topView && bottomChild < bottomView;
  284. bool offTop = bottomChild > bottomView && topChild > topView;
  285. bool tooLarge = (bottomChild - topChild) > (bottomView - topView);
  286. if (!offBottom && !offTop)
  287. { return topView; } //Don't do anything, already in view
  288. if ((offBottom && !tooLarge) || (offTop && tooLarge))
  289. { return topChild; }
  290. return (bottomChild - (bottomView - topView));
  291. }
  292. /// <summary>
  293. /// Verifies the scroll data.
  294. /// </summary>
  295. /// <param name="viewport">The viewport.</param>
  296. /// <param name="extent">The extent.</param>
  297. protected void VerifyScrollData(Size viewport, Size extent)
  298. {
  299. if (double.IsInfinity(viewport.Width))
  300. { viewport.Width = extent.Width; }
  301. if (double.IsInfinity(viewport.Height))
  302. { viewport.Height = extent.Height; }
  303. _Extent = extent;
  304. _Viewport = viewport;
  305. _Offset.X = Math.Max(0,
  306. Math.Min(_Offset.X, ExtentWidth - ViewportWidth));
  307. _Offset.Y = Math.Max(0,
  308. Math.Min(_Offset.Y, ExtentHeight - ViewportHeight));
  309. if (ScrollOwner != null)
  310. { ScrollOwner.InvalidateScrollInfo(); }
  311. }
  312. /// <summary>
  313. /// Sets the amount of horizontal offset.
  314. /// </summary>
  315. /// <param name="offset">The degree to which content is horizontally offset from the containing viewport.</param>
  316. public void SetHorizontalOffset(double offset)
  317. {
  318. offset = Math.Max(0,
  319. Math.Min(offset, ExtentWidth - ViewportWidth));
  320. if (!offset.Equals(_Offset.X))
  321. {
  322. _Offset.X = offset;
  323. InvalidateArrange();
  324. }
  325. }
  326. /// <summary>
  327. /// Sets the amount of vertical offset.
  328. /// </summary>
  329. /// <param name="offset">The degree to which content is vertically offset from the containing viewport.</param>
  330. public void SetVerticalOffset(double offset)
  331. {
  332. offset = Math.Max(0,
  333. Math.Min(offset, ExtentHeight - ViewportHeight));
  334. if (!offset.Equals(_Offset.Y))
  335. {
  336. _Offset.Y = offset;
  337. InvalidateArrange();
  338. }
  339. }
  340. }
  341. }