Browse Source

Merge branch 'master' into design-properties

pull/1952/head
danwalmsley 8 years ago
committed by GitHub
parent
commit
56321fe4c7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      build.cake
  2. 2
      build/ReactiveUI.props
  3. 1
      samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs
  4. 4
      src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
  5. 4
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  6. 4
      src/Avalonia.ReactiveUI/AppBuilderExtensions.cs
  7. 38
      src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
  8. 34
      src/Avalonia.Visuals/Rendering/RenderLoop.cs
  9. 20
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  10. 4
      src/Windows/Avalonia.Win32/ScreenImpl.cs
  11. 2
      src/Windows/Avalonia.Win32/Win32Platform.cs
  12. 4
      src/Windows/Avalonia.Win32/WindowImpl.cs
  13. 34
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  14. 26
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  15. 14
      tests/Avalonia.ReactiveUI.UnitTests/Avalonia.ReactiveUI.UnitTests.csproj
  16. 89
      tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs
  17. 15
      tests/Avalonia.Visuals.UnitTests/Rendering/RenderLoopTests.cs

1
build.cake

@ -170,6 +170,7 @@ Task("Run-Unit-Tests-Impl")
RunCoreTest("./tests/Avalonia.Styling.UnitTests", data.Parameters, false); RunCoreTest("./tests/Avalonia.Styling.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.Visuals.UnitTests", data.Parameters, false); RunCoreTest("./tests/Avalonia.Visuals.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.Skia.UnitTests", data.Parameters, false); RunCoreTest("./tests/Avalonia.Skia.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.ReactiveUI.UnitTests", data.Parameters, false);
if (data.Parameters.IsRunningOnWindows) if (data.Parameters.IsRunningOnWindows)
{ {
RunCoreTest("./tests/Avalonia.Direct2D1.UnitTests", data.Parameters, false); RunCoreTest("./tests/Avalonia.Direct2D1.UnitTests", data.Parameters, false);

2
build/ReactiveUI.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="reactiveui" Version="8.7.1" /> <PackageReference Include="reactiveui" Version="9.0.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

1
samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs

@ -7,6 +7,7 @@ using System.Linq;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using ReactiveUI.Legacy;
using ReactiveUI; using ReactiveUI;
namespace VirtualizationDemo.ViewModels namespace VirtualizationDemo.ViewModels

4
src/Avalonia.Controls/Generators/ItemContainerGenerator.cs

@ -15,7 +15,7 @@ namespace Avalonia.Controls.Generators
/// </summary> /// </summary>
public class ItemContainerGenerator : IItemContainerGenerator public class ItemContainerGenerator : IItemContainerGenerator
{ {
private Dictionary<int, ItemContainerInfo> _containers = new Dictionary<int, ItemContainerInfo>(); private SortedDictionary<int, ItemContainerInfo> _containers = new SortedDictionary<int, ItemContainerInfo>();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ItemContainerGenerator"/> class. /// Initializes a new instance of the <see cref="ItemContainerGenerator"/> class.
@ -246,4 +246,4 @@ namespace Avalonia.Controls.Generators
Recycled?.Invoke(this, e); Recycled?.Invoke(this, e);
} }
} }
} }

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

@ -289,12 +289,12 @@ namespace Avalonia.Controls.Primitives
/// <inheritdoc/> /// <inheritdoc/>
public override void EndInit() public override void EndInit()
{ {
base.EndInit();
if (--_updateCount == 0) if (--_updateCount == 0)
{ {
UpdateFinished(); UpdateFinished();
} }
base.EndInit();
} }
/// <summary> /// <summary>

4
src/Avalonia.ReactiveUI/AppBuilderExtensions.cs

@ -4,6 +4,7 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Threading; using Avalonia.Threading;
using ReactiveUI; using ReactiveUI;
using Splat;
namespace Avalonia namespace Avalonia
{ {
@ -15,6 +16,9 @@ namespace Avalonia
return builder.AfterSetup(_ => return builder.AfterSetup(_ =>
{ {
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance; RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
Locator.CurrentMutable.Register(
() => new AvaloniaActivationForViewFetcher(),
typeof(IActivationForViewFetcher));
}); });
} }
} }

38
src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs

@ -0,0 +1,38 @@
// 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;
using System.Reflection;
using System.Reactive.Linq;
using Avalonia;
using Avalonia.VisualTree;
using ReactiveUI;
namespace Avalonia
{
public class AvaloniaActivationForViewFetcher : IActivationForViewFetcher
{
public int GetAffinityForView(Type view)
{
return typeof(IVisual).GetTypeInfo().IsAssignableFrom(view.GetTypeInfo()) ? 10 : 0;
}
public IObservable<bool> GetActivationForView(IActivatable view)
{
if (!(view is IVisual visual)) return Observable.Return(false);
var viewLoaded = Observable
.FromEventPattern<VisualTreeAttachmentEventArgs>(
x => visual.AttachedToVisualTree += x,
x => visual.DetachedFromVisualTree -= x)
.Select(args => true);
var viewUnloaded = Observable
.FromEventPattern<VisualTreeAttachmentEventArgs>(
x => visual.DetachedFromVisualTree += x,
x => visual.DetachedFromVisualTree -= x)
.Select(args => false);
return viewLoaded
.Merge(viewUnloaded)
.DistinctUntilChanged();
}
}
}

34
src/Avalonia.Visuals/Rendering/RenderLoop.cs

@ -19,7 +19,8 @@ namespace Avalonia.Rendering
private readonly IDispatcher _dispatcher; private readonly IDispatcher _dispatcher;
private List<IRenderLoopTask> _items = new List<IRenderLoopTask>(); private List<IRenderLoopTask> _items = new List<IRenderLoopTask>();
private IRenderTimer _timer; private IRenderTimer _timer;
private int inTick; private int _inTick;
private int _inUpdate;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RenderLoop"/> class. /// Initializes a new instance of the <see cref="RenderLoop"/> class.
@ -84,21 +85,36 @@ namespace Avalonia.Rendering
} }
} }
private async void TimerTick(TimeSpan time) private void TimerTick(TimeSpan time)
{ {
if (Interlocked.CompareExchange(ref inTick, 1, 0) == 0) if (Interlocked.CompareExchange(ref _inTick, 1, 0) == 0)
{ {
try try
{ {
if (_items.Any(item => item.NeedsUpdate)) if (_items.Any(item => item.NeedsUpdate) &&
Interlocked.CompareExchange(ref _inUpdate, 1, 0) == 0)
{ {
await _dispatcher.InvokeAsync(() => _dispatcher.Post(() =>
{ {
foreach (var i in _items) for (var i = 0; i < _items.Count; ++i)
{ {
i.Update(time); var item = _items[i];
if (item.NeedsUpdate)
{
try
{
item.Update(time);
}
catch (Exception ex)
{
Logger.Error(LogArea.Visual, this, "Exception in render update: {Error}", ex);
}
}
} }
}, DispatcherPriority.Render).ConfigureAwait(false);
Interlocked.Exchange(ref _inUpdate, 0);
}, DispatcherPriority.Render);
} }
foreach (var i in _items) foreach (var i in _items)
@ -112,7 +128,7 @@ namespace Avalonia.Rendering
} }
finally finally
{ {
Interlocked.Exchange(ref inTick, 0); Interlocked.Exchange(ref _inTick, 0);
} }
} }
} }

20
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -976,6 +976,9 @@ namespace Avalonia.Win32.Interop
[DllImport("shcore.dll")] [DllImport("shcore.dll")]
public static extern void GetScaleFactorForMonitor(IntPtr hMon, out uint pScale); public static extern void GetScaleFactorForMonitor(IntPtr hMon, out uint pScale);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool SetProcessDPIAware();
[DllImport("user32.dll")] [DllImport("user32.dll")]
public static extern IntPtr MonitorFromPoint(POINT pt, MONITOR dwFlags); public static extern IntPtr MonitorFromPoint(POINT pt, MONITOR dwFlags);
@ -987,7 +990,7 @@ namespace Avalonia.Win32.Interop
[DllImport("user32", EntryPoint = "GetMonitorInfoW", ExactSpelling = true, CharSet = CharSet.Unicode)] [DllImport("user32", EntryPoint = "GetMonitorInfoW", ExactSpelling = true, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetMonitorInfo([In] IntPtr hMonitor, [Out] MONITORINFO lpmi); public static extern bool GetMonitorInfo([In] IntPtr hMonitor, ref MONITORINFO lpmi);
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "PostMessageW")] [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "PostMessageW")]
@ -1055,12 +1058,17 @@ namespace Avalonia.Win32.Interop
} }
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal class MONITORINFO internal struct MONITORINFO
{ {
public int cbSize = Marshal.SizeOf<MONITORINFO>(); public int cbSize;
public RECT rcMonitor = new RECT(); public RECT rcMonitor;
public RECT rcWork = new RECT(); public RECT rcWork;
public int dwFlags = 0; public int dwFlags;
public static MONITORINFO Create()
{
return new MONITORINFO() { cbSize = Marshal.SizeOf<MONITORINFO>() };
}
public enum MonitorOptions : uint public enum MonitorOptions : uint
{ {

4
src/Windows/Avalonia.Win32/ScreenImpl.cs

@ -26,8 +26,8 @@ namespace Avalonia.Win32
EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero,
(IntPtr monitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr data) => (IntPtr monitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr data) =>
{ {
MONITORINFO monitorInfo = new MONITORINFO(); MONITORINFO monitorInfo = MONITORINFO.Create();
if (GetMonitorInfo(monitor, monitorInfo)) if (GetMonitorInfo(monitor,ref monitorInfo))
{ {
RECT bounds = monitorInfo.rcMonitor; RECT bounds = monitorInfo.rcMonitor;
RECT workingArea = monitorInfo.rcWork; RECT workingArea = monitorInfo.rcWork;

2
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -77,6 +77,8 @@ namespace Avalonia.Win32
public static void Initialize(bool deferredRendering = true) public static void Initialize(bool deferredRendering = true)
{ {
UnmanagedMethods.SetProcessDPIAware();
AvaloniaLocator.CurrentMutable AvaloniaLocator.CurrentMutable
.Bind<IClipboard>().ToSingleton<ClipboardImpl>() .Bind<IClipboard>().ToSingleton<ClipboardImpl>()
.Bind<IStandardCursorFactory>().ToConstant(CursorFactory.Instance) .Bind<IStandardCursorFactory>().ToConstant(CursorFactory.Instance)

4
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -837,9 +837,9 @@ namespace Avalonia.Win32
if (monitor != IntPtr.Zero) if (monitor != IntPtr.Zero)
{ {
MONITORINFO monitorInfo = new MONITORINFO(); MONITORINFO monitorInfo = MONITORINFO.Create();
if (GetMonitorInfo(monitor, monitorInfo)) if (GetMonitorInfo(monitor,ref monitorInfo))
{ {
RECT rcMonitorArea = monitorInfo.rcMonitor; RECT rcMonitorArea = monitorInfo.rcMonitor;

34
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -1,16 +1,18 @@
// 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.ObjectModel;
using System.Linq; using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Input; using Avalonia.Data;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Xunit; using Xunit;
using Avalonia.Collections;
namespace Avalonia.Controls.UnitTests namespace Avalonia.Controls.UnitTests
{ {
@ -170,9 +172,33 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Size(100, 10), target.Scroll.Viewport); Assert.Equal(new Size(100, 10), target.Scroll.Viewport);
} }
[Fact]
public void Containers_Correct_After_Clear_Add_Remove()
{
// Issue #1936
var items = new AvaloniaList<string>(Enumerable.Range(0, 11).Select(x => $"Item {x}"));
var target = new ListBox
{
Template = ListBoxTemplate(),
Items = items,
ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Width = 20, Height = 10 }),
SelectedIndex = 0,
};
Prepare(target);
items.Clear();
items.AddRange(Enumerable.Range(0, 11).Select(x => $"Item {x}"));
items.Remove("Item 2");
Assert.Equal(
items,
target.Presenter.Panel.Children.Cast<ListBoxItem>().Select(x => (string)x.Content));
}
private FuncControlTemplate ListBoxTemplate() private FuncControlTemplate ListBoxTemplate()
{ {
return new FuncControlTemplate<ListBox>(parent => return new FuncControlTemplate<ListBox>(parent =>
new ScrollViewer new ScrollViewer
{ {
Name = "PART_ScrollViewer", Name = "PART_ScrollViewer",
@ -189,7 +215,7 @@ namespace Avalonia.Controls.UnitTests
private FuncControlTemplate ListBoxItemTemplate() private FuncControlTemplate ListBoxItemTemplate()
{ {
return new FuncControlTemplate<ListBoxItem>(parent => return new FuncControlTemplate<ListBoxItem>(parent =>
new ContentPresenter new ContentPresenter
{ {
Name = "PART_ContentPresenter", Name = "PART_ContentPresenter",

26
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@ -707,6 +707,26 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.True(target.SelectedIndex == 1); Assert.True(target.SelectedIndex == 1);
} }
[Fact]
public void Binding_With_DelayedBinding_And_Initialization_Where_DataContext_Is_Root_Works()
{
// Test for #1932.
var root = new RootWithItems();
root.BeginInit();
root.DataContext = root;
var target = new ListBox();
target.BeginInit();
root.Child = target;
DelayedBinding.Add(target, ItemsControl.ItemsProperty, new Binding(nameof(RootWithItems.Items)));
DelayedBinding.Add(target, ListBox.SelectedItemProperty, new Binding(nameof(RootWithItems.Selected)));
target.EndInit();
root.EndInit();
Assert.Equal("b", target.SelectedItem);
}
private FuncControlTemplate Template() private FuncControlTemplate Template()
{ {
@ -745,5 +765,11 @@ namespace Avalonia.Controls.UnitTests.Primitives
public IList<Item> Items { get; set; } public IList<Item> Items { get; set; }
public Item SelectedItem { get; set; } public Item SelectedItem { get; set; }
} }
private class RootWithItems : TestRoot
{
public List<string> Items { get; set; } = new List<string>() { "a", "b", "c", "d", "e" };
public string Selected { get; set; } = "b";
}
} }
} }

14
tests/Avalonia.ReactiveUI.UnitTests/Avalonia.ReactiveUI.UnitTests.csproj

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />
<Import Project="..\..\build\Moq.props" />
<Import Project="..\..\build\XUnit.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\Microsoft.Reactive.Testing.props" />
<ItemGroup>
<ProjectReference Include="..\Avalonia.UnitTests\Avalonia.UnitTests.csproj" />
<ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj"/>
</ItemGroup>
</Project>

89
tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs

@ -0,0 +1,89 @@
using System;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using Avalonia.Controls;
using Avalonia.Rendering;
using Avalonia.Platform;
using Avalonia.UnitTests;
using Avalonia;
using ReactiveUI;
using DynamicData;
using Xunit;
using Splat;
namespace Avalonia
{
public class AvaloniaActivationForViewFetcherTest
{
public class TestUserControl : UserControl, IActivatable { }
public class TestUserControlWithWhenActivated : UserControl, IActivatable
{
public bool Active { get; private set; }
public TestUserControlWithWhenActivated()
{
this.WhenActivated(disposables => {
Active = true;
Disposable
.Create(() => Active = false)
.DisposeWith(disposables);
});
}
}
[Fact]
public void Visual_Element_Is_Activated_And_Deactivated()
{
var userControl = new TestUserControl();
var activationForViewFetcher = new AvaloniaActivationForViewFetcher();
activationForViewFetcher
.GetActivationForView(userControl)
.ToObservableChangeSet(scheduler: ImmediateScheduler.Instance)
.Bind(out var activated)
.Subscribe();
var fakeRenderedDecorator = new TestRoot();
fakeRenderedDecorator.Child = userControl;
Assert.True(activated[0]);
Assert.Equal(1, activated.Count);
fakeRenderedDecorator.Child = null;
Assert.True(activated[0]);
Assert.False(activated[1]);
Assert.Equal(2, activated.Count);
}
[Fact]
public void Get_Affinity_For_View_Should_Return_Non_Zero_For_Visual_Elements()
{
var userControl = new TestUserControl();
var activationForViewFetcher = new AvaloniaActivationForViewFetcher();
var forUserControl = activationForViewFetcher.GetAffinityForView(userControl.GetType());
var forNonUserControl = activationForViewFetcher.GetAffinityForView(typeof(object));
Assert.NotEqual(0, forUserControl);
Assert.Equal(0, forNonUserControl);
}
[Fact]
public void Activation_For_View_Fetcher_Should_Support_When_Activated()
{
Locator.CurrentMutable.RegisterConstant(
new AvaloniaActivationForViewFetcher(),
typeof(IActivationForViewFetcher));
var userControl = new TestUserControlWithWhenActivated();
Assert.False(userControl.Active);
var fakeRenderedDecorator = new TestRoot();
fakeRenderedDecorator.Child = userControl;
Assert.True(userControl.Active);
fakeRenderedDecorator.Child = null;
Assert.False(userControl.Active);
}
}
}

15
tests/Avalonia.Visuals.UnitTests/Rendering/RenderLoopTests.cs

@ -19,14 +19,13 @@ namespace Avalonia.Visuals.UnitTests.Rendering
bool inDispatcher = false; bool inDispatcher = false;
dispatcher.Setup( dispatcher.Setup(
d => d.InvokeAsync(It.IsAny<Action>(), DispatcherPriority.Render)) d => d.Post(It.IsAny<Action>(), DispatcherPriority.Render))
.Callback((Action a, DispatcherPriority _) => .Callback((Action a, DispatcherPriority _) =>
{ {
inDispatcher = true; inDispatcher = true;
a(); a();
inDispatcher = false; inDispatcher = false;
}) });
.Returns(Task.CompletedTask);
var timer = new Mock<IRenderTimer>(); var timer = new Mock<IRenderTimer>();
@ -71,14 +70,13 @@ namespace Avalonia.Visuals.UnitTests.Rendering
var dispatcher = new Mock<IDispatcher>(); var dispatcher = new Mock<IDispatcher>();
bool inDispatcher = false; bool inDispatcher = false;
dispatcher.Setup( dispatcher.Setup(
d => d.InvokeAsync(It.IsAny<Action>(), DispatcherPriority.Render)) d => d.Post(It.IsAny<Action>(), DispatcherPriority.Render))
.Callback((Action a, DispatcherPriority _) => .Callback((Action a, DispatcherPriority _) =>
{ {
inDispatcher = true; inDispatcher = true;
a(); a();
inDispatcher = false; inDispatcher = false;
}) });
.Returns(Task.CompletedTask);
var timer = new Mock<IRenderTimer>(); var timer = new Mock<IRenderTimer>();
var loop = new RenderLoop(timer.Object, dispatcher.Object); var loop = new RenderLoop(timer.Object, dispatcher.Object);
@ -100,9 +98,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering
{ {
var dispatcher = new Mock<IDispatcher>(); var dispatcher = new Mock<IDispatcher>();
dispatcher.Setup( dispatcher.Setup(
d => d.InvokeAsync(It.IsAny<Action>(), DispatcherPriority.Render)) d => d.Post(It.IsAny<Action>(), DispatcherPriority.Render))
.Callback((Action a, DispatcherPriority _) => a()) .Callback((Action a, DispatcherPriority _) => a());
.Returns(Task.CompletedTask);
var timer = new Mock<IRenderTimer>(); var timer = new Mock<IRenderTimer>();
var loop = new RenderLoop(timer.Object, dispatcher.Object); var loop = new RenderLoop(timer.Object, dispatcher.Object);

Loading…
Cancel
Save