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