mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-09-06 19:37:55 +02:00
404 lines
16 KiB
C#
404 lines
16 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|
|
}
|