diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs
index 74c7da20d6..3a2cb688cb 100644
--- a/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs
+++ b/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs
@@ -16,6 +16,8 @@ namespace Avalonia.Controls.Presenters
///
internal abstract class ItemVirtualizer : IVirtualizingController, IDisposable
{
+ private double _crossAxisOffset;
+
///
/// Initializes a new instance of the class.
///
@@ -60,7 +62,7 @@ namespace Avalonia.Controls.Presenters
///
/// Gets a value indicating whether the items should be scroll horizontally or vertically.
///
- public bool Vertical => VirtualizingPanel.ScrollDirection == Orientation.Vertical;
+ public bool Vertical => VirtualizingPanel?.ScrollDirection == Orientation.Vertical;
///
/// Gets a value indicating whether logical scrolling is enabled.
@@ -85,12 +87,28 @@ namespace Avalonia.Controls.Presenters
///
/// Gets the as a .
///
- public Size Extent => Vertical ? new Size(0, ExtentValue) : new Size(ExtentValue, 0);
+ public Size Extent
+ {
+ get
+ {
+ return Vertical ?
+ new Size(Owner.Panel.DesiredSize.Width, ExtentValue) :
+ new Size(ExtentValue, Owner.Panel.DesiredSize.Height);
+ }
+ }
///
/// Gets the as a .
///
- public Size Viewport => Vertical ? new Size(0, ViewportValue) : new Size(ViewportValue, 0);
+ public Size Viewport
+ {
+ get
+ {
+ return Vertical ?
+ new Size(Owner.Panel.Bounds.Width, ViewportValue) :
+ new Size(ViewportValue, Owner.Panel.Bounds.Height);
+ }
+ }
///
/// Gets or sets the as a .
@@ -99,12 +117,28 @@ namespace Avalonia.Controls.Presenters
{
get
{
- return Vertical ? new Vector(0, OffsetValue) : new Vector(OffsetValue, 0);
+ return Vertical ? new Vector(_crossAxisOffset, OffsetValue) : new Vector(OffsetValue, _crossAxisOffset);
}
set
{
- OffsetValue = Vertical ? value.Y : value.X;
+ var oldCrossAxisOffset = _crossAxisOffset;
+
+ if (Vertical)
+ {
+ OffsetValue = value.Y;
+ _crossAxisOffset = value.X;
+ }
+ else
+ {
+ OffsetValue = value.X;
+ _crossAxisOffset = value.Y;
+ }
+
+ if (_crossAxisOffset != oldCrossAxisOffset)
+ {
+ Owner.InvalidateArrange();
+ }
}
}
@@ -143,6 +177,29 @@ namespace Avalonia.Controls.Presenters
return result;
}
+ ///
+ /// Carries out a measure for the related .
+ ///
+ /// The size available to the control.
+ /// The desired size for the control.
+ public virtual Size MeasureOverride(Size availableSize)
+ {
+ Owner.Panel.Measure(availableSize);
+ return Owner.Panel.DesiredSize;
+ }
+
+ ///
+ /// Carries out an arrange for the related .
+ ///
+ /// The size available to the control.
+ /// The actual size used.
+ public virtual Size ArrangeOverride(Size finalSize)
+ {
+ var origin = Vertical ? new Point(-_crossAxisOffset, 0) : new Point(0, _crossAxisOffset);
+ Owner.Panel.Arrange(new Rect(origin, finalSize));
+ return finalSize;
+ }
+
///
public virtual void UpdateControls()
{
diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
index afa5bb76db..228ad65ffa 100644
--- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
+++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
@@ -94,6 +94,44 @@ namespace Avalonia.Controls.Presenters
}
}
+ ///
+ public override Size MeasureOverride(Size availableSize)
+ {
+ var window = Owner.GetVisualRoot() as TopLevel;
+
+ // If infinity is passed as the available size and we're virtualized then we need to
+ // fill the available space, but to do that we *don't* want to materialize all our
+ // items! Take a look at the root of the tree for a MaxClientSize and use that as
+ // the available size.
+ if (VirtualizingPanel.ScrollDirection == Orientation.Vertical)
+ {
+ if (availableSize.Height == double.PositiveInfinity)
+ {
+ if (window != null)
+ {
+ availableSize = availableSize.WithHeight(window.PlatformImpl.MaxClientSize.Height);
+ }
+ }
+
+ availableSize = availableSize.WithWidth(double.PositiveInfinity);
+ }
+ else
+ {
+ if (availableSize.Width == double.PositiveInfinity)
+ {
+ if (window != null)
+ {
+ availableSize = availableSize.WithWidth(window.PlatformImpl.MaxClientSize.Width);
+ }
+ }
+
+ availableSize = availableSize.WithHeight(double.PositiveInfinity);
+ }
+
+ Owner.Panel.Measure(availableSize);
+ return Owner.Panel.DesiredSize;
+ }
+
///
public override void UpdateControls()
{
@@ -481,9 +519,19 @@ namespace Avalonia.Controls.Presenters
{
layoutManager.ExecuteLayoutPass();
- if (!new Rect(panel.Bounds.Size).Contains(container.Bounds))
+ if (panel.ScrollDirection == Orientation.Vertical)
{
- OffsetValue += 1;
+ if (container.Bounds.Y < panel.Bounds.Y || container.Bounds.Bottom > panel.Bounds.Bottom)
+ {
+ OffsetValue += 1;
+ }
+ }
+ else
+ {
+ if (container.Bounds.X < panel.Bounds.X || container.Bounds.Right > panel.Bounds.Right)
+ {
+ OffsetValue += 1;
+ }
}
}
diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs
index fa15766e4d..185193f889 100644
--- a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs
@@ -91,24 +91,15 @@ namespace Avalonia.Controls.Presenters
_virtualizer?.ScrollIntoView(item);
}
+ ///
protected override Size MeasureOverride(Size availableSize)
{
- // If infinity is passed as the available size and we're virtualized then we need to
- // fill the available space, but to do that we *don't* want to materialize all our
- // items! Take a look at the root of the tree for a MaxClientSize and use that as
- // the available size.
- if (availableSize == Size.Infinity && VirtualizationMode != ItemVirtualizationMode.None)
- {
- var window = VisualRoot as TopLevel;
-
- if (window != null)
- {
- availableSize = window.PlatformImpl.MaxClientSize;
- }
- }
+ return _virtualizer?.MeasureOverride(availableSize) ?? Size.Empty;
+ }
- Panel.Measure(availableSize);
- return Panel.DesiredSize;
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ return _virtualizer?.ArrangeOverride(finalSize) ?? Size.Empty;
}
///
diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs
index 85daff28b9..b7b83bf852 100644
--- a/src/Avalonia.Layout/LayoutManager.cs
+++ b/src/Avalonia.Layout/LayoutManager.cs
@@ -168,7 +168,7 @@ namespace Avalonia.Layout
private void QueueLayoutPass()
{
- if (!_queued)
+ if (!_queued && !_running)
{
Dispatcher.UIThread.InvokeAsync(ExecuteLayoutPass, DispatcherPriority.Render);
_queued = true;
diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs
index 02dffdead6..1433bc7f85 100644
--- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs
+++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs
@@ -111,7 +111,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
target.Arrange(new Rect(0, 0, 100, 100));
var scroll = (ScrollContentPresenter)target.Parent;
- Assert.Equal(new Size(0, 20), scroll.Extent);
+ Assert.Equal(new Size(10, 20), scroll.Extent);
Assert.Equal(new Size(0, 10), scroll.Viewport);
}
@@ -212,8 +212,8 @@ namespace Avalonia.Controls.UnitTests.Presenters
target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(10, target.Panel.Children.Count);
- Assert.Equal(new Size(0, 20), scroll.Extent);
- Assert.Equal(new Size(0, 10), scroll.Viewport);
+ Assert.Equal(new Size(10, 20), scroll.Extent);
+ Assert.Equal(new Size(100, 10), scroll.Viewport);
}
[Fact]
@@ -253,7 +253,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
scroll.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(10, target.Panel.Children.Count);
- Assert.Equal(new Size(0, 20), scroll.Extent);
+ Assert.Equal(new Size(10, 20), scroll.Extent);
Assert.Equal(new Size(0, 10), scroll.Viewport);
target.VirtualizationMode = ItemVirtualizationMode.None;
diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
index 2a59227b52..8d10b01f94 100644
--- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
+++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
@@ -49,7 +49,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
- Assert.Equal(new Size(0, 10), ((ILogicalScrollable)target).Viewport);
+ Assert.Equal(new Size(100, 10), ((ILogicalScrollable)target).Viewport);
}
[Fact]
@@ -61,7 +61,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
- Assert.Equal(new Size(10, 0), ((ILogicalScrollable)target).Viewport);
+ Assert.Equal(new Size(10, 100), ((ILogicalScrollable)target).Viewport);
}
[Fact]
@@ -146,7 +146,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
target.Measure(new Size(100, 95));
target.Arrange(new Rect(0, 0, 100, 95));
- Assert.Equal(new Size(0, 9), ((ILogicalScrollable)target).Viewport);
+ Assert.Equal(new Size(100, 9), ((ILogicalScrollable)target).Viewport);
}
[Fact]
@@ -772,6 +772,19 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Same(target.Panel.Children[0], result);
}
}
+
+ [Fact]
+ public void Should_Return_Horizontal_Extent_And_Viewport()
+ {
+ var target = CreateTarget();
+
+ target.ApplyTemplate();
+ target.Measure(new Size(5, 100));
+ target.Arrange(new Rect(0, 0, 5, 100));
+
+ Assert.Equal(new Size(10, 20), ((ILogicalScrollable)target).Extent);
+ Assert.Equal(new Size(5, 10), ((ILogicalScrollable)target).Viewport);
+ }
}
public class Horizontal