Browse Source

Merge branch 'master' into fixes/1343-rectanglegeometry-transform

pull/1349/head
Steven Kirk 9 years ago
committed by GitHub
parent
commit
2e108483df
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      samples/Previewer/MainWindow.xaml.cs
  2. 4
      samples/RemoteTest/Program.cs
  3. 12
      samples/VirtualizationTest/MainWindow.xaml
  4. 18
      samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs
  5. 2
      src/Avalonia.Base/AvaloniaObject.cs
  6. 4
      src/Avalonia.Base/PriorityBindingEntry.cs
  7. 2
      src/Avalonia.Base/Threading/AvaloniaScheduler.cs
  8. 4
      src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs
  9. 4
      src/Avalonia.Base/Threading/Dispatcher.cs
  10. 4
      src/Avalonia.Base/Threading/IDispatcher.cs
  11. 12
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  12. 27
      src/Avalonia.Controls/Presenters/ItemsPresenter.cs
  13. 63
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  14. 2
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  15. 4
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  16. 10
      src/Avalonia.Controls/Primitives/ILogicalScrollable.cs
  17. 1
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  18. 3
      src/Avalonia.Controls/Primitives/ScrollBarVisibility.cs
  19. 2
      src/Avalonia.Controls/Remote/RemoteWidget.cs
  20. 10
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  21. 97
      src/Avalonia.Controls/ScrollViewer.cs
  22. 22
      src/Avalonia.Controls/TextBox.cs
  23. 2
      src/Avalonia.Controls/TreeView.cs
  24. 2
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  25. 2
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  26. 2
      src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
  27. 2
      src/Avalonia.Layout/LayoutManager.cs
  28. 7
      src/Avalonia.Themes.Default/ListBox.xaml
  29. 5
      src/Avalonia.Themes.Default/ScrollViewer.xaml
  30. 3
      src/Avalonia.Themes.Default/TextBox.xaml
  31. 2
      src/Avalonia.Themes.Default/TreeView.xaml
  32. 2
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  33. 9
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  34. 12
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  35. 17
      src/Avalonia.Visuals/Visual.cs
  36. 51
      src/Avalonia.Visuals/VisualTree/BoundsTracker.cs
  37. 5
      src/Avalonia.Visuals/VisualTree/IVisual.cs
  38. 2
      src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
  39. 2
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  40. 2
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  41. 2
      src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs
  42. 2
      src/OSX/Avalonia.MonoMac/TopLevelImpl.cs
  43. 2
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs
  44. 2
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
  45. 11
      tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs
  46. 46
      tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests_ILogicalScrollable.cs
  47. 30
      tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs
  48. 2
      tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs
  49. 12
      tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Observable.cs
  50. 4
      tests/Avalonia.UnitTests/ImmediateDispatcher.cs
  51. 4
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs
  52. 2
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs
  53. 2
      tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs
  54. 5
      tests/Avalonia.Visuals.UnitTests/VisualTree/TransformedBoundsTests.cs

4
samples/Previewer/MainWindow.xaml.cs

@ -39,7 +39,7 @@ namespace Previewer
})); }));
new BsonTcpTransport().Listen(IPAddress.Loopback, 25000, t => new BsonTcpTransport().Listen(IPAddress.Loopback, 25000, t =>
{ {
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.Post(() =>
{ {
if (_connection != null) if (_connection != null)
{ {
@ -61,7 +61,7 @@ namespace Previewer
private void OnMessage(IAvaloniaRemoteTransportConnection transport, object obj) private void OnMessage(IAvaloniaRemoteTransportConnection transport, object obj)
{ {
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.Post(() =>
{ {
if (transport != _connection) if (transport != _connection)
return; return;

4
samples/RemoteTest/Program.cs

@ -25,7 +25,7 @@ namespace RemoteTest
var transport = new BsonTcpTransport(); var transport = new BsonTcpTransport();
transport.Listen(IPAddress.Loopback, port, sc => transport.Listen(IPAddress.Loopback, port, sc =>
{ {
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.Post(() =>
{ {
new RemoteServer(sc).Content = new MainView(); new RemoteServer(sc).Content = new MainView();
}); });
@ -34,7 +34,7 @@ namespace RemoteTest
var cts = new CancellationTokenSource(); var cts = new CancellationTokenSource();
transport.Connect(IPAddress.Loopback, port).ContinueWith(t => transport.Connect(IPAddress.Loopback, port).ContinueWith(t =>
{ {
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.Post(() =>
{ {
var window = new Window() var window = new Window()
{ {

12
samples/VirtualizationTest/MainWindow.xaml

@ -21,6 +21,12 @@
<TextBox Watermark="Viewport" <TextBox Watermark="Viewport"
UseFloatingWatermark="True" UseFloatingWatermark="True"
Text="{Binding #listBox.Scroll.Viewport, Mode=OneWay}"/> Text="{Binding #listBox.Scroll.Viewport, Mode=OneWay}"/>
<TextBlock>Horiz. ScrollBar</TextBlock>
<DropDown Items="{Binding ScrollBarVisibilities}"
SelectedItem="{Binding HorizontalScrollBarVisibility}"/>
<TextBlock>Vert. ScrollBar</TextBlock>
<DropDown Items="{Binding ScrollBarVisibilities}"
SelectedItem="{Binding VerticalScrollBarVisibility}"/>
<TextBox Watermark="Item to Create" <TextBox Watermark="Item to Create"
UseFloatingWatermark="True" UseFloatingWatermark="True"
Text="{Binding NewItemString}"/> Text="{Binding NewItemString}"/>
@ -35,7 +41,9 @@
Items="{Binding Items}" Items="{Binding Items}"
SelectedItems="{Binding SelectedItems}" SelectedItems="{Binding SelectedItems}"
SelectionMode="Multiple" SelectionMode="Multiple"
VirtualizationMode="{Binding VirtualizationMode}"> VirtualizationMode="{Binding VirtualizationMode}"
ScrollViewer.HorizontalScrollBarVisibility="{Binding HorizontalScrollBarVisibility, Mode=TwoWay}"
ScrollViewer.VerticalScrollBarVisibility="{Binding VerticalScrollBarVisibility, Mode=TwoWay}">
<ListBox.ItemsPanel> <ListBox.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="{Binding Orientation}"/> <VirtualizingStackPanel Orientation="{Binding Orientation}"/>
@ -43,7 +51,7 @@
</ListBox.ItemsPanel> </ListBox.ItemsPanel>
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate> <DataTemplate>
<TextBlock Text="{Binding Header}"/> <TextBlock Text="{Binding Header}" TextWrapping="Wrap"/>
</DataTemplate> </DataTemplate>
</ListBox.ItemTemplate> </ListBox.ItemTemplate>
</ListBox> </ListBox>

18
samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using ReactiveUI; using ReactiveUI;
namespace VirtualizationTest.ViewModels namespace VirtualizationTest.ViewModels
@ -17,6 +18,8 @@ namespace VirtualizationTest.ViewModels
private int _newItemIndex; private int _newItemIndex;
private IReactiveList<ItemViewModel> _items; private IReactiveList<ItemViewModel> _items;
private string _prefix = "Item"; private string _prefix = "Item";
private ScrollBarVisibility _horizontalScrollBarVisibility = ScrollBarVisibility.Auto;
private ScrollBarVisibility _verticalScrollBarVisibility = ScrollBarVisibility.Auto;
private Orientation _orientation = Orientation.Vertical; private Orientation _orientation = Orientation.Vertical;
private ItemVirtualizationMode _virtualizationMode = ItemVirtualizationMode.Simple; private ItemVirtualizationMode _virtualizationMode = ItemVirtualizationMode.Simple;
@ -64,6 +67,21 @@ namespace VirtualizationTest.ViewModels
public IEnumerable<Orientation> Orientations => public IEnumerable<Orientation> Orientations =>
Enum.GetValues(typeof(Orientation)).Cast<Orientation>(); Enum.GetValues(typeof(Orientation)).Cast<Orientation>();
public ScrollBarVisibility HorizontalScrollBarVisibility
{
get { return _horizontalScrollBarVisibility; }
set { this.RaiseAndSetIfChanged(ref _horizontalScrollBarVisibility, value); }
}
public ScrollBarVisibility VerticalScrollBarVisibility
{
get { return _verticalScrollBarVisibility; }
set { this.RaiseAndSetIfChanged(ref _verticalScrollBarVisibility, value); }
}
public IEnumerable<ScrollBarVisibility> ScrollBarVisibilities =>
Enum.GetValues(typeof(ScrollBarVisibility)).Cast<ScrollBarVisibility>();
public ItemVirtualizationMode VirtualizationMode public ItemVirtualizationMode VirtualizationMode
{ {
get { return _virtualizationMode; } get { return _virtualizationMode; }

2
src/Avalonia.Base/AvaloniaObject.cs

@ -774,7 +774,7 @@ namespace Avalonia
} }
else else
{ {
Dispatcher.UIThread.InvokeAsync(Set); Dispatcher.UIThread.Post(Set);
} }
} }

4
src/Avalonia.Base/PriorityBindingEntry.cs

@ -123,7 +123,7 @@ namespace Avalonia
} }
else else
{ {
Dispatcher.UIThread.InvokeAsync(Signal); Dispatcher.UIThread.Post(Signal);
} }
} }
@ -135,7 +135,7 @@ namespace Avalonia
} }
else else
{ {
Dispatcher.UIThread.InvokeAsync(() => _owner.Completed(this)); Dispatcher.UIThread.Post(() => _owner.Completed(this));
} }
} }
} }

2
src/Avalonia.Base/Threading/AvaloniaScheduler.cs

@ -33,7 +33,7 @@ namespace Avalonia.Threading
if (!Dispatcher.UIThread.CheckAccess()) if (!Dispatcher.UIThread.CheckAccess())
{ {
var cancellation = new CancellationDisposable(); var cancellation = new CancellationDisposable();
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.Post(() =>
{ {
if (!cancellation.Token.IsCancellationRequested) if (!cancellation.Token.IsCancellationRequested)
{ {

4
src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs

@ -36,7 +36,7 @@ namespace Avalonia.Threading
/// <inheritdoc/> /// <inheritdoc/>
public override void Post(SendOrPostCallback d, object state) public override void Post(SendOrPostCallback d, object state)
{ {
Dispatcher.UIThread.InvokeAsync(() => d(state), DispatcherPriority.Send); Dispatcher.UIThread.Post(() => d(state), DispatcherPriority.Send);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -45,7 +45,7 @@ namespace Avalonia.Threading
if (Dispatcher.UIThread.CheckAccess()) if (Dispatcher.UIThread.CheckAccess())
d(state); d(state);
else else
Dispatcher.UIThread.InvokeTaskAsync(() => d(state), DispatcherPriority.Send).Wait(); Dispatcher.UIThread.InvokeAsync(() => d(state), DispatcherPriority.Send).Wait();
} }
} }
} }

4
src/Avalonia.Base/Threading/Dispatcher.cs

@ -79,13 +79,13 @@ namespace Avalonia.Threading
public void RunJobs(DispatcherPriority minimumPriority) => _jobRunner.RunJobs(minimumPriority); public void RunJobs(DispatcherPriority minimumPriority) => _jobRunner.RunJobs(minimumPriority);
/// <inheritdoc/> /// <inheritdoc/>
public Task InvokeTaskAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{ {
return _jobRunner?.InvokeAsync(action, priority); return _jobRunner?.InvokeAsync(action, priority);
} }
/// <inheritdoc/> /// <inheritdoc/>
public void InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) public void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{ {
_jobRunner?.Post(action, priority); _jobRunner?.Post(action, priority);
} }

4
src/Avalonia.Base/Threading/IDispatcher.cs

@ -25,7 +25,7 @@ namespace Avalonia.Threading
/// <param name="action">The method.</param> /// <param name="action">The method.</param>
/// <param name="priority">The priority with which to invoke the method.</param> /// <param name="priority">The priority with which to invoke the method.</param>
/// <returns>A task that can be used to track the method's execution.</returns> /// <returns>A task that can be used to track the method's execution.</returns>
void InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal); void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal);
/// <summary> /// <summary>
/// Post action that will be invoked on main thread /// Post action that will be invoked on main thread
@ -34,6 +34,6 @@ namespace Avalonia.Threading
/// <param name="priority">The priority with which to invoke the method.</param> /// <param name="priority">The priority with which to invoke the method.</param>
// TODO: The naming of this method is confusing: the Async suffix usually means return a task. // TODO: The naming of this method is confusing: the Async suffix usually means return a task.
// Remove this and rename InvokeTaskAsync as InvokeAsync. See #816. // Remove this and rename InvokeTaskAsync as InvokeAsync. See #816.
Task InvokeTaskAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal); Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal);
} }
} }

12
src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs

@ -5,6 +5,7 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq; using System.Linq;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Utils; using Avalonia.Controls.Utils;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Layout; using Avalonia.Layout;
@ -97,6 +98,7 @@ namespace Avalonia.Controls.Presenters
/// <inheritdoc/> /// <inheritdoc/>
public override Size MeasureOverride(Size availableSize) public override Size MeasureOverride(Size availableSize)
{ {
var scrollable = (ILogicalScrollable)Owner;
var visualRoot = Owner.GetVisualRoot(); var visualRoot = Owner.GetVisualRoot();
var maxAvailableSize = (visualRoot as WindowBase)?.PlatformImpl?.MaxClientSize var maxAvailableSize = (visualRoot as WindowBase)?.PlatformImpl?.MaxClientSize
?? (visualRoot as TopLevel)?.ClientSize; ?? (visualRoot as TopLevel)?.ClientSize;
@ -115,7 +117,10 @@ namespace Avalonia.Controls.Presenters
} }
} }
availableSize = availableSize.WithWidth(double.PositiveInfinity); if (scrollable.CanHorizontallyScroll)
{
availableSize = availableSize.WithWidth(double.PositiveInfinity);
}
} }
else else
{ {
@ -127,7 +132,10 @@ namespace Avalonia.Controls.Presenters
} }
} }
availableSize = availableSize.WithHeight(double.PositiveInfinity); if (scrollable.CanVerticallyScroll)
{
availableSize = availableSize.WithHeight(double.PositiveInfinity);
}
} }
Owner.Panel.Measure(availableSize); Owner.Panel.Measure(availableSize);

27
src/Avalonia.Controls/Presenters/ItemsPresenter.cs

@ -23,6 +23,8 @@ namespace Avalonia.Controls.Presenters
defaultValue: ItemVirtualizationMode.None); defaultValue: ItemVirtualizationMode.None);
private ItemVirtualizer _virtualizer; private ItemVirtualizer _virtualizer;
private bool _canHorizontallyScroll;
private bool _canVerticallyScroll;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="ItemsPresenter"/> class. /// Initializes static members of the <see cref="ItemsPresenter"/> class.
@ -46,6 +48,31 @@ namespace Avalonia.Controls.Presenters
set { SetValue(VirtualizationModeProperty, value); } set { SetValue(VirtualizationModeProperty, value); }
} }
/// <summary>
/// Gets or sets a value indicating whether the content can be scrolled horizontally.
/// </summary>
bool ILogicalScrollable.CanHorizontallyScroll
{
get { return _canHorizontallyScroll; }
set
{
_canHorizontallyScroll = value;
InvalidateMeasure();
}
}
/// <summary>
/// Gets or sets a value indicating whether the content can be scrolled horizontally.
/// </summary>
bool ILogicalScrollable.CanVerticallyScroll
{
get { return _canVerticallyScroll; }
set
{
_canVerticallyScroll = value;
InvalidateMeasure();
}
}
/// <inheritdoc/> /// <inheritdoc/>
bool ILogicalScrollable.IsLogicalScrollEnabled bool ILogicalScrollable.IsLogicalScrollEnabled
{ {

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

@ -17,6 +17,24 @@ namespace Avalonia.Controls.Presenters
/// </summary> /// </summary>
public class ScrollContentPresenter : ContentPresenter, IPresenter, IScrollable public class ScrollContentPresenter : ContentPresenter, IPresenter, IScrollable
{ {
/// <summary>
/// Defines the <see cref="CanHorizontallyScroll"/> property.
/// </summary>
public static readonly DirectProperty<ScrollContentPresenter, bool> CanHorizontallyScrollProperty =
AvaloniaProperty.RegisterDirect<ScrollContentPresenter, bool>(
nameof(CanHorizontallyScroll),
o => o.CanHorizontallyScroll,
(o, v) => o.CanHorizontallyScroll = v);
/// <summary>
/// Defines the <see cref="CanVerticallyScroll"/> property.
/// </summary>
public static readonly DirectProperty<ScrollContentPresenter, bool> CanVerticallyScrollProperty =
AvaloniaProperty.RegisterDirect<ScrollContentPresenter, bool>(
nameof(CanVerticallyScroll),
o => o.CanVerticallyScroll,
(o, v) => o.CanVerticallyScroll = v);
/// <summary> /// <summary>
/// Defines the <see cref="Extent"/> property. /// Defines the <see cref="Extent"/> property.
/// </summary> /// </summary>
@ -41,12 +59,8 @@ namespace Avalonia.Controls.Presenters
o => o.Viewport, o => o.Viewport,
(o, v) => o.Viewport = v); (o, v) => o.Viewport = v);
/// <summary> private bool _canHorizontallyScroll;
/// Defines the <see cref="CanScrollHorizontally"/> property. private bool _canVerticallyScroll;
/// </summary>
public static readonly StyledProperty<bool> CanScrollHorizontallyProperty =
ScrollViewer.CanScrollHorizontallyProperty.AddOwner<ScrollContentPresenter>();
private Size _extent; private Size _extent;
private Size _measuredExtent; private Size _measuredExtent;
private Vector _offset; private Vector _offset;
@ -73,6 +87,24 @@ namespace Avalonia.Controls.Presenters
this.GetObservable(ChildProperty).Subscribe(UpdateScrollableSubscription); this.GetObservable(ChildProperty).Subscribe(UpdateScrollableSubscription);
} }
/// <summary>
/// Gets or sets a value indicating whether the content can be scrolled horizontally.
/// </summary>
public bool CanHorizontallyScroll
{
get { return _canHorizontallyScroll; }
set { SetAndRaise(CanHorizontallyScrollProperty, ref _canHorizontallyScroll, value); }
}
/// <summary>
/// Gets or sets a value indicating whether the content can be scrolled horizontally.
/// </summary>
public bool CanVerticallyScroll
{
get { return _canVerticallyScroll; }
set { SetAndRaise(CanVerticallyScrollProperty, ref _canVerticallyScroll, value); }
}
/// <summary> /// <summary>
/// Gets the extent of the scrollable content. /// Gets the extent of the scrollable content.
/// </summary> /// </summary>
@ -100,11 +132,6 @@ namespace Avalonia.Controls.Presenters
private set { SetAndRaise(ViewportProperty, ref _viewport, value); } private set { SetAndRaise(ViewportProperty, ref _viewport, value); }
} }
/// <summary>
/// Gets a value indicating whether the content can be scrolled horizontally.
/// </summary>
public bool CanScrollHorizontally => GetValue(CanScrollHorizontallyProperty);
/// <summary> /// <summary>
/// Attempts to bring a portion of the target visual into view by scrolling the content. /// Attempts to bring a portion of the target visual into view by scrolling the content.
/// </summary> /// </summary>
@ -182,10 +209,15 @@ namespace Avalonia.Controls.Presenters
{ {
measureSize = new Size(double.PositiveInfinity, double.PositiveInfinity); measureSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
if (!CanScrollHorizontally) if (!CanHorizontallyScroll)
{ {
measureSize = measureSize.WithWidth(availableSize.Width); measureSize = measureSize.WithWidth(availableSize.Width);
} }
if (!CanVerticallyScroll)
{
measureSize = measureSize.WithHeight(availableSize.Height);
}
} }
child.Measure(measureSize); child.Measure(measureSize);
@ -289,7 +321,12 @@ namespace Avalonia.Controls.Presenters
if (scrollable.IsLogicalScrollEnabled == true) if (scrollable.IsLogicalScrollEnabled == true)
{ {
_logicalScrollSubscription = new CompositeDisposable( _logicalScrollSubscription = new CompositeDisposable(
this.GetObservable(OffsetProperty).Skip(1).Subscribe(x => scrollable.Offset = x), this.GetObservable(CanHorizontallyScrollProperty)
.Subscribe(x => scrollable.CanHorizontallyScroll = x),
this.GetObservable(CanVerticallyScrollProperty)
.Subscribe(x => scrollable.CanVerticallyScroll = x),
this.GetObservable(OffsetProperty)
.Skip(1).Subscribe(x => scrollable.Offset = x),
Disposable.Create(() => scrollable.InvalidateScroll = null)); Disposable.Create(() => scrollable.InvalidateScroll = null));
UpdateFromScrollable(scrollable); UpdateFromScrollable(scrollable);
} }

2
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -191,7 +191,7 @@ namespace Avalonia.Controls.Presenters
// The measure is currently invalid so there's no point trying to bring the // The measure is currently invalid so there's no point trying to bring the
// current char into view until a measure has been carried out as the scroll // current char into view until a measure has been carried out as the scroll
// viewer extents may not be up-to-date. // viewer extents may not be up-to-date.
Dispatcher.UIThread.InvokeAsync( Dispatcher.UIThread.Post(
() => () =>
{ {
var rect = FormattedText.HitTestTextPosition(caretIndex); var rect = FormattedText.HitTestTextPosition(caretIndex);

4
src/Avalonia.Controls/Primitives/AdornerLayer.cs

@ -18,8 +18,6 @@ namespace Avalonia.Controls.Primitives
private static readonly AttachedProperty<AdornedElementInfo> s_adornedElementInfoProperty = private static readonly AttachedProperty<AdornedElementInfo> s_adornedElementInfoProperty =
AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, AdornedElementInfo>("AdornedElementInfo"); AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, AdornedElementInfo>("AdornedElementInfo");
private readonly BoundsTracker _tracker = new BoundsTracker();
static AdornerLayer() static AdornerLayer()
{ {
AdornedElementProperty.Changed.Subscribe(AdornedElementChanged); AdornedElementProperty.Changed.Subscribe(AdornedElementChanged);
@ -118,7 +116,7 @@ namespace Avalonia.Controls.Primitives
adorner.SetValue(s_adornedElementInfoProperty, info); adorner.SetValue(s_adornedElementInfoProperty, info);
} }
info.Subscription = _tracker.Track(adorned).Subscribe(x => info.Subscription = adorned.GetObservable(TransformedBoundsProperty).Subscribe(x =>
{ {
info.Bounds = x; info.Bounds = x;
InvalidateArrange(); InvalidateArrange();

10
src/Avalonia.Controls/Primitives/ILogicalScrollable.cs

@ -19,6 +19,16 @@ namespace Avalonia.Controls.Primitives
/// </remarks> /// </remarks>
public interface ILogicalScrollable : IScrollable public interface ILogicalScrollable : IScrollable
{ {
/// <summary>
/// Gets or sets a value indicating whether the content can be scrolled horizontally.
/// </summary>
bool CanHorizontallyScroll { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the content can be scrolled horizontally.
/// </summary>
bool CanVerticallyScroll { get; set; }
/// <summary> /// <summary>
/// Gets a value indicating whether logical scrolling is enabled on the control. /// Gets a value indicating whether logical scrolling is enabled on the control.
/// </summary> /// </summary>

1
src/Avalonia.Controls/Primitives/ScrollBar.cs

@ -99,6 +99,7 @@ namespace Avalonia.Controls.Primitives
case ScrollBarVisibility.Visible: case ScrollBarVisibility.Visible:
return true; return true;
case ScrollBarVisibility.Disabled:
case ScrollBarVisibility.Hidden: case ScrollBarVisibility.Hidden:
return false; return false;

3
src/Avalonia.Controls/Primitives/ScrollBarVisibility.cs

@ -5,8 +5,9 @@ namespace Avalonia.Controls.Primitives
{ {
public enum ScrollBarVisibility public enum ScrollBarVisibility
{ {
Disabled,
Auto, Auto,
Visible,
Hidden, Hidden,
Visible,
} }
} }

2
src/Avalonia.Controls/Remote/RemoteWidget.cs

@ -18,7 +18,7 @@ namespace Avalonia.Controls.Remote
public RemoteWidget(IAvaloniaRemoteTransportConnection connection) public RemoteWidget(IAvaloniaRemoteTransportConnection connection)
{ {
_connection = connection; _connection = connection;
_connection.OnMessage += (t, msg) => Dispatcher.UIThread.InvokeAsync(() => OnMessage(msg)); _connection.OnMessage += (t, msg) => Dispatcher.UIThread.Post(() => OnMessage(msg));
_connection.Send(new ClientSupportedPixelFormatsMessage _connection.Send(new ClientSupportedPixelFormatsMessage
{ {
Formats = new[] Formats = new[]

10
src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs

@ -46,16 +46,16 @@ namespace Avalonia.Controls.Remote.Server
{ {
_lastReceivedFrame = lastFrame.SequenceId; _lastReceivedFrame = lastFrame.SequenceId;
} }
Dispatcher.UIThread.InvokeAsync(RenderIfNeeded); Dispatcher.UIThread.Post(RenderIfNeeded);
} }
if (obj is ClientSupportedPixelFormatsMessage supportedFormats) if (obj is ClientSupportedPixelFormatsMessage supportedFormats)
{ {
lock (_lock) lock (_lock)
_supportedFormats = supportedFormats.Formats; _supportedFormats = supportedFormats.Formats;
Dispatcher.UIThread.InvokeAsync(RenderIfNeeded); Dispatcher.UIThread.Post(RenderIfNeeded);
} }
if (obj is MeasureViewportMessage measure) if (obj is MeasureViewportMessage measure)
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.Post(() =>
{ {
var m = Measure(new Size(measure.Width, measure.Height)); var m = Measure(new Size(measure.Width, measure.Height));
_transport.Send(new MeasureViewportMessage _transport.Send(new MeasureViewportMessage
@ -69,7 +69,7 @@ namespace Avalonia.Controls.Remote.Server
lock (_lock) lock (_lock)
{ {
if (_pendingAllocation == null) if (_pendingAllocation == null)
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.Post(() =>
{ {
ClientViewportAllocatedMessage allocation; ClientViewportAllocatedMessage allocation;
lock (_lock) lock (_lock)
@ -168,7 +168,7 @@ namespace Avalonia.Controls.Remote.Server
public override void Invalidate(Rect rect) public override void Invalidate(Rect rect)
{ {
_invalidated = true; _invalidated = true;
Dispatcher.UIThread.InvokeAsync(RenderIfNeeded); Dispatcher.UIThread.Post(RenderIfNeeded);
} }
public override IMouseDevice MouseDevice { get; } = new MouseDevice(); public override IMouseDevice MouseDevice { get; } = new MouseDevice();

97
src/Avalonia.Controls/ScrollViewer.cs

@ -2,8 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
@ -15,16 +13,34 @@ namespace Avalonia.Controls
public class ScrollViewer : ContentControl, IScrollable public class ScrollViewer : ContentControl, IScrollable
{ {
/// <summary> /// <summary>
/// Defines the <see cref="CanScrollHorizontally"/> property. /// Defines the <see cref="CanHorizontallyScroll"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<bool> CanScrollHorizontallyProperty = /// <remarks>
AvaloniaProperty.Register<ScrollViewer, bool>(nameof(CanScrollHorizontally), true); /// There is no public C# accessor for this property as it is intended to be bound to by a
/// <see cref="ScrollContentPresenter"/> in the control's template.
/// </remarks>
public static readonly DirectProperty<ScrollViewer, bool> CanHorizontallyScrollProperty =
AvaloniaProperty.RegisterDirect<ScrollViewer, bool>(
nameof(CanHorizontallyScroll),
o => o.CanHorizontallyScroll);
/// <summary>
/// Defines the <see cref="CanVerticallyScroll"/> property.
/// </summary>
/// <remarks>
/// There is no public C# accessor for this property as it is intended to be bound to by a
/// <see cref="ScrollContentPresenter"/> in the control's template.
/// </remarks>
public static readonly DirectProperty<ScrollViewer, bool> CanVerticallyScrollProperty =
AvaloniaProperty.RegisterDirect<ScrollViewer, bool>(
nameof(CanVerticallyScroll),
o => o.CanVerticallyScroll);
/// <summary> /// <summary>
/// Defines the <see cref="Extent"/> property. /// Defines the <see cref="Extent"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<ScrollViewer, Size> ExtentProperty = public static readonly DirectProperty<ScrollViewer, Size> ExtentProperty =
AvaloniaProperty.RegisterDirect<ScrollViewer, Size>(nameof(Extent), AvaloniaProperty.RegisterDirect<ScrollViewer, Size>(nameof(Extent),
o => o.Extent, o => o.Extent,
(o, v) => o.Extent = v); (o, v) => o.Extent = v);
@ -41,7 +57,7 @@ namespace Avalonia.Controls
/// Defines the <see cref="Viewport"/> property. /// Defines the <see cref="Viewport"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<ScrollViewer, Size> ViewportProperty = public static readonly DirectProperty<ScrollViewer, Size> ViewportProperty =
AvaloniaProperty.RegisterDirect<ScrollViewer, Size>(nameof(Viewport), AvaloniaProperty.RegisterDirect<ScrollViewer, Size>(nameof(Viewport),
o => o.Viewport, o => o.Viewport,
(o, v) => o.Viewport = v); (o, v) => o.Viewport = v);
@ -85,14 +101,10 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="HorizontalScrollBarVisibility"/> property. /// Defines the <see cref="HorizontalScrollBarVisibility"/> property.
/// </summary> /// </summary>
/// <remarks>
/// There is no public C# accessor for this property as it is intended to be bound to by a
/// <see cref="ScrollContentPresenter"/> in the control's template.
/// </remarks>
public static readonly AttachedProperty<ScrollBarVisibility> HorizontalScrollBarVisibilityProperty = public static readonly AttachedProperty<ScrollBarVisibility> HorizontalScrollBarVisibilityProperty =
AvaloniaProperty.RegisterAttached<ScrollViewer, Control, ScrollBarVisibility>( AvaloniaProperty.RegisterAttached<ScrollViewer, Control, ScrollBarVisibility>(
nameof(HorizontalScrollBarVisibility), nameof(HorizontalScrollBarVisibility),
ScrollBarVisibility.Auto); ScrollBarVisibility.Hidden);
/// <summary> /// <summary>
/// Defines the VerticalScrollBarMaximum property. /// Defines the VerticalScrollBarMaximum property.
@ -136,7 +148,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public static readonly AttachedProperty<ScrollBarVisibility> VerticalScrollBarVisibilityProperty = public static readonly AttachedProperty<ScrollBarVisibility> VerticalScrollBarVisibilityProperty =
AvaloniaProperty.RegisterAttached<ScrollViewer, Control, ScrollBarVisibility>( AvaloniaProperty.RegisterAttached<ScrollViewer, Control, ScrollBarVisibility>(
nameof(VerticalScrollBarVisibility), nameof(VerticalScrollBarVisibility),
ScrollBarVisibility.Auto); ScrollBarVisibility.Auto);
private Size _extent; private Size _extent;
@ -150,6 +162,8 @@ namespace Avalonia.Controls
{ {
AffectsValidation(ExtentProperty, OffsetProperty); AffectsValidation(ExtentProperty, OffsetProperty);
AffectsValidation(ViewportProperty, OffsetProperty); AffectsValidation(ViewportProperty, OffsetProperty);
HorizontalScrollBarVisibilityProperty.Changed.AddClassHandler<ScrollViewer>(x => x.ScrollBarVisibilityChanged);
VerticalScrollBarVisibilityProperty.Changed.AddClassHandler<ScrollViewer>(x => x.ScrollBarVisibilityChanged);
} }
/// <summary> /// <summary>
@ -218,15 +232,6 @@ namespace Avalonia.Controls
} }
} }
/// <summary>
/// Gets a value indicating whether the content can be scrolled horizontally.
/// </summary>
public bool CanScrollHorizontally
{
get { return GetValue(CanScrollHorizontallyProperty); }
set { SetValue(CanScrollHorizontallyProperty, value); }
}
/// <summary> /// <summary>
/// Gets or sets the horizontal scrollbar visibility. /// Gets or sets the horizontal scrollbar visibility.
/// </summary> /// </summary>
@ -245,6 +250,22 @@ namespace Avalonia.Controls
set { SetValue(VerticalScrollBarVisibilityProperty, value); } set { SetValue(VerticalScrollBarVisibilityProperty, value); }
} }
/// <summary>
/// Gets a value indicating whether the viewer can scroll horizontally.
/// </summary>
protected bool CanHorizontallyScroll
{
get { return HorizontalScrollBarVisibility != ScrollBarVisibility.Disabled; }
}
/// <summary>
/// Gets a value indicating whether the viewer can scroll vertically.
/// </summary>
protected bool CanVerticallyScroll
{
get { return VerticalScrollBarVisibility != ScrollBarVisibility.Disabled; }
}
/// <summary> /// <summary>
/// Gets the maximum horizontal scrollbar value. /// Gets the maximum horizontal scrollbar value.
/// </summary> /// </summary>
@ -316,7 +337,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
/// <param name="control">The control to read the value from.</param> /// <param name="control">The control to read the value from.</param>
/// <returns>The value of the property.</returns> /// <returns>The value of the property.</returns>
public ScrollBarVisibility GetHorizontalScrollBarVisibility(Control control) public static ScrollBarVisibility GetHorizontalScrollBarVisibility(Control control)
{ {
return control.GetValue(HorizontalScrollBarVisibilityProperty); return control.GetValue(HorizontalScrollBarVisibilityProperty);
} }
@ -326,7 +347,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
/// <param name="control">The control to set the value on.</param> /// <param name="control">The control to set the value on.</param>
/// <param name="value">The value of the property.</param> /// <param name="value">The value of the property.</param>
public void SetHorizontalScrollBarVisibility(Control control, ScrollBarVisibility value) public static void SetHorizontalScrollBarVisibility(Control control, ScrollBarVisibility value)
{ {
control.SetValue(HorizontalScrollBarVisibilityProperty, value); control.SetValue(HorizontalScrollBarVisibilityProperty, value);
} }
@ -336,7 +357,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
/// <param name="control">The control to read the value from.</param> /// <param name="control">The control to read the value from.</param>
/// <returns>The value of the property.</returns> /// <returns>The value of the property.</returns>
public ScrollBarVisibility GetVerticalScrollBarVisibility(Control control) public static ScrollBarVisibility GetVerticalScrollBarVisibility(Control control)
{ {
return control.GetValue(VerticalScrollBarVisibilityProperty); return control.GetValue(VerticalScrollBarVisibilityProperty);
} }
@ -346,7 +367,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
/// <param name="control">The control to set the value on.</param> /// <param name="control">The control to set the value on.</param>
/// <param name="value">The value of the property.</param> /// <param name="value">The value of the property.</param>
public void SetVerticalScrollBarVisibility(Control control, ScrollBarVisibility value) public static void SetVerticalScrollBarVisibility(Control control, ScrollBarVisibility value)
{ {
control.SetValue(VerticalScrollBarVisibilityProperty, value); control.SetValue(VerticalScrollBarVisibilityProperty, value);
} }
@ -385,6 +406,30 @@ namespace Avalonia.Controls
} }
} }
private void ScrollBarVisibilityChanged(AvaloniaPropertyChangedEventArgs e)
{
var wasEnabled = !ScrollBarVisibility.Disabled.Equals(e.OldValue);
var isEnabled = !ScrollBarVisibility.Disabled.Equals(e.NewValue);
if (wasEnabled != isEnabled)
{
if (e.Property == HorizontalScrollBarVisibilityProperty)
{
RaisePropertyChanged(
CanHorizontallyScrollProperty,
wasEnabled,
isEnabled);
}
else if (e.Property == VerticalScrollBarVisibilityProperty)
{
RaisePropertyChanged(
CanVerticallyScrollProperty,
wasEnabled,
isEnabled);
}
}
}
private void CalculatedPropertiesChanged() private void CalculatedPropertiesChanged()
{ {
// Pass old values of 0 here because we don't have the old values at this point, // Pass old values of 0 here because we don't have the old values at this point,

22
src/Avalonia.Controls/TextBox.cs

@ -8,7 +8,6 @@ using System.Linq;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils; using Avalonia.Controls.Utils;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
@ -26,9 +25,6 @@ namespace Avalonia.Controls
public static readonly StyledProperty<bool> AcceptsTabProperty = public static readonly StyledProperty<bool> AcceptsTabProperty =
AvaloniaProperty.Register<TextBox, bool>(nameof(AcceptsTab)); AvaloniaProperty.Register<TextBox, bool>(nameof(AcceptsTab));
public static readonly DirectProperty<TextBox, bool> CanScrollHorizontallyProperty =
AvaloniaProperty.RegisterDirect<TextBox, bool>(nameof(CanScrollHorizontally), o => o.CanScrollHorizontally);
public static readonly DirectProperty<TextBox, int> CaretIndexProperty = public static readonly DirectProperty<TextBox, int> CaretIndexProperty =
AvaloniaProperty.RegisterDirect<TextBox, int>( AvaloniaProperty.RegisterDirect<TextBox, int>(
nameof(CaretIndex), nameof(CaretIndex),
@ -92,7 +88,6 @@ namespace Avalonia.Controls
private int _caretIndex; private int _caretIndex;
private int _selectionStart; private int _selectionStart;
private int _selectionEnd; private int _selectionEnd;
private bool _canScrollHorizontally;
private TextPresenter _presenter; private TextPresenter _presenter;
private UndoRedoHelper<UndoRedoState> _undoRedoHelper; private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
private bool _ignoreTextChanges; private bool _ignoreTextChanges;
@ -106,12 +101,11 @@ namespace Avalonia.Controls
public TextBox() public TextBox()
{ {
this.GetObservable(TextWrappingProperty) var horizontalScrollBarVisibility = Observable.CombineLatest(
.Select(x => x == TextWrapping.NoWrap) this.GetObservable(AcceptsReturnProperty),
.Subscribe(x => CanScrollHorizontally = x); this.GetObservable(TextWrappingProperty),
(acceptsReturn, wrapping) => acceptsReturn && wrapping == TextWrapping.NoWrap ?
var horizontalScrollBarVisibility = this.GetObservable(AcceptsReturnProperty) ScrollBarVisibility.Auto : ScrollBarVisibility.Disabled);
.Select(x => x ? ScrollBarVisibility.Auto : ScrollBarVisibility.Hidden);
Bind( Bind(
ScrollViewer.HorizontalScrollBarVisibilityProperty, ScrollViewer.HorizontalScrollBarVisibilityProperty,
@ -132,12 +126,6 @@ namespace Avalonia.Controls
set { SetValue(AcceptsTabProperty, value); } set { SetValue(AcceptsTabProperty, value); }
} }
public bool CanScrollHorizontally
{
get { return _canScrollHorizontally; }
private set { SetAndRaise(CanScrollHorizontallyProperty, ref _canScrollHorizontally, value); }
}
public int CaretIndex public int CaretIndex
{ {
get get

2
src/Avalonia.Controls/TreeView.cs

@ -250,7 +250,7 @@ namespace Avalonia.Controls
if (AutoScrollToSelectedItem) if (AutoScrollToSelectedItem)
{ {
Dispatcher.UIThread.InvokeAsync(container.ContainerControl.BringIntoView); Dispatcher.UIThread.Post(container.ContainerControl.BringIntoView);
} }
break; break;

2
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -3,11 +3,9 @@
using System; using System;
using System.Collections.Specialized; using System.Collections.Specialized;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.VisualTree;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {

2
src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs

@ -49,7 +49,7 @@ namespace Avalonia.DesignerSupport.Remote
// In previewer mode we completely ignore client-side viewport size // In previewer mode we completely ignore client-side viewport size
if (obj is ClientViewportAllocatedMessage alloc) if (obj is ClientViewportAllocatedMessage alloc)
{ {
Dispatcher.UIThread.InvokeAsync(() => SetDpi(new Vector(alloc.DpiX, alloc.DpiY))); Dispatcher.UIThread.Post(() => SetDpi(new Vector(alloc.DpiX, alloc.DpiY)));
return; return;
} }
base.OnMessage(transport, obj); base.OnMessage(transport, obj);

2
src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs

@ -140,7 +140,7 @@ namespace Avalonia.DesignerSupport.Remote
}; };
} }
private static void OnTransportMessage(IAvaloniaRemoteTransportConnection transport, object obj) => Dispatcher.UIThread.InvokeAsync(() => private static void OnTransportMessage(IAvaloniaRemoteTransportConnection transport, object obj) => Dispatcher.UIThread.Post(() =>
{ {
if (obj is ClientSupportedPixelFormatsMessage formats) if (obj is ClientSupportedPixelFormatsMessage formats)
{ {

2
src/Avalonia.Layout/LayoutManager.cs

@ -203,7 +203,7 @@ namespace Avalonia.Layout
{ {
if (!_queued && !_running) if (!_queued && !_running)
{ {
Dispatcher.UIThread.InvokeAsync(ExecuteLayoutPass, DispatcherPriority.Layout); Dispatcher.UIThread.Post(ExecuteLayoutPass, DispatcherPriority.Layout);
_queued = true; _queued = true;
} }
} }

7
src/Avalonia.Themes.Default/ListBox.xaml

@ -3,11 +3,16 @@
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/> <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
<Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/> <Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/>
<Setter Property="Padding" Value="4"/> <Setter Property="Padding" Value="4"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border BorderBrush="{TemplateBinding BorderBrush}" <Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"> BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer Name="PART_ScrollViewer" Background="{TemplateBinding Background}"> <ScrollViewer Name="PART_ScrollViewer"
Background="{TemplateBinding Background}"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
<ItemsPresenter Name="PART_ItemsPresenter" <ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}" Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}" ItemsPanel="{TemplateBinding ItemsPanel}"

5
src/Avalonia.Themes.Default/ScrollViewer.xaml

@ -6,12 +6,13 @@
<Grid ColumnDefinitions="*,Auto" RowDefinitions="*,Auto"> <Grid ColumnDefinitions="*,Auto" RowDefinitions="*,Auto">
<ScrollContentPresenter Name="PART_ContentPresenter" <ScrollContentPresenter Name="PART_ContentPresenter"
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
CanHorizontallyScroll="{TemplateBinding CanHorizontallyScroll}"
CanVerticallyScroll="{TemplateBinding CanVerticallyScroll}"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
Extent="{TemplateBinding Path=Extent, Mode=TwoWay}" Extent="{TemplateBinding Path=Extent, Mode=TwoWay}"
Margin="{TemplateBinding Padding}" Margin="{TemplateBinding Padding}"
Offset="{TemplateBinding Path=Offset, Mode=TwoWay}" Offset="{TemplateBinding Path=Offset, Mode=TwoWay}"
Viewport="{TemplateBinding Path=Viewport, Mode=TwoWay}" Viewport="{TemplateBinding Path=Viewport, Mode=TwoWay}"/>
CanScrollHorizontally="{TemplateBinding CanScrollHorizontally}"/>
<ScrollBar Name="horizontalScrollBar" <ScrollBar Name="horizontalScrollBar"
Orientation="Horizontal" Orientation="Horizontal"
Maximum="{TemplateBinding HorizontalScrollBarMaximum}" Maximum="{TemplateBinding HorizontalScrollBarMaximum}"

3
src/Avalonia.Themes.Default/TextBox.xaml

@ -36,8 +36,7 @@
<Path Data="M14,7 A7,7 0 0,0 0,7 M0,7 A7,7 0 1,0 14,7 M7,3l0,5 M7,9l0,2" Stroke="{DynamicResource ErrorBrush}" StrokeThickness="2"/> <Path Data="M14,7 A7,7 0 0,0 0,7 M0,7 A7,7 0 1,0 14,7 M7,3l0,5 M7,9l0,2" Stroke="{DynamicResource ErrorBrush}" StrokeThickness="2"/>
</Canvas> </Canvas>
<ScrollViewer CanScrollHorizontally="{TemplateBinding CanScrollHorizontally}" <ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"> VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
<Panel> <Panel>

2
src/Avalonia.Themes.Default/TreeView.xaml

@ -7,7 +7,7 @@
<ControlTemplate> <ControlTemplate>
<Border BorderBrush="{TemplateBinding BorderBrush}" <Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"> BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer CanScrollHorizontally="True" Background="{TemplateBinding Background}"> <ScrollViewer Background="{TemplateBinding Background}">
<ItemsPresenter Name="PART_ItemsPresenter" <ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}" Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}" ItemsPanel="{TemplateBinding ItemsPanel}"

2
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -415,7 +415,7 @@ namespace Avalonia.Rendering
if (!_updateQueued && (_dirty == null || _dirty.Count > 0)) if (!_updateQueued && (_dirty == null || _dirty.Count > 0))
{ {
_updateQueued = true; _updateQueued = true;
_dispatcher.InvokeAsync(UpdateScene, DispatcherPriority.Render); _dispatcher.Post(UpdateScene, DispatcherPriority.Render);
} }
Scene scene = null; Scene scene = null;

9
src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs

@ -169,7 +169,7 @@ namespace Avalonia.Rendering
{ {
foreach (var e in visual.GetSelfAndVisualDescendants()) foreach (var e in visual.GetSelfAndVisualDescendants())
{ {
BoundsTracker.SetTransformedBounds((Visual)visual, null); visual.TransformedBounds = null;
} }
} }
@ -197,7 +197,7 @@ namespace Avalonia.Rendering
if (filter?.Invoke(visual) != false) if (filter?.Invoke(visual) != false)
{ {
bool containsPoint = BoundsTracker.GetTransformedBounds((Visual)visual)?.Contains(p) == true; bool containsPoint = visual.TransformedBounds?.Contains(p) == true;
if ((containsPoint || !visual.ClipToBounds) && visual.VisualChildren.Count > 0) if ((containsPoint || !visual.ClipToBounds) && visual.VisualChildren.Count > 0)
{ {
@ -257,10 +257,7 @@ namespace Avalonia.Rendering
new TransformedBounds(bounds, new Rect(), context.CurrentContainerTransform); new TransformedBounds(bounds, new Rect(), context.CurrentContainerTransform);
#pragma warning restore 0618 #pragma warning restore 0618
if (visual is Visual) visual.TransformedBounds = transformed;
{
BoundsTracker.SetTransformedBounds((Visual)visual, transformed);
}
foreach (var child in visual.VisualChildren.OrderBy(x => x, ZIndexComparer.Instance)) foreach (var child in visual.VisualChildren.OrderBy(x => x, ZIndexComparer.Instance))
{ {

12
src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs

@ -209,11 +209,8 @@ namespace Avalonia.Rendering.SceneGraph
} }
catch { } catch { }
if (visual is Visual) var transformed = new TransformedBounds(new Rect(visual.Bounds.Size), clip, node.Transform);
{ visual.TransformedBounds = transformed;
var transformed = new TransformedBounds(new Rect(visual.Bounds.Size), clip, node.Transform);
BoundsTracker.SetTransformedBounds((Visual)visual, transformed);
}
if (forceRecurse) if (forceRecurse)
{ {
@ -279,10 +276,7 @@ namespace Avalonia.Rendering.SceneGraph
scene.Layers[node.LayerRoot].Dirty.Add(node.Bounds); scene.Layers[node.LayerRoot].Dirty.Add(node.Bounds);
if (node.Visual is Visual v) node.Visual.TransformedBounds = null;
{
BoundsTracker.SetTransformedBounds(v, null);
}
foreach (VisualNode child in node.Children) foreach (VisualNode child in node.Children)
{ {

17
src/Avalonia.Visuals/Visual.cs

@ -32,6 +32,11 @@ namespace Avalonia
public static readonly DirectProperty<Visual, Rect> BoundsProperty = public static readonly DirectProperty<Visual, Rect> BoundsProperty =
AvaloniaProperty.RegisterDirect<Visual, Rect>(nameof(Bounds), o => o.Bounds); AvaloniaProperty.RegisterDirect<Visual, Rect>(nameof(Bounds), o => o.Bounds);
public static readonly DirectProperty<Visual, TransformedBounds?> TransformedBoundsProperty =
AvaloniaProperty.RegisterDirect<Visual, TransformedBounds?>(
nameof(TransformedBounds),
o => o.TransformedBounds);
/// <summary> /// <summary>
/// Defines the <see cref="ClipToBounds"/> property. /// Defines the <see cref="ClipToBounds"/> property.
/// </summary> /// </summary>
@ -87,6 +92,7 @@ namespace Avalonia
AvaloniaProperty.Register<Visual, int>(nameof(ZIndex)); AvaloniaProperty.Register<Visual, int>(nameof(ZIndex));
private Rect _bounds; private Rect _bounds;
private TransformedBounds? _transformedBounds;
private IRenderRoot _visualRoot; private IRenderRoot _visualRoot;
private IVisual _visualParent; private IVisual _visualParent;
@ -135,6 +141,11 @@ namespace Avalonia
protected set { SetAndRaise(BoundsProperty, ref _bounds, value); } protected set { SetAndRaise(BoundsProperty, ref _bounds, value); }
} }
/// <summary>
/// Gets the bounds of the control relative to the window, accounting for rendering transforms.
/// </summary>
public TransformedBounds? TransformedBounds => _transformedBounds;
/// <summary> /// <summary>
/// Gets a value indicating whether the control should be clipped to its bounds. /// Gets a value indicating whether the control should be clipped to its bounds.
/// </summary> /// </summary>
@ -253,6 +264,12 @@ namespace Avalonia
/// Gets the root of the visual tree, if the control is attached to a visual tree. /// Gets the root of the visual tree, if the control is attached to a visual tree.
/// </summary> /// </summary>
IRenderRoot IVisual.VisualRoot => VisualRoot; IRenderRoot IVisual.VisualRoot => VisualRoot;
TransformedBounds? IVisual.TransformedBounds
{
get { return _transformedBounds; }
set { SetAndRaise(TransformedBoundsProperty, ref _transformedBounds, value); }
}
/// <summary> /// <summary>
/// Invalidates the visual and queues a repaint. /// Invalidates the visual and queues a repaint.

51
src/Avalonia.Visuals/VisualTree/BoundsTracker.cs

@ -1,51 +0,0 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.VisualTree
{
/// <summary>
/// Tracks the bounds of a control.
/// </summary>
/// <remarks>
/// This class is used to track a controls's bounds for hit testing.
/// TODO: This shouldn't be implemented as an attached property: it would be more performant
/// to just store bounds in some sort of central repository.
/// </remarks>
public class BoundsTracker
{
/// <summary>
/// Defines the TransformedBounds attached property.
/// </summary>
private static AttachedProperty<TransformedBounds?> TransformedBoundsProperty =
AvaloniaProperty.RegisterAttached<BoundsTracker, Visual, TransformedBounds?>("TransformedBounds");
/// <summary>
/// Starts tracking the specified visual.
/// </summary>
/// <param name="visual">The visual.</param>
/// <returns>An observable that returns the tracked bounds.</returns>
public IObservable<TransformedBounds?> Track(Visual visual)
{
return visual.GetObservable(TransformedBoundsProperty);
}
/// <summary>
/// Sets the transformed bounds of the visual.
/// </summary>
/// <param name="visual">The visual.</param>
/// <param name="value">The transformed bounds.</param>
internal static void SetTransformedBounds(Visual visual, TransformedBounds? value)
{
visual.SetValue(TransformedBoundsProperty, value);
}
/// <summary>
/// Gets the transformed bounds of the visual.
/// </summary>
/// <param name="visual">The visual.</param>
/// <returns>The transformed bounds or null if the visual is not visible.</returns>
public static TransformedBounds? GetTransformedBounds(Visual visual) => visual.GetValue(TransformedBoundsProperty);
}
}

5
src/Avalonia.Visuals/VisualTree/IVisual.cs

@ -36,6 +36,11 @@ namespace Avalonia.VisualTree
/// </summary> /// </summary>
Rect Bounds { get; } Rect Bounds { get; }
/// <summary>
/// Gets the bounds of the control relative to the window, accounting for rendering transforms.
/// </summary>
TransformedBounds? TransformedBounds { get; set; }
/// <summary> /// <summary>
/// Gets a value indicating whether the control should be clipped to its bounds. /// Gets a value indicating whether the control should be clipped to its bounds.
/// </summary> /// </summary>

2
src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs

@ -351,7 +351,7 @@ namespace Avalonia.Gtk3
void OnInput(RawInputEventArgs args) void OnInput(RawInputEventArgs args)
{ {
Dispatcher.UIThread.InvokeAsync(() => Input?.Invoke(args), DispatcherPriority.Input); Dispatcher.UIThread.Post(() => Input?.Invoke(args), DispatcherPriority.Input);
} }
public Point PointToClient(Point point) public Point PointToClient(Point point)

2
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@ -41,7 +41,7 @@ namespace Avalonia.LinuxFramebuffer
if(_renderQueued) if(_renderQueued)
return; return;
_renderQueued = true; _renderQueued = true;
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.Post(() =>
{ {
Paint?.Invoke(new Rect(default(Point), ClientSize)); Paint?.Invoke(new Rect(default(Point), ClientSize));
_renderQueued = false; _renderQueued = false;

2
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@ -62,7 +62,7 @@ public static class LinuxFramebufferPlatformExtensions
public TokenClosable(CancellationToken token) public TokenClosable(CancellationToken token)
{ {
token.Register(() => Dispatcher.UIThread.InvokeAsync(() => Closed?.Invoke(this, new EventArgs()))); token.Register(() => Dispatcher.UIThread.Post(() => Closed?.Invoke(this, new EventArgs())));
} }
} }

2
src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs

@ -104,7 +104,7 @@ namespace Avalonia.Markup.Data.Plugins
protected override void SubscribeCore(IObserver<object> observer) protected override void SubscribeCore(IObserver<object> observer)
{ {
_subscription = Instance.GetWeakObservable(_property).Subscribe(observer); _subscription = Instance?.GetWeakObservable(_property).Subscribe(observer);
} }
} }
} }

2
src/OSX/Avalonia.MonoMac/TopLevelImpl.cs

@ -107,7 +107,7 @@ namespace Avalonia.MonoMac
if (_nonUiRedrawQueued) if (_nonUiRedrawQueued)
return; return;
_nonUiRedrawQueued = true; _nonUiRedrawQueued = true;
Dispatcher.UIThread.InvokeAsync( Dispatcher.UIThread.Post(
() => () =>
{ {
lock (SyncRoot) lock (SyncRoot)

2
tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs

@ -281,6 +281,8 @@ namespace Avalonia.Controls.UnitTests.Presenters
var scroller = new TestScroller var scroller = new TestScroller
{ {
CanHorizontallyScroll = false,
CanVerticallyScroll = true,
Content = result = new TestItemsPresenter(useContainers) Content = result = new TestItemsPresenter(useContainers)
{ {
Items = items, Items = items,

2
tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs

@ -998,6 +998,8 @@ namespace Avalonia.Controls.UnitTests.Presenters
var scroller = new TestScroller var scroller = new TestScroller
{ {
CanHorizontallyScroll = true,
CanVerticallyScroll = true,
Content = result = new TestItemsPresenter(useContainers) Content = result = new TestItemsPresenter(useContainers)
{ {
Items = items, Items = items,

11
tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Layout; using Avalonia.Layout;
using Xunit; using Xunit;
@ -118,6 +119,8 @@ namespace Avalonia.Controls.UnitTests.Presenters
TestControl content; TestControl content;
var target = new ScrollContentPresenter var target = new ScrollContentPresenter
{ {
CanHorizontallyScroll = true,
CanVerticallyScroll = true,
Content = content = new TestControl(), Content = content = new TestControl(),
}; };
@ -134,6 +137,8 @@ namespace Avalonia.Controls.UnitTests.Presenters
Border content; Border content;
var target = new ScrollContentPresenter var target = new ScrollContentPresenter
{ {
CanHorizontallyScroll = true,
CanVerticallyScroll = true,
Content = content = new Border Content = content = new Border
{ {
Width = 150, Width = 150,
@ -155,8 +160,8 @@ namespace Avalonia.Controls.UnitTests.Presenters
var child = new TestControl(); var child = new TestControl();
var target = new ScrollContentPresenter var target = new ScrollContentPresenter
{ {
CanVerticallyScroll = true,
Content = child, Content = child,
[ScrollContentPresenter.CanScrollHorizontallyProperty] = false,
}; };
target.UpdateChild(); target.UpdateChild();
@ -171,6 +176,8 @@ namespace Avalonia.Controls.UnitTests.Presenters
var child = new TestControl(); var child = new TestControl();
var target = new ScrollContentPresenter var target = new ScrollContentPresenter
{ {
CanHorizontallyScroll = true,
CanVerticallyScroll = true,
Content = child, Content = child,
}; };
@ -246,6 +253,8 @@ namespace Avalonia.Controls.UnitTests.Presenters
Border border; Border border;
var target = new ScrollContentPresenter var target = new ScrollContentPresenter
{ {
CanHorizontallyScroll = true,
CanVerticallyScroll = true,
Width = 100, Width = 100,
Height = 100, Height = 100,
Content = new Decorator Content = new Decorator

46
tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests_ILogicalScrollable.cs

@ -13,7 +13,7 @@ namespace Avalonia.Controls.UnitTests
public class ScrollContentPresenterTests_ILogicalScrollable public class ScrollContentPresenterTests_ILogicalScrollable
{ {
[Fact] [Fact]
public void Measure_Should_Pass_Unchanged_Bounds_To_IScrollable() public void Measure_Should_Pass_Unchanged_Bounds_To_ILogicalScrollable()
{ {
var scrollable = new TestScrollable(); var scrollable = new TestScrollable();
var target = new ScrollContentPresenter var target = new ScrollContentPresenter
@ -28,7 +28,7 @@ namespace Avalonia.Controls.UnitTests
} }
[Fact] [Fact]
public void Arrange_Should_Not_Offset_IScrollable_Bounds() public void Arrange_Should_Not_Offset_ILogicalScrollable_Bounds()
{ {
var scrollable = new TestScrollable var scrollable = new TestScrollable
{ {
@ -50,7 +50,7 @@ namespace Avalonia.Controls.UnitTests
} }
[Fact] [Fact]
public void Arrange_Should_Offset_IScrollable_Bounds_When_Logical_Scroll_Disabled() public void Arrange_Should_Offset_ILogicalScrollable_Bounds_When_Logical_Scroll_Disabled()
{ {
var scrollable = new TestScrollable var scrollable = new TestScrollable
{ {
@ -59,6 +59,8 @@ namespace Avalonia.Controls.UnitTests
var target = new ScrollContentPresenter var target = new ScrollContentPresenter
{ {
CanHorizontallyScroll = true,
CanVerticallyScroll = true,
Content = scrollable, Content = scrollable,
Offset = new Vector(25, 25), Offset = new Vector(25, 25),
}; };
@ -71,7 +73,7 @@ namespace Avalonia.Controls.UnitTests
} }
[Fact] [Fact]
public void Arrange_Should_Not_Set_Viewport_And_Extent_With_IScrollable() public void Arrange_Should_Not_Set_Viewport_And_Extent_With_ILogicalScrollable()
{ {
var target = new ScrollContentPresenter var target = new ScrollContentPresenter
{ {
@ -122,7 +124,7 @@ namespace Avalonia.Controls.UnitTests
} }
[Fact] [Fact]
public void Extent_Offset_And_Viewport_Should_Be_Read_From_IScrollable() public void Extent_Offset_And_Viewport_Should_Be_Read_From_ILogicalScrollable()
{ {
var scrollable = new TestScrollable var scrollable = new TestScrollable
{ {
@ -152,7 +154,7 @@ namespace Avalonia.Controls.UnitTests
} }
[Fact] [Fact]
public void Offset_Should_Be_Written_To_IScrollable() public void Offset_Should_Be_Written_To_ILogicalScrollable()
{ {
var scrollable = new TestScrollable var scrollable = new TestScrollable
{ {
@ -172,7 +174,7 @@ namespace Avalonia.Controls.UnitTests
} }
[Fact] [Fact]
public void Offset_Should_Not_Be_Written_To_IScrollable_After_Removal() public void Offset_Should_Not_Be_Written_To_ILogicalScrollable_After_Removal()
{ {
var scrollable = new TestScrollable var scrollable = new TestScrollable
{ {
@ -203,6 +205,8 @@ namespace Avalonia.Controls.UnitTests
var target = new ScrollContentPresenter var target = new ScrollContentPresenter
{ {
CanHorizontallyScroll = true,
CanVerticallyScroll = true,
Content = scrollable, Content = scrollable,
}; };
@ -253,6 +257,8 @@ namespace Avalonia.Controls.UnitTests
var target = new ScrollContentPresenter var target = new ScrollContentPresenter
{ {
CanHorizontallyScroll = true,
CanVerticallyScroll = true,
Content = logicalScrollable, Content = logicalScrollable,
}; };
@ -286,12 +292,38 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Rect(0, 0, 100, 100), logicalScrollable.Bounds); Assert.Equal(new Rect(0, 0, 100, 100), logicalScrollable.Bounds);
} }
[Fact]
public void Should_Set_ILogicalScrolable_CanHorizontallyScroll()
{
var logicalScrollable = new TestScrollable();
var target = new ScrollContentPresenter { Content = logicalScrollable };
target.UpdateChild();
Assert.False(logicalScrollable.CanHorizontallyScroll);
target.CanHorizontallyScroll = true;
Assert.True(logicalScrollable.CanHorizontallyScroll);
}
[Fact]
public void Should_Set_ILogicalScrolable_CanVerticallyScroll()
{
var logicalScrollable = new TestScrollable();
var target = new ScrollContentPresenter { Content = logicalScrollable };
target.UpdateChild();
Assert.False(logicalScrollable.CanVerticallyScroll);
target.CanVerticallyScroll = true;
Assert.True(logicalScrollable.CanVerticallyScroll);
}
private class TestScrollable : Control, ILogicalScrollable private class TestScrollable : Control, ILogicalScrollable
{ {
private Size _extent; private Size _extent;
private Vector _offset; private Vector _offset;
private Size _viewport; private Size _viewport;
public bool CanHorizontallyScroll { get; set; }
public bool CanVerticallyScroll { get; set; }
public bool IsLogicalScrollEnabled { get; set; } = true; public bool IsLogicalScrollEnabled { get; set; } = true;
public Size AvailableSize { get; private set; } public Size AvailableSize { get; private set; }
public Action InvalidateScroll { get; set; } public Action InvalidateScroll { get; set; }

30
tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs

@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved. // Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
@ -25,6 +27,32 @@ namespace Avalonia.Controls.UnitTests
Assert.IsType<TextBlock>(target.Presenter.Child); Assert.IsType<TextBlock>(target.Presenter.Child);
} }
[Fact]
public void CanHorizontallyScroll_Should_Track_HorizontalScrollBarVisibility()
{
var target = new ScrollViewer();
var values = new List<bool>();
target.GetObservable(ScrollViewer.CanHorizontallyScrollProperty).Subscribe(x => values.Add(x));
target.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;
target.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
Assert.Equal(new[] { true, false, true }, values);
}
[Fact]
public void CanVerticallyScroll_Should_Track_VerticalScrollBarVisibility()
{
var target = new ScrollViewer();
var values = new List<bool>();
target.GetObservable(ScrollViewer.CanVerticallyScrollProperty).Subscribe(x => values.Add(x));
target.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
target.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
Assert.Equal(new[] { true, false, true }, values);
}
[Fact] [Fact]
public void Offset_Should_Be_Coerced_To_Viewport() public void Offset_Should_Be_Coerced_To_Viewport()
{ {
@ -59,7 +87,7 @@ namespace Avalonia.Controls.UnitTests
[~~ScrollContentPresenter.ExtentProperty] = control[~~ScrollViewer.ExtentProperty], [~~ScrollContentPresenter.ExtentProperty] = control[~~ScrollViewer.ExtentProperty],
[~~ScrollContentPresenter.OffsetProperty] = control[~~ScrollViewer.OffsetProperty], [~~ScrollContentPresenter.OffsetProperty] = control[~~ScrollViewer.OffsetProperty],
[~~ScrollContentPresenter.ViewportProperty] = control[~~ScrollViewer.ViewportProperty], [~~ScrollContentPresenter.ViewportProperty] = control[~~ScrollViewer.ViewportProperty],
[~ScrollContentPresenter.CanScrollHorizontallyProperty] = control[~ScrollViewer.CanScrollHorizontallyProperty], [~ScrollContentPresenter.CanHorizontallyScrollProperty] = control[~ScrollViewer.CanHorizontallyScrollProperty],
}, },
new ScrollBar new ScrollBar
{ {

2
tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs

@ -85,7 +85,7 @@ namespace Avalonia.Layout.UnitTests
{ {
Width = 200, Width = 200,
Height = 200, Height = 200,
CanScrollHorizontally = true, HorizontalScrollBarVisibility = ScrollBarVisibility.Auto,
HorizontalAlignment = HorizontalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
Content = textBlock = new TextBlock Content = textBlock = new TextBlock

12
tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Observable.cs

@ -103,20 +103,22 @@ namespace Avalonia.Markup.UnitTests.Data
{ {
using (var sync = UnitTestSynchronizationContext.Begin()) using (var sync = UnitTestSynchronizationContext.Begin())
{ {
var data = new Class1(); var data1 = new Class1();
var target = new ExpressionObserver(data, "Next^.Foo", true); var data2 = new Class2("foo");
var target = new ExpressionObserver(data1, "Next^.Foo", true);
var result = new List<object>(); var result = new List<object>();
var sub = target.Subscribe(x => result.Add(x)); var sub = target.Subscribe(x => result.Add(x));
data.Next.OnNext(new Class2("foo")); data1.Next.OnNext(data2);
sync.ExecutePostedCallbacks(); sync.ExecutePostedCallbacks();
Assert.Equal(new[] { new BindingNotification("foo") }, result); Assert.Equal(new[] { new BindingNotification("foo") }, result);
sub.Dispose(); sub.Dispose();
Assert.Equal(0, data.PropertyChangedSubscriptionCount); Assert.Equal(0, data1.PropertyChangedSubscriptionCount);
GC.KeepAlive(data); GC.KeepAlive(data1);
GC.KeepAlive(data2);
} }
} }

4
tests/Avalonia.UnitTests/ImmediateDispatcher.cs

@ -14,12 +14,12 @@ namespace Avalonia.UnitTests
return true; return true;
} }
public void InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) public void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{ {
action(); action();
} }
public Task InvokeTaskAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{ {
action(); action();
return Task.FromResult<object>(null); return Task.FromResult<object>(null);

4
tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs

@ -24,13 +24,13 @@ namespace Avalonia.Visuals.UnitTests.Rendering
var root = new TestRoot(); var root = new TestRoot();
var dispatcher = new Mock<IDispatcher>(); var dispatcher = new Mock<IDispatcher>();
dispatcher.Setup(x => x.InvokeAsync(It.IsAny<Action>(), DispatcherPriority.Render)) dispatcher.Setup(x => x.Post(It.IsAny<Action>(), DispatcherPriority.Render))
.Callback<Action, DispatcherPriority>((a, p) => a()); .Callback<Action, DispatcherPriority>((a, p) => a());
CreateTargetAndRunFrame(root, dispatcher: dispatcher.Object); CreateTargetAndRunFrame(root, dispatcher: dispatcher.Object);
dispatcher.Verify(x => dispatcher.Verify(x =>
x.InvokeAsync( x.Post(
It.Is<Action>(a => a.Method.Name == "UpdateScene"), It.Is<Action>(a => a.Method.Name == "UpdateScene"),
DispatcherPriority.Render)); DispatcherPriority.Render));
} }

2
tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs

@ -372,6 +372,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering
Margin = new Thickness(0, 100, 0, 0), Margin = new Thickness(0, 100, 0, 0),
Child = scroll = new ScrollContentPresenter() Child = scroll = new ScrollContentPresenter()
{ {
CanHorizontallyScroll = true,
CanVerticallyScroll = true,
Content = new StackPanel() Content = new StackPanel()
{ {
Children = Children =

2
tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs

@ -357,6 +357,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering
Margin = new Thickness(0, 100, 0, 0), Margin = new Thickness(0, 100, 0, 0),
Child = scroll = new ScrollContentPresenter() Child = scroll = new ScrollContentPresenter()
{ {
CanHorizontallyScroll = true,
CanVerticallyScroll = true,
Content = new StackPanel() Content = new StackPanel()
{ {
Children = Children =

5
tests/Avalonia.Visuals.UnitTests/VisualTree/BoundsTrackerTests.cs → tests/Avalonia.Visuals.UnitTests/VisualTree/TransformedBoundsTests.cs

@ -17,14 +17,13 @@ using Avalonia.Platform;
namespace Avalonia.Visuals.UnitTests.VisualTree namespace Avalonia.Visuals.UnitTests.VisualTree
{ {
public class BoundsTrackerTests public class TransformedBoundsTests
{ {
[Fact] [Fact]
public void Should_Track_Bounds() public void Should_Track_Bounds()
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var target = new BoundsTracker();
var control = default(Rectangle); var control = default(Rectangle);
var tree = new Decorator var tree = new Decorator
{ {
@ -46,7 +45,7 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
tree.Arrange(new Rect(0, 0, 100, 100)); tree.Arrange(new Rect(0, 0, 100, 100));
ImmediateRenderer.Render(tree, context); ImmediateRenderer.Render(tree, context);
var track = target.Track(control); var track = control.GetObservable(Visual.TransformedBoundsProperty);
var results = new List<TransformedBounds?>(); var results = new List<TransformedBounds?>();
track.Subscribe(results.Add); track.Subscribe(results.Add);
Loading…
Cancel
Save