Browse Source

Added IScrollable.IsLogicalScrollEnabled.

pull/554/head
Steven Kirk 10 years ago
parent
commit
7db353e742
  1. 1
      samples/XamlTestApplicationPcl/TestScrollable.cs
  2. 61
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  3. 1
      src/Avalonia.Controls/Presenters/ThingamybobPresenter.cs
  4. 12
      src/Avalonia.Controls/Primitives/IScrollable.cs
  5. 69
      tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests_IScrollable.cs

1
samples/XamlTestApplicationPcl/TestScrollable.cs

@ -14,6 +14,7 @@ namespace XamlTestApplication
private Size _viewport;
private Size _lineSize;
public bool IsLogicalScrollEnabled => true;
public Action InvalidateScroll { get; set; }
Size IScrollable.Extent

61
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@ -69,7 +69,7 @@ namespace Avalonia.Controls.Presenters
{
AddHandler(RequestBringIntoViewEvent, BringIntoViewRequested);
this.GetObservable(ChildProperty).Subscribe(ChildChanged);
this.GetObservable(ChildProperty).Subscribe(UpdateScrollableSubscription);
}
/// <summary>
@ -194,22 +194,25 @@ namespace Avalonia.Controls.Presenters
protected override Size ArrangeOverride(Size finalSize)
{
var child = this.GetVisualChildren().SingleOrDefault() as ILayoutable;
var offset = default(Vector);
var logicalScroll = _scrollableSubscription != null;
if (_scrollableSubscription == null)
if (!logicalScroll)
{
Viewport = finalSize;
Extent = _measuredExtent;
offset = Offset;
}
if (child != null)
{
var size = new Size(
if (child != null)
{
var size = new Size(
Math.Max(finalSize.Width, child.DesiredSize.Width),
Math.Max(finalSize.Height, child.DesiredSize.Height));
child.Arrange(new Rect((Point)(-offset), size));
return finalSize;
child.Arrange(new Rect((Point)(-Offset), size));
return finalSize;
}
}
else if (child != null)
{
child.Arrange(new Rect(finalSize));
}
return new Size();
@ -222,7 +225,7 @@ namespace Avalonia.Controls.Presenters
{
var scrollable = Child as IScrollable;
if (scrollable != null)
if (scrollable?.IsLogicalScrollEnabled == true)
{
var y = Offset.Y + (-e.Delta.Y * scrollable.ScrollSize.Height);
y = Math.Max(y, 0);
@ -246,7 +249,7 @@ namespace Avalonia.Controls.Presenters
e.Handled = BringDescendentIntoView(e.TargetObject, e.TargetRect);
}
private void ChildChanged(IControl child)
private void UpdateScrollableSubscription(IControl child)
{
var scrollable = child as IScrollable;
@ -256,18 +259,38 @@ namespace Avalonia.Controls.Presenters
if (scrollable != null)
{
scrollable.InvalidateScroll = () => UpdateFromScrollable(scrollable);
_scrollableSubscription = new CompositeDisposable(
this.GetObservable(OffsetProperty).Skip(1).Subscribe(x => scrollable.Offset = x),
Disposable.Create(() => scrollable.InvalidateScroll = null));
UpdateFromScrollable(scrollable);
if (scrollable?.IsLogicalScrollEnabled == true)
{
_scrollableSubscription = new CompositeDisposable(
this.GetObservable(OffsetProperty).Skip(1).Subscribe(x => scrollable.Offset = x),
Disposable.Create(() => scrollable.InvalidateScroll = null));
UpdateFromScrollable(scrollable);
}
}
}
private void UpdateFromScrollable(IScrollable scrollable)
{
Viewport = scrollable.Viewport;
Extent = scrollable.Extent;
Offset = scrollable.Offset;
var logicalScroll = _scrollableSubscription != null;
if (logicalScroll != scrollable.IsLogicalScrollEnabled)
{
UpdateScrollableSubscription(Child);
if (!scrollable.IsLogicalScrollEnabled)
{
Offset = default(Vector);
InvalidateMeasure();
}
}
if (scrollable.IsLogicalScrollEnabled)
{
Viewport = scrollable.Viewport;
Extent = scrollable.Extent;
Offset = scrollable.Offset;
}
}
}
}

1
src/Avalonia.Controls/Presenters/ThingamybobPresenter.cs

@ -23,6 +23,7 @@ namespace Avalonia.Controls.Presenters
public IPanel Panel => _panel;
bool IScrollable.IsLogicalScrollEnabled => true;
Action IScrollable.InvalidateScroll { get; set; }
Size IScrollable.Extent => new Size(1, 100 * AverageItemSize );

12
src/Avalonia.Controls/Primitives/IScrollable.cs

@ -9,8 +9,20 @@ namespace Avalonia.Controls.Primitives
/// Interface implemented by controls that handle their own scrolling when placed inside a
/// <see cref="ScrollViewer"/>.
/// </summary>
/// <remarks>
/// Controls that implement this interface, when placed inside a <see cref="ScrollViewer"/>
/// can override the physical scrolling behavior of the scroll viewer with logical scrolling.
/// Physical scrolling means that the scroll viewer is a simple viewport onto a larger canvas
/// whereas logical scrolling means that the scrolling is handled by the child control itself
/// and it can choose to do handle the scroll information as it sees fit.
/// </remarks>
public interface IScrollable
{
/// <summary>
/// Gets a value indicating whether logical scrolling is enabled on the control.
/// </summary>
bool IsLogicalScrollEnabled { get; }
/// <summary>
/// Gets or sets the scroll invalidation method.
/// </summary>

69
tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests_IScrollable.cs

@ -5,6 +5,7 @@ using System;
using System.Reactive.Linq;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Layout;
using Xunit;
namespace Avalonia.Controls.UnitTests
@ -48,6 +49,27 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Rect(0, 0, 100, 100), scrollable.Bounds);
}
[Fact]
public void Arrange_Should_Offset_IScrollable_Bounds_When_Logical_Scroll_Disabled()
{
var scrollable = new TestScrollable
{
IsLogicalScrollEnabled = false,
};
var target = new ScrollContentPresenter
{
Content = scrollable,
Offset = new Vector(25, 25),
};
target.UpdateChild();
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(new Rect(-25, -25, 150, 150), scrollable.Bounds);
}
[Fact]
public void Arrange_Should_Not_Set_Viewport_And_Extent_With_IScrollable()
{
@ -169,12 +191,59 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Vector(50, 50), scrollable.Offset);
}
[Fact]
public void Toggling_IsLogicalScrollEnabled_Should_Update_State()
{
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.UpdateChild();
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(scrollable.Extent, target.Extent);
Assert.Equal(scrollable.Offset, target.Offset);
Assert.Equal(scrollable.Viewport, target.Viewport);
Assert.Equal(new Rect(0, 0, 100, 100), scrollable.Bounds);
scrollable.IsLogicalScrollEnabled = false;
scrollable.InvalidateScroll();
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(new Size(150, 150), target.Extent);
Assert.Equal(new Vector(0, 0), target.Offset);
Assert.Equal(new Size(100, 100), target.Viewport);
Assert.Equal(new Rect(0, 0, 150, 150), scrollable.Bounds);
scrollable.IsLogicalScrollEnabled = true;
scrollable.InvalidateScroll();
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(scrollable.Extent, target.Extent);
Assert.Equal(scrollable.Offset, target.Offset);
Assert.Equal(scrollable.Viewport, target.Viewport);
Assert.Equal(new Rect(0, 0, 100, 100), scrollable.Bounds);
}
private class TestScrollable : Control, IScrollable
{
private Size _extent;
private Vector _offset;
private Size _viewport;
public bool IsLogicalScrollEnabled { get; set; } = true;
public Size AvailableSize { get; private set; }
public Action InvalidateScroll { get; set; }

Loading…
Cancel
Save