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
{
///
/// 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.
///
public class ScrollingPanel : Grid, IScrollInfo
{
///
/// The infinite size
///
private static Size InfiniteSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
///
/// The line size
///
private const double LineSize = 16;
///
/// The wheel size
///
private const double WheelSize = 3 * LineSize;
///
/// The _ offset
///
private Vector _Offset;
///
/// The _ extent
///
private Size _Extent;
///
/// The _ viewport
///
private Size _Viewport;
///
/// The _ animation length
///
private TimeSpan _AnimationLength = TimeSpan.FromMilliseconds(125);
///
/// When overridden in a derived class, measures the size in layout required for child elements and determines a size for the -derived class.
///
/// 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.
/// The size that this element determines it needs during layout, based on its calculations of child element sizes.
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;
}
///
/// Arranges the content of a element.
///
/// Specifies the size this element should use to arrange its child elements.
/// that represents the arranged size of this Grid element and its children.
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;
}
///
/// Gets the animation.
///
/// To value.
/// DoubleAnimation.
private DoubleAnimation GetAnimation(double toValue)
{
var animation = new DoubleAnimation(toValue, _AnimationLength);
animation.EasingFunction = new ExponentialEase { EasingMode = EasingMode.EaseInOut };
return animation;
}
#region Movement Methods
///
/// Scrolls down within content by one logical unit.
///
public void LineDown()
{ SetVerticalOffset(VerticalOffset + LineSize); }
///
/// Scrolls up within content by one logical unit.
///
public void LineUp()
{ SetVerticalOffset(VerticalOffset - LineSize); }
///
/// Scrolls left within content by one logical unit.
///
public void LineLeft()
{ SetHorizontalOffset(HorizontalOffset - LineSize); }
///
/// Scrolls right within content by one logical unit.
///
public void LineRight()
{ SetHorizontalOffset(HorizontalOffset + LineSize); }
///
/// Scrolls down within content after a user clicks the wheel button on a mouse.
///
public void MouseWheelDown()
{ SetVerticalOffset(VerticalOffset + WheelSize); }
///
/// Scrolls up within content after a user clicks the wheel button on a mouse.
///
public void MouseWheelUp()
{ SetVerticalOffset(VerticalOffset - WheelSize); }
///
/// Scrolls left within content after a user clicks the wheel button on a mouse.
///
public void MouseWheelLeft()
{ SetHorizontalOffset(HorizontalOffset - WheelSize); }
///
/// Scrolls right within content after a user clicks the wheel button on a mouse.
///
public void MouseWheelRight()
{ SetHorizontalOffset(HorizontalOffset + WheelSize); }
///
/// Scrolls down within content by one page.
///
public void PageDown()
{ SetVerticalOffset(VerticalOffset + ViewportHeight); }
///
/// Scrolls up within content by one page.
///
public void PageUp()
{ SetVerticalOffset(VerticalOffset - ViewportHeight); }
///
/// Scrolls left within content by one page.
///
public void PageLeft()
{ SetHorizontalOffset(HorizontalOffset - ViewportWidth); }
///
/// Scrolls right within content by one page.
///
public void PageRight()
{ SetHorizontalOffset(HorizontalOffset + ViewportWidth); }
#endregion
///
/// Gets or sets a element that controls scrolling behavior.
///
/// The scroll owner.
/// A element that controls scrolling behavior. This property has no default value.
public ScrollViewer ScrollOwner { get; set; }
///
/// Gets or sets a value that indicates whether scrolling on the horizontal axis is possible.
///
/// true if this instance can horizontally scroll; otherwise, false.
/// true if scrolling is possible; otherwise, false. This property has no default value.
public bool CanHorizontallyScroll { get; set; }
///
/// Gets or sets a value that indicates whether scrolling on the vertical axis is possible.
///
/// true if this instance can vertically scroll; otherwise, false.
/// true if scrolling is possible; otherwise, false. This property has no default value.
public bool CanVerticallyScroll { get; set; }
///
/// Gets the vertical size of the extent.
///
/// The height of the extent.
/// A that represents, in device independent pixels, the vertical size of the extent.This property has no default value.
public double ExtentHeight
{ get { return _Extent.Height; } }
///
/// Gets the horizontal size of the extent.
///
/// The width of the extent.
/// A that represents, in device independent pixels, the horizontal size of the extent. This property has no default value.
public double ExtentWidth
{ get { return _Extent.Width; } }
///
/// Gets the horizontal offset of the scrolled content.
///
/// The horizontal offset.
/// A that represents, in device independent pixels, the horizontal offset. This property has no default value.
public double HorizontalOffset
{ get { return _Offset.X; } }
///
/// Gets the vertical offset of the scrolled content.
///
/// The vertical offset.
/// A that represents, in device independent pixels, the vertical offset of the scrolled content. Valid values are between zero and the minus the . This property has no default value.
public double VerticalOffset
{ get { return _Offset.Y; } }
///
/// Gets the vertical size of the viewport for this content.
///
/// The height of the viewport.
/// A that represents, in device independent pixels, the vertical size of the viewport for this content. This property has no default value.
public double ViewportHeight
{ get { return _Viewport.Height; } }
///
/// Gets the horizontal size of the viewport for this content.
///
/// The width of the viewport.
/// A that represents, in device independent pixels, the horizontal size of the viewport for this content. This property has no default value.
public double ViewportWidth
{ get { return _Viewport.Width; } }
///
/// Forces content to scroll until the coordinate space of a object is visible.
///
/// A that becomes visible.
/// A bounding rectangle that identifies the coordinate space to make visible.
/// A that is visible.
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;
}
///
/// Calculates the new scroll offset.
///
/// The top view.
/// The bottom view.
/// The top child.
/// The bottom child.
/// System.Double.
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));
}
///
/// Verifies the scroll data.
///
/// The viewport.
/// The extent.
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(); }
}
///
/// Sets the amount of horizontal offset.
///
/// The degree to which content is horizontally offset from the containing viewport.
public void SetHorizontalOffset(double offset)
{
offset = Math.Max(0,
Math.Min(offset, ExtentWidth - ViewportWidth));
if (!offset.Equals(_Offset.X))
{
_Offset.X = offset;
InvalidateArrange();
}
}
///
/// Sets the amount of vertical offset.
///
/// The degree to which content is vertically offset from the containing viewport.
public void SetVerticalOffset(double offset)
{
offset = Math.Max(0,
Math.Min(offset, ExtentHeight - ViewportHeight));
if (!offset.Equals(_Offset.Y))
{
_Offset.Y = offset;
InvalidateArrange();
}
}
}
}