aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.UI.Controls/ScrollingPanel.cs
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.UI.Controls/ScrollingPanel.cs')
-rw-r--r--MediaBrowser.UI.Controls/ScrollingPanel.cs404
1 files changed, 404 insertions, 0 deletions
diff --git a/MediaBrowser.UI.Controls/ScrollingPanel.cs b/MediaBrowser.UI.Controls/ScrollingPanel.cs
new file mode 100644
index 000000000..636661f54
--- /dev/null
+++ b/MediaBrowser.UI.Controls/ScrollingPanel.cs
@@ -0,0 +1,404 @@
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+
+namespace MediaBrowser.UI.Controls
+{
+ /// <summary>
+ /// This started from:
+ /// http://www.switchonthecode.com/tutorials/wpf-tutorial-implementing-iscrollinfo
+ /// Then, after implementing this, content was being displayed in stack panel like manner.
+ /// I then reviewed the source code of ScrollContentPresenter and updated MeasureOverride and ArrangeOverride to match.
+ /// </summary>
+ public class ScrollingPanel : Grid, IScrollInfo
+ {
+ /// <summary>
+ /// The infinite size
+ /// </summary>
+ private static Size InfiniteSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
+ /// <summary>
+ /// The line size
+ /// </summary>
+ private const double LineSize = 16;
+ /// <summary>
+ /// The wheel size
+ /// </summary>
+ private const double WheelSize = 3 * LineSize;
+
+ /// <summary>
+ /// The _ offset
+ /// </summary>
+ private Vector _Offset;
+ /// <summary>
+ /// The _ extent
+ /// </summary>
+ private Size _Extent;
+ /// <summary>
+ /// The _ viewport
+ /// </summary>
+ private Size _Viewport;
+
+ /// <summary>
+ /// The _ animation length
+ /// </summary>
+ private TimeSpan _AnimationLength = TimeSpan.FromMilliseconds(125);
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <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>
+ /// <returns>The size that this element determines it needs during layout, based on its calculations of child element sizes.</returns>
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ if (Children == null || Children.Count == 0)
+ {
+ return availableSize;
+ }
+
+ var constraint2 = availableSize;
+ if (CanHorizontallyScroll)
+ {
+ constraint2.Width = double.PositiveInfinity;
+ }
+ if (CanVerticallyScroll)
+ {
+ constraint2.Height = double.PositiveInfinity;
+ }
+
+ var uiElement = Children[0];
+
+ uiElement.Measure(constraint2);
+ var size = uiElement.DesiredSize;
+
+ VerifyScrollData(availableSize, size);
+
+ size.Width = Math.Min(availableSize.Width, size.Width);
+ size.Height = Math.Min(availableSize.Height, size.Height);
+
+ return size;
+ }
+
+ /// <summary>
+ /// Arranges the content of a <see cref="T:System.Windows.Controls.Grid" /> element.
+ /// </summary>
+ /// <param name="arrangeSize">Specifies the size this <see cref="T:System.Windows.Controls.Grid" /> element should use to arrange its child elements.</param>
+ /// <returns><see cref="T:System.Windows.Size" /> that represents the arranged size of this Grid element and its children.</returns>
+ protected override Size ArrangeOverride(Size arrangeSize)
+ {
+ this.VerifyScrollData(arrangeSize, _Extent);
+
+ if (this.Children == null || this.Children.Count == 0)
+ {
+ return arrangeSize;
+ }
+
+ TranslateTransform trans = null;
+
+ var uiElement = Children[0];
+
+ var finalRect = new Rect(uiElement.DesiredSize);
+
+ // ScrollContentPresenter sets these to 0 - current offset
+ // We need to set it to zero in order to make the animation work
+ finalRect.X = 0;
+ finalRect.Y = 0;
+
+ finalRect.Width = Math.Max(finalRect.Width, arrangeSize.Width);
+ finalRect.Height = Math.Max(finalRect.Height, arrangeSize.Height);
+
+ trans = uiElement.RenderTransform as TranslateTransform;
+
+ if (trans == null)
+ {
+ uiElement.RenderTransformOrigin = new Point(0, 0);
+ trans = new TranslateTransform();
+ uiElement.RenderTransform = trans;
+ }
+
+ uiElement.Arrange(finalRect);
+
+ trans.BeginAnimation(TranslateTransform.XProperty,
+ GetAnimation(0 - HorizontalOffset),
+ HandoffBehavior.Compose);
+ trans.BeginAnimation(TranslateTransform.YProperty,
+ GetAnimation(0 - VerticalOffset),
+ HandoffBehavior.Compose);
+
+ return arrangeSize;
+ }
+
+ /// <summary>
+ /// Gets the animation.
+ /// </summary>
+ /// <param name="toValue">To value.</param>
+ /// <returns>DoubleAnimation.</returns>
+ private DoubleAnimation GetAnimation(double toValue)
+ {
+ var animation = new DoubleAnimation(toValue, _AnimationLength);
+
+ animation.EasingFunction = new ExponentialEase { EasingMode = EasingMode.EaseInOut };
+
+ return animation;
+ }
+
+ #region Movement Methods
+ /// <summary>
+ /// Scrolls down within content by one logical unit.
+ /// </summary>
+ public void LineDown()
+ { SetVerticalOffset(VerticalOffset + LineSize); }
+
+ /// <summary>
+ /// Scrolls up within content by one logical unit.
+ /// </summary>
+ public void LineUp()
+ { SetVerticalOffset(VerticalOffset - LineSize); }
+
+ /// <summary>
+ /// Scrolls left within content by one logical unit.
+ /// </summary>
+ public void LineLeft()
+ { SetHorizontalOffset(HorizontalOffset - LineSize); }
+
+ /// <summary>
+ /// Scrolls right within content by one logical unit.
+ /// </summary>
+ public void LineRight()
+ { SetHorizontalOffset(HorizontalOffset + LineSize); }
+
+ /// <summary>
+ /// Scrolls down within content after a user clicks the wheel button on a mouse.
+ /// </summary>
+ public void MouseWheelDown()
+ { SetVerticalOffset(VerticalOffset + WheelSize); }
+
+ /// <summary>
+ /// Scrolls up within content after a user clicks the wheel button on a mouse.
+ /// </summary>
+ public void MouseWheelUp()
+ { SetVerticalOffset(VerticalOffset - WheelSize); }
+
+ /// <summary>
+ /// Scrolls left within content after a user clicks the wheel button on a mouse.
+ /// </summary>
+ public void MouseWheelLeft()
+ { SetHorizontalOffset(HorizontalOffset - WheelSize); }
+
+ /// <summary>
+ /// Scrolls right within content after a user clicks the wheel button on a mouse.
+ /// </summary>
+ public void MouseWheelRight()
+ { SetHorizontalOffset(HorizontalOffset + WheelSize); }
+
+ /// <summary>
+ /// Scrolls down within content by one page.
+ /// </summary>
+ public void PageDown()
+ { SetVerticalOffset(VerticalOffset + ViewportHeight); }
+
+ /// <summary>
+ /// Scrolls up within content by one page.
+ /// </summary>
+ public void PageUp()
+ { SetVerticalOffset(VerticalOffset - ViewportHeight); }
+
+ /// <summary>
+ /// Scrolls left within content by one page.
+ /// </summary>
+ public void PageLeft()
+ { SetHorizontalOffset(HorizontalOffset - ViewportWidth); }
+
+ /// <summary>
+ /// Scrolls right within content by one page.
+ /// </summary>
+ public void PageRight()
+ { SetHorizontalOffset(HorizontalOffset + ViewportWidth); }
+ #endregion
+
+ /// <summary>
+ /// Gets or sets a <see cref="T:System.Windows.Controls.ScrollViewer" /> element that controls scrolling behavior.
+ /// </summary>
+ /// <value>The scroll owner.</value>
+ /// <returns>A <see cref="T:System.Windows.Controls.ScrollViewer" /> element that controls scrolling behavior. This property has no default value.</returns>
+ public ScrollViewer ScrollOwner { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value that indicates whether scrolling on the horizontal axis is possible.
+ /// </summary>
+ /// <value><c>true</c> if this instance can horizontally scroll; otherwise, <c>false</c>.</value>
+ /// <returns>true if scrolling is possible; otherwise, false. This property has no default value.</returns>
+ public bool CanHorizontallyScroll { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value that indicates whether scrolling on the vertical axis is possible.
+ /// </summary>
+ /// <value><c>true</c> if this instance can vertically scroll; otherwise, <c>false</c>.</value>
+ /// <returns>true if scrolling is possible; otherwise, false. This property has no default value.</returns>
+ public bool CanVerticallyScroll { get; set; }
+
+ /// <summary>
+ /// Gets the vertical size of the extent.
+ /// </summary>
+ /// <value>The height of the extent.</value>
+ /// <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>
+ public double ExtentHeight
+ { get { return _Extent.Height; } }
+
+ /// <summary>
+ /// Gets the horizontal size of the extent.
+ /// </summary>
+ /// <value>The width of the extent.</value>
+ /// <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>
+ public double ExtentWidth
+ { get { return _Extent.Width; } }
+
+ /// <summary>
+ /// Gets the horizontal offset of the scrolled content.
+ /// </summary>
+ /// <value>The horizontal offset.</value>
+ /// <returns>A <see cref="T:System.Double" /> that represents, in device independent pixels, the horizontal offset. This property has no default value.</returns>
+ public double HorizontalOffset
+ { get { return _Offset.X; } }
+
+ /// <summary>
+ /// Gets the vertical offset of the scrolled content.
+ /// </summary>
+ /// <value>The vertical offset.</value>
+ /// <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>
+ public double VerticalOffset
+ { get { return _Offset.Y; } }
+
+ /// <summary>
+ /// Gets the vertical size of the viewport for this content.
+ /// </summary>
+ /// <value>The height of the viewport.</value>
+ /// <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>
+ public double ViewportHeight
+ { get { return _Viewport.Height; } }
+
+ /// <summary>
+ /// Gets the horizontal size of the viewport for this content.
+ /// </summary>
+ /// <value>The width of the viewport.</value>
+ /// <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>
+ public double ViewportWidth
+ { get { return _Viewport.Width; } }
+
+ /// <summary>
+ /// Forces content to scroll until the coordinate space of a <see cref="T:System.Windows.Media.Visual" /> object is visible.
+ /// </summary>
+ /// <param name="visual">A <see cref="T:System.Windows.Media.Visual" /> that becomes visible.</param>
+ /// <param name="rectangle">A bounding rectangle that identifies the coordinate space to make visible.</param>
+ /// <returns>A <see cref="T:System.Windows.Rect" /> that is visible.</returns>
+ public Rect MakeVisible(Visual visual, Rect rectangle)
+ {
+ if (rectangle.IsEmpty || visual == null
+ || visual == this || !base.IsAncestorOf(visual))
+ { return Rect.Empty; }
+
+ rectangle = visual.TransformToAncestor(this).TransformBounds(rectangle);
+
+ //rectangle.Inflate(50, 50);
+ rectangle.Scale(1.2, 1.2);
+
+ Rect viewRect = new Rect(HorizontalOffset,
+ VerticalOffset, ViewportWidth, ViewportHeight);
+ rectangle.X += viewRect.X;
+ rectangle.Y += viewRect.Y;
+
+ viewRect.X = CalculateNewScrollOffset(viewRect.Left,
+ viewRect.Right, rectangle.Left, rectangle.Right);
+ viewRect.Y = CalculateNewScrollOffset(viewRect.Top,
+ viewRect.Bottom, rectangle.Top, rectangle.Bottom);
+ SetHorizontalOffset(viewRect.X);
+ SetVerticalOffset(viewRect.Y);
+ rectangle.Intersect(viewRect);
+ rectangle.X -= viewRect.X;
+ rectangle.Y -= viewRect.Y;
+
+ return rectangle;
+ }
+
+ /// <summary>
+ /// Calculates the new scroll offset.
+ /// </summary>
+ /// <param name="topView">The top view.</param>
+ /// <param name="bottomView">The bottom view.</param>
+ /// <param name="topChild">The top child.</param>
+ /// <param name="bottomChild">The bottom child.</param>
+ /// <returns>System.Double.</returns>
+ private static double CalculateNewScrollOffset(double topView,
+ double bottomView, double topChild, double bottomChild)
+ {
+ bool offBottom = topChild < topView && bottomChild < bottomView;
+ bool offTop = bottomChild > bottomView && topChild > topView;
+ bool tooLarge = (bottomChild - topChild) > (bottomView - topView);
+
+ if (!offBottom && !offTop)
+ { return topView; } //Don't do anything, already in view
+
+ if ((offBottom && !tooLarge) || (offTop && tooLarge))
+ { return topChild; }
+
+ return (bottomChild - (bottomView - topView));
+ }
+
+ /// <summary>
+ /// Verifies the scroll data.
+ /// </summary>
+ /// <param name="viewport">The viewport.</param>
+ /// <param name="extent">The extent.</param>
+ protected void VerifyScrollData(Size viewport, Size extent)
+ {
+ if (double.IsInfinity(viewport.Width))
+ { viewport.Width = extent.Width; }
+
+ if (double.IsInfinity(viewport.Height))
+ { viewport.Height = extent.Height; }
+
+ _Extent = extent;
+ _Viewport = viewport;
+
+ _Offset.X = Math.Max(0,
+ Math.Min(_Offset.X, ExtentWidth - ViewportWidth));
+ _Offset.Y = Math.Max(0,
+ Math.Min(_Offset.Y, ExtentHeight - ViewportHeight));
+
+ if (ScrollOwner != null)
+ { ScrollOwner.InvalidateScrollInfo(); }
+ }
+
+ /// <summary>
+ /// Sets the amount of horizontal offset.
+ /// </summary>
+ /// <param name="offset">The degree to which content is horizontally offset from the containing viewport.</param>
+ public void SetHorizontalOffset(double offset)
+ {
+ offset = Math.Max(0,
+ Math.Min(offset, ExtentWidth - ViewportWidth));
+ if (!offset.Equals(_Offset.X))
+ {
+ _Offset.X = offset;
+ InvalidateArrange();
+ }
+ }
+
+ /// <summary>
+ /// Sets the amount of vertical offset.
+ /// </summary>
+ /// <param name="offset">The degree to which content is vertically offset from the containing viewport.</param>
+ public void SetVerticalOffset(double offset)
+ {
+ offset = Math.Max(0,
+ Math.Min(offset, ExtentHeight - ViewportHeight));
+ if (!offset.Equals(_Offset.Y))
+ {
+ _Offset.Y = offset;
+ InvalidateArrange();
+ }
+ }
+ }
+}