13 changed files with 443 additions and 237 deletions
@ -0,0 +1,92 @@ |
|||
using System; |
|||
using Perspex; |
|||
using Perspex.Controls; |
|||
using Perspex.Controls.Primitives; |
|||
using Perspex.Media; |
|||
|
|||
namespace XamlTestApplication |
|||
{ |
|||
public class TestScrollable : Control, IScrollable |
|||
{ |
|||
private int itemCount = 100; |
|||
private Action _invalidateScroll; |
|||
private Size _extent; |
|||
private Vector _offset; |
|||
private Size _viewport; |
|||
private Size _lineSize; |
|||
|
|||
public Action InvalidateScroll |
|||
{ |
|||
get { return _invalidateScroll; } |
|||
set { _invalidateScroll = value; } |
|||
} |
|||
|
|||
Size IScrollable.Extent |
|||
{ |
|||
get { return _extent; } |
|||
} |
|||
|
|||
Vector IScrollable.Offset |
|||
{ |
|||
get |
|||
{ |
|||
return _offset; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
_offset = value; |
|||
InvalidateVisual(); |
|||
} |
|||
} |
|||
|
|||
Size IScrollable.Viewport |
|||
{ |
|||
get { return _viewport; } |
|||
} |
|||
|
|||
protected override Size MeasureOverride(Size availableSize) |
|||
{ |
|||
using (var line = new FormattedText( |
|||
"Item 100", |
|||
TextBlock.GetFontFamily(this), |
|||
TextBlock.GetFontSize(this), |
|||
TextBlock.GetFontStyle(this), |
|||
TextAlignment.Left, |
|||
TextBlock.GetFontWeight(this))) |
|||
{ |
|||
line.Constraint = availableSize; |
|||
_lineSize = line.Measure(); |
|||
return new Size(_lineSize.Width, _lineSize.Height * itemCount); |
|||
} |
|||
} |
|||
|
|||
protected override Size ArrangeOverride(Size finalSize) |
|||
{ |
|||
_viewport = new Size(finalSize.Width, finalSize.Height / _lineSize.Height); |
|||
_extent = new Size(_lineSize.Width, itemCount); |
|||
InvalidateScroll(); |
|||
return finalSize; |
|||
} |
|||
|
|||
public override void Render(DrawingContext context) |
|||
{ |
|||
var y = 0.0; |
|||
|
|||
for (var i = (int)_offset.Y; i < itemCount; ++i) |
|||
{ |
|||
using (var line = new FormattedText( |
|||
"Item " + (i + 1), |
|||
TextBlock.GetFontFamily(this), |
|||
TextBlock.GetFontSize(this), |
|||
TextBlock.GetFontStyle(this), |
|||
TextAlignment.Left, |
|||
TextBlock.GetFontWeight(this))) |
|||
{ |
|||
context.DrawText(Brushes.Black, new Point(-_offset.X, y), line); |
|||
y += _lineSize.Height; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,97 +0,0 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Perspex.Controls.Primitives |
|||
{ |
|||
public interface IScrollInfoBase |
|||
{ |
|||
/// <summary>
|
|||
/// ScrollOwner is the container that controls any scrollbars, headers, etc... that are dependant
|
|||
/// on this IScrollInfo's properties. Implementers of IScrollInfo should call InvalidateScrollInfo()
|
|||
/// on this object when properties change.
|
|||
/// </summary>
|
|||
ScrollViewer ScrollOwner { get; set; } |
|||
|
|||
Rect MakeVisible(Visual visual, Rect rectangle); |
|||
} |
|||
|
|||
public interface IVerticalScrollInfo : IScrollInfoBase |
|||
{ |
|||
/// <summary>
|
|||
/// VerticalOffset is the vertical offset into the scrolled content that represents the first unit visible.
|
|||
/// </summary>
|
|||
double VerticalOffset { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// ExtentHeight contains the full vertical range of the scrolled content.
|
|||
/// </summary>
|
|||
double ExtentHeight { get; } |
|||
|
|||
/// <summary>
|
|||
/// ViewportHeight contains the currently visible vertical range of the scrolled content.
|
|||
/// </summary>
|
|||
double ViewportHeight { get; } |
|||
|
|||
/// <summary>
|
|||
/// This property indicates to the IScrollInfo whether or not it can scroll in the vertical given dimension.
|
|||
/// </summary>
|
|||
bool CanVerticallyScroll { get; set; } |
|||
|
|||
void LineDown(); |
|||
|
|||
void LineUp(); |
|||
|
|||
void MouseWheelDown(); |
|||
|
|||
void MouseWheelUp(); |
|||
|
|||
void PageDown(); |
|||
|
|||
void PageUp(); |
|||
} |
|||
|
|||
public interface IHorizontalScrollInfo : IScrollInfoBase |
|||
{ |
|||
/// <summary>
|
|||
/// ExtentWidth contains the full horizontal range of the scrolled content.
|
|||
/// </summary>
|
|||
double ExtentWidth { get; } |
|||
|
|||
/// <summary>
|
|||
/// ViewportWidth contains the currently visible horizontal range of the scrolled content.
|
|||
/// </summary>
|
|||
double ViewportWidth { get; } |
|||
|
|||
/// <summary>
|
|||
/// HorizontalOffset is the horizontal offset into the scrolled content that represents the first unit visible.
|
|||
/// </summary>
|
|||
double HorizontalOffset { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// This property indicates to the IScrollInfo whether or not it can scroll in the horizontal given dimension.
|
|||
/// </summary>
|
|||
bool CanHorizontallyScroll { get; set; } |
|||
|
|||
void LineLeft(); |
|||
|
|||
void LineRight(); |
|||
|
|||
void MouseWheelLeft(); |
|||
|
|||
void MouseWheelRight(); |
|||
|
|||
void PageLeft(); |
|||
|
|||
void PageRight(); |
|||
} |
|||
|
|||
public interface IScrollInfo : IHorizontalScrollInfo, IVerticalScrollInfo |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
namespace Perspex.Controls.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// Interface implemented by controls that handle their own scrolling when placed inside a
|
|||
/// <see cref="ScrollViewer"/>.
|
|||
/// </summary>
|
|||
public interface IScrollable |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the invalidation method which notifies the attached
|
|||
/// <see cref="ScrollViewer"/> of a change in <see cref="Extent"/> or <see cref="Offset"/>.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This property is set by the parent <see cref="ScrollViewer"/> when the
|
|||
/// <see cref="IScrollable"/> is placed inside it.
|
|||
/// </remarks>
|
|||
Action InvalidateScroll { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the extent of the scrollable content, in logical units
|
|||
/// </summary>
|
|||
Size Extent { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the current scroll offset, in logical units.
|
|||
/// </summary>
|
|||
Vector Offset { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the size of the viewport, in logical units.
|
|||
/// </summary>
|
|||
Size Viewport { get; } |
|||
} |
|||
} |
|||
@ -1,126 +0,0 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
namespace Perspex.Controls.Primitives |
|||
{ |
|||
public class ScrollInfoAdapter : IScrollInfo |
|||
{ |
|||
private readonly IScrollInfoBase _nfo; |
|||
public ScrollInfoAdapter(IScrollInfoBase nfo) |
|||
{ |
|||
_nfo = nfo; |
|||
} |
|||
|
|||
public ScrollViewer ScrollOwner |
|||
{ |
|||
get { return _nfo.ScrollOwner; } |
|||
set { _nfo.ScrollOwner = value; } |
|||
} |
|||
|
|||
public double ExtentWidth => (_nfo as IHorizontalScrollInfo)?.ExtentWidth ?? 0; |
|||
|
|||
public double ViewportWidth => (_nfo as IHorizontalScrollInfo)?.ViewportWidth ?? 0; |
|||
|
|||
public double ExtentHeight => (_nfo as IVerticalScrollInfo)?.ExtentHeight ?? 0; |
|||
|
|||
public double ViewportHeight => (_nfo as IVerticalScrollInfo)?.ViewportHeight ?? 0; |
|||
|
|||
private double _horizontalOffset; |
|||
public double HorizontalOffset |
|||
{ |
|||
get |
|||
{ |
|||
return (_nfo as IHorizontalScrollInfo)?.HorizontalOffset ?? _horizontalOffset; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
var info = (_nfo as IHorizontalScrollInfo); |
|||
if (info == null) |
|||
_horizontalOffset = value; |
|||
else |
|||
info.HorizontalOffset = value; |
|||
} |
|||
} |
|||
|
|||
private double _verticalOffset; |
|||
public double VerticalOffset |
|||
{ |
|||
get |
|||
{ |
|||
return (_nfo as IVerticalScrollInfo)?.VerticalOffset ?? _verticalOffset; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
var info = (_nfo as IVerticalScrollInfo); |
|||
if (info == null) |
|||
_verticalOffset = value; |
|||
else |
|||
info.VerticalOffset = value; |
|||
} |
|||
} |
|||
|
|||
public void LineLeft() => (_nfo as IHorizontalScrollInfo)?.LineLeft(); |
|||
|
|||
public void LineRight() => (_nfo as IHorizontalScrollInfo)?.LineRight(); |
|||
|
|||
public void MouseWheelLeft() => (_nfo as IHorizontalScrollInfo)?.MouseWheelLeft(); |
|||
|
|||
public void MouseWheelRight() => (_nfo as IHorizontalScrollInfo)?.MouseWheelRight(); |
|||
|
|||
public void PageLeft() => (_nfo as IHorizontalScrollInfo)?.PageLeft(); |
|||
|
|||
public Rect MakeVisible(Visual visual, Rect rectangle) => _nfo.MakeVisible(visual, rectangle); |
|||
|
|||
public void PageRight() => (_nfo as IHorizontalScrollInfo)?.PageRight(); |
|||
|
|||
public void LineDown() => (_nfo as IVerticalScrollInfo)?.LineDown(); |
|||
|
|||
public void LineUp() => (_nfo as IVerticalScrollInfo)?.LineUp(); |
|||
|
|||
public void MouseWheelDown() => (_nfo as IVerticalScrollInfo)?.MouseWheelDown(); |
|||
|
|||
public void MouseWheelUp() => (_nfo as IVerticalScrollInfo)?.MouseWheelUp(); |
|||
|
|||
public void PageDown() => (_nfo as IVerticalScrollInfo)?.PageDown(); |
|||
|
|||
public void PageUp() => (_nfo as IVerticalScrollInfo)?.PageUp(); |
|||
|
|||
private bool _canVerticallyScroll; |
|||
public bool CanVerticallyScroll |
|||
{ |
|||
get |
|||
{ |
|||
return (_nfo as IVerticalScrollInfo)?.CanVerticallyScroll ?? _canVerticallyScroll; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
var info = (_nfo as IVerticalScrollInfo); |
|||
if (info == null) |
|||
_canVerticallyScroll = value; |
|||
else |
|||
info.CanVerticallyScroll = value; |
|||
} |
|||
} |
|||
|
|||
private bool _canHorizontallyScroll; |
|||
public bool CanHorizontallyScroll |
|||
{ |
|||
get |
|||
{ |
|||
return (_nfo as IHorizontalScrollInfo)?.CanHorizontallyScroll ?? _canHorizontallyScroll; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
var info = (_nfo as IHorizontalScrollInfo); |
|||
if (info == null) |
|||
_canHorizontallyScroll = value; |
|||
else |
|||
info.CanHorizontallyScroll = value; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,216 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Reactive.Linq; |
|||
using Perspex.Controls.Presenters; |
|||
using Perspex.Controls.Primitives; |
|||
using Xunit; |
|||
|
|||
namespace Perspex.Controls.UnitTests |
|||
{ |
|||
public class ScrollContentPresenterTests_IScrollable |
|||
{ |
|||
[Fact] |
|||
public void Measure_Should_Pass_Unchanged_Bounds_To_IScrollable() |
|||
{ |
|||
var scrollable = new TestScrollable(); |
|||
var target = new ScrollContentPresenter |
|||
{ |
|||
Content = scrollable, |
|||
}; |
|||
|
|||
target.Measure(new Size(100, 100)); |
|||
|
|||
Assert.Equal(new Size(100, 100), scrollable.AvailableSize); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Arrange_Should_Not_Offset_IScrollable_Bounds() |
|||
{ |
|||
var scrollable = new TestScrollable |
|||
{ |
|||
Extent = new Size(100, 100), |
|||
Offset = new Vector(50, 50), |
|||
Viewport = new Size(25, 25), |
|||
}; |
|||
|
|||
var target = new ScrollContentPresenter |
|||
{ |
|||
Content = scrollable, |
|||
}; |
|||
|
|||
target.Measure(new Size(100, 100)); |
|||
target.Arrange(new Rect(0, 0, 100, 100)); |
|||
|
|||
Assert.Equal(new Rect(0, 0, 100, 100), scrollable.Bounds); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Arrange_Should_Not_Set_Viewport_And_Extent_With_IScrollable() |
|||
{ |
|||
var target = new ScrollContentPresenter |
|||
{ |
|||
Content = new TestScrollable() |
|||
}; |
|||
|
|||
var changed = false; |
|||
|
|||
target.Measure(new Size(100, 100)); |
|||
|
|||
target.GetObservable(ScrollViewer.ViewportProperty).Skip(1).Subscribe(_ => changed = true); |
|||
target.GetObservable(ScrollViewer.ExtentProperty).Skip(1).Subscribe(_ => changed = true); |
|||
|
|||
target.Arrange(new Rect(0, 0, 100, 100)); |
|||
|
|||
Assert.False(changed); |
|||
} |
|||
|
|||
[Fact] |
|||
public void InvalidateScroll_Should_Be_Set_When_Set_As_Content() |
|||
{ |
|||
var scrollable = new TestScrollable(); |
|||
var target = new ScrollContentPresenter |
|||
{ |
|||
Content = scrollable |
|||
}; |
|||
|
|||
target.ApplyTemplate(); |
|||
|
|||
Assert.NotNull(scrollable.InvalidateScroll); |
|||
} |
|||
|
|||
[Fact] |
|||
public void InvalidateScroll_Should_Be_Cleared_When_Removed_From_Content() |
|||
{ |
|||
var scrollable = new TestScrollable(); |
|||
var target = new ScrollContentPresenter |
|||
{ |
|||
Content = scrollable |
|||
}; |
|||
|
|||
target.ApplyTemplate(); |
|||
target.Content = null; |
|||
target.ApplyTemplate(); |
|||
|
|||
Assert.Null(scrollable.InvalidateScroll); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Extent_Offset_And_Viewport_Should_Be_Read_From_IScrollable() |
|||
{ |
|||
var scrollable = new TestScrollable |
|||
{ |
|||
Extent = new Size(100, 100), |
|||
Offset = new Vector(50, 50), |
|||
Viewport = new Size(25, 25), |
|||
}; |
|||
|
|||
var target = new ScrollContentPresenter |
|||
{ |
|||
Content = scrollable |
|||
}; |
|||
|
|||
target.ApplyTemplate(); |
|||
|
|||
Assert.Equal(scrollable.Extent, target.Extent); |
|||
Assert.Equal(scrollable.Offset, target.Offset); |
|||
Assert.Equal(scrollable.Viewport, target.Viewport); |
|||
|
|||
scrollable.Extent = new Size(200, 200); |
|||
scrollable.Offset = new Vector(100, 100); |
|||
scrollable.Viewport = new Size(50, 50); |
|||
|
|||
Assert.Equal(scrollable.Extent, target.Extent); |
|||
Assert.Equal(scrollable.Offset, target.Offset); |
|||
Assert.Equal(scrollable.Viewport, target.Viewport); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Offset_Should_Be_Written_To_IScrollable() |
|||
{ |
|||
var scrollable = new TestScrollable |
|||
{ |
|||
Extent = new Size(100, 100), |
|||
Offset = new Vector(50, 50), |
|||
}; |
|||
|
|||
var target = new ScrollContentPresenter |
|||
{ |
|||
Content = scrollable |
|||
}; |
|||
|
|||
target.ApplyTemplate(); |
|||
|
|||
target.Offset = new Vector(25, 25); |
|||
|
|||
Assert.Equal(target.Offset, scrollable.Offset); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Offset_Should_Not_Be_Written_To_IScrollable_After_Removal() |
|||
{ |
|||
var scrollable = new TestScrollable |
|||
{ |
|||
Extent = new Size(100, 100), |
|||
Offset = new Vector(50, 50), |
|||
}; |
|||
|
|||
var target = new ScrollContentPresenter |
|||
{ |
|||
Content = scrollable |
|||
}; |
|||
|
|||
target.Content = null; |
|||
target.Offset = new Vector(25, 25); |
|||
|
|||
Assert.Equal(new Vector(50, 50), scrollable.Offset); |
|||
} |
|||
|
|||
private class TestScrollable : Control, IScrollable |
|||
{ |
|||
private Size _extent; |
|||
private Vector _offset; |
|||
private Size _viewport; |
|||
|
|||
public Size AvailableSize { get; private set; } |
|||
public Action InvalidateScroll { get; set; } |
|||
|
|||
public Size Extent |
|||
{ |
|||
get { return _extent; } |
|||
set |
|||
{ |
|||
_extent = value; |
|||
InvalidateScroll?.Invoke(); |
|||
} |
|||
} |
|||
|
|||
public Vector Offset |
|||
{ |
|||
get { return _offset; } |
|||
set |
|||
{ |
|||
_offset = value; |
|||
InvalidateScroll?.Invoke(); |
|||
} |
|||
} |
|||
|
|||
public Size Viewport |
|||
{ |
|||
get { return _viewport; } |
|||
set |
|||
{ |
|||
_viewport = value; |
|||
InvalidateScroll?.Invoke(); |
|||
} |
|||
} |
|||
|
|||
protected override Size MeasureOverride(Size availableSize) |
|||
{ |
|||
AvailableSize = availableSize; |
|||
return new Size(150, 150); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue