Browse Source

Merge branch 'master' into scenegraph

scenegraph-after-breakage
Steven Kirk 10 years ago
parent
commit
8ed9823c5a
  1. 5
      appveyor.yml
  2. 32
      build.cake
  3. 4
      samples/BindingTest/MainWindow.xaml
  4. 18
      samples/BindingTest/ViewModels/MainWindowViewModel.cs
  5. 10
      src/Avalonia.Base/AvaloniaObject.cs
  6. 31
      src/Avalonia.Base/Threading/AvaloniaScheduler.cs
  7. 4
      src/Avalonia.Controls/Application.cs
  8. 6
      src/Avalonia.Controls/TextBox.cs
  9. 18
      src/Avalonia.Visuals/VisualExtensions.cs
  10. 5
      src/Shared/PlatformSupport/StandardRuntimePlatform.cs
  11. 4
      src/Skia/Avalonia.Skia/FormattedTextImpl.cs
  12. 1
      src/Windows/Avalonia.Win32/WindowImpl.cs
  13. 30
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
  14. 29
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
  15. 1
      tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj
  16. 47
      tests/Avalonia.LeakTests/MemberSelectorTests.cs
  17. 26
      tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs

5
appveyor.yml

@ -20,6 +20,11 @@ build_script:
- ps: .\build.ps1 -Target "AppVeyor" -Platform "$env:platform" -Configuration "$env:configuration"
after_build:
- .\packages\JetBrains.dotMemoryUnit.2.1.20150828.125449\tools\dotMemoryUnit.exe -targetExecutable="%xunit20%\xunit.console.x86.exe" -returnTargetExitCode --"tests\Avalonia.LeakTests\bin\Release\Avalonia.LeakTests.dll"
- "SET PATH=C:\\Python34;C:\\Python34\\Scripts;%PATH%"
- pip install codecov
- codecov -f "./artifacts/coverage.xml"
test: off
artifacts:
- path: artifacts\nuget\*.nupkg

32
build.cake

@ -10,6 +10,7 @@
///////////////////////////////////////////////////////////////////////////////
#tool "nuget:?package=xunit.runner.console&version=2.1.0"
#tool "nuget:?package=OpenCover"
///////////////////////////////////////////////////////////////////////////////
// USINGS
@ -92,6 +93,7 @@ var artifactsDir = (DirectoryPath)Directory("./artifacts");
var nugetRoot = artifactsDir.Combine("nuget");
var zipRoot = artifactsDir.Combine("zip");
var binRoot = artifactsDir.Combine("bin");
var testsRoot = artifactsDir.Combine("tests");
var dirSuffix = configuration;
var dirSuffixSkia = (isPlatformAnyCPU ? "x86" : platform) + "/" + configuration;
@ -585,6 +587,7 @@ Task("Clean")
CleanDirectory(nugetRoot);
CleanDirectory(zipRoot);
CleanDirectory(binRoot);
CleanDirectory(testsRoot);
});
Task("Restore-NuGet-Packages")
@ -670,18 +673,35 @@ Task("Run-Unit-Tests")
"./tools/xunit.runner.console/tools/xunit.console.x86.exe" :
"./tools/xunit.runner.console/tools/xunit.console.exe";
var settings = new XUnit2Settings
var xUnitSettings = new XUnit2Settings
{
ToolPath = toolPath,
Parallelism = ParallelismOption.None
Parallelism = ParallelismOption.None,
ShadowCopy = false
};
settings.NoAppDomain = !isRunningOnWindows;
xUnitSettings.NoAppDomain = !isRunningOnWindows;
foreach (var file in unitTests)
var openCoverOutput = artifactsDir.GetFilePath(new FilePath("./coverage.xml"));
var openCoverSettings = new OpenCoverSettings()
.WithFilter("+[Avalonia.*]* -[*Test*]* -[ControlCatalog*]*")
.WithFilter("-[Avalonia.*]OmniXaml.* -[Avalonia.*]Glass.*")
.WithFilter("-[Avalonia.HtmlRenderer]TheArtOfDev.HtmlRenderer.* +[Avalonia.HtmlRenderer]TheArtOfDev.HtmlRenderer.Avalonia.* -[Avalonia.ReactiveUI]*");
foreach(var test in unitTests)
{
Information("Running test " + file.GetFilenameWithoutExtension());
XUnit2(file.FullPath, settings);
CopyDirectory(test.GetDirectory(), testsRoot);
}
if(isRunningOnWindows)
{
OpenCover(context => {
context.XUnit2(unitTests.Select(test => testsRoot.GetFilePath(test).FullPath), xUnitSettings);
}, openCoverOutput, openCoverSettings);
}
else
{
XUnit2(unitTests.Select(test => test.FullPath), xUnitSettings);
}
});

4
samples/BindingTest/MainWindow.xaml

@ -41,6 +41,10 @@
<TextBox Watermark="Value of first TextBox" UseFloatingWatermark="True"
Text="{Binding #first.Text, Mode=TwoWay}"/>
</StackPanel>
<StackPanel Margin="18" Gap="4" Width="200" HorizontalAlignment="Left">
<TextBlock FontSize="16" Text="Scheduler"/>
<TextBox Watermark="Background Thread" Text="{Binding CurrentTime, Mode=OneWay}"/>
</StackPanel>
</StackPanel>
</StackPanel>
</TabItem>

18
samples/BindingTest/ViewModels/MainWindowViewModel.cs

@ -3,6 +3,8 @@ using System.Collections.ObjectModel;
using System.Linq;
using ReactiveUI;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Threading;
namespace BindingTest.ViewModels
{
@ -12,6 +14,7 @@ namespace BindingTest.ViewModels
private double _doubleValue = 5.0;
private string _stringValue = "Simple Binding";
private bool _booleanFlag = false;
private string _currentTime;
public MainWindowViewModel()
{
@ -37,6 +40,15 @@ namespace BindingTest.ViewModels
BooleanFlag = !BooleanFlag;
StringValue = param.ToString();
});
Task.Run(() =>
{
while (true)
{
CurrentTime = DateTimeOffset.Now.ToString();
Thread.Sleep(1000);
}
});
}
public ObservableCollection<TestItem> Items { get; }
@ -67,6 +79,12 @@ namespace BindingTest.ViewModels
set { this.RaiseAndSetIfChanged(ref _booleanFlag, value); }
}
public string CurrentTime
{
get { return _currentTime; }
private set { this.RaiseAndSetIfChanged(ref _currentTime, value); }
}
public ReactiveCommand<object> StringValueCommand { get; }
public DataAnnotationsErrorViewModel DataAnnotationsValidation { get; } = new DataAnnotationsErrorViewModel();

10
src/Avalonia.Base/AvaloniaObject.cs

@ -12,6 +12,7 @@ using Avalonia.Diagnostics;
using Avalonia.Logging;
using Avalonia.Threading;
using Avalonia.Utilities;
using System.Reactive.Concurrency;
namespace Avalonia
{
@ -304,6 +305,11 @@ namespace Avalonia
VerifyAccess();
var description = GetDescription(source);
var scheduler = AvaloniaLocator.Current.GetService<IScheduler>() ?? ImmediateScheduler.Instance;
source = source.ObserveOn(scheduler);
if (property.IsDirect)
{
if (property.IsReadOnly)
@ -316,7 +322,7 @@ namespace Avalonia
this,
"Bound {Property} to {Binding} with priority LocalValue",
property,
GetDescription(source));
description);
IDisposable subscription = null;
@ -358,7 +364,7 @@ namespace Avalonia
this,
"Bound {Property} to {Binding} with priority {Priority}",
property,
GetDescription(source),
description,
priority);
return v.Add(source, (int)priority);

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

@ -3,6 +3,7 @@
using System;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
namespace Avalonia.Threading
{
@ -26,13 +27,31 @@ namespace Avalonia.Threading
/// <inheritdoc/>
public override IDisposable Schedule<TState>(TState state, TimeSpan dueTime, Func<IScheduler, TState, IDisposable> action)
{
return DispatcherTimer.Run(
() =>
var composite = new CompositeDisposable(2);
if (dueTime == TimeSpan.Zero)
{
if (!Dispatcher.UIThread.CheckAccess())
{
action(this, state);
return false;
},
dueTime);
var cancellation = new CancellationDisposable();
Dispatcher.UIThread.InvokeAsync(() =>
{
if (!cancellation.Token.IsCancellationRequested)
{
composite.Add(action(this, state));
}
}, DispatcherPriority.DataBind);
composite.Add(cancellation);
}
else
{
return action(this, state);
}
}
else
{
composite.Add(DispatcherTimer.RunOnce(() => composite.Add(action(this, state)), dueTime));
}
return composite;
}
}
}

4
src/Avalonia.Controls/Application.cs

@ -11,6 +11,7 @@ using Avalonia.Layout;
using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.Threading;
using System.Reactive.Concurrency;
namespace Avalonia
{
@ -175,7 +176,8 @@ namespace Avalonia
.Bind<IKeyboardNavigationHandler>().ToTransient<KeyboardNavigationHandler>()
.Bind<IStyler>().ToConstant(_styler)
.Bind<ILayoutManager>().ToSingleton<LayoutManager>()
.Bind<IApplicationLifecycle>().ToConstant(this);
.Bind<IApplicationLifecycle>().ToConstant(this)
.Bind<IScheduler>().ToConstant(AvaloniaScheduler.Instance);
}
}
}

6
src/Avalonia.Controls/TextBox.cs

@ -202,6 +202,7 @@ namespace Avalonia.Controls
{
if (!_ignoreTextChanges)
{
CaretIndex = CoerceCaretIndex(CaretIndex, value?.Length ?? 0);
SetAndRaise(TextProperty, ref _text, value);
}
}
@ -558,10 +559,11 @@ namespace Avalonia.Controls
return null;
}
private int CoerceCaretIndex(int value)
private int CoerceCaretIndex(int value) => CoerceCaretIndex(value, Text?.Length ?? 0);
private int CoerceCaretIndex(int value, int length)
{
var text = Text;
var length = text?.Length ?? 0;
if (value < 0)
{

18
src/Avalonia.Visuals/VisualExtensions.cs

@ -30,7 +30,7 @@ namespace Avalonia
public static Point PointToClient(this IVisual visual, Point point)
{
var p = GetRootAndPosition(visual);
return p.Item1.PointToClient(point + p.Item2);
return p.Item1.PointToClient(point - p.Item2);
}
/// <summary>
@ -45,6 +45,22 @@ namespace Avalonia
return p.Item1.PointToScreen(point + p.Item2);
}
/// <summary>
/// Translates a point relative to this visual to coordinates that are relative to the specified visual.
/// The visual and relativeTo should be descendants of the same root window
/// </summary>
/// <param name="visual">The visual.</param>
/// <param name="point">The point value, as relative to this visual.</param>
/// <param name="relativeTo">The visual to translate the given point into.</param>
/// <returns>A point value, now relative to the target visual rather than this source element.</returns>
public static Point TranslatePoint(this IVisual visual, Point point, IVisual relativeTo)
{
var pos = GetRootAndPosition(visual);
var relToPos = GetRootAndPosition(relativeTo);
return point - (relToPos.Item2 - pos.Item2);
}
/// <summary>
/// Gets the root of the control's visual tree and the position of the control
/// in the root's coordinate space.

5
src/Shared/PlatformSupport/StandardRuntimePlatform.cs

@ -16,10 +16,7 @@ namespace Avalonia.Shared.PlatformSupport
public void PostThreadPoolItem(Action cb) => ThreadPool.UnsafeQueueUserWorkItem(_ => cb(), null);
public IDisposable StartSystemTimer(TimeSpan interval, Action tick)
{
var timer = new Timer(delegate
{
}, null, interval, interval);
var timer = new Timer(_ => tick(), null, interval, interval);
return Disposable.Create(() => timer.Dispose());
}

4
src/Skia/Avalonia.Skia/FormattedTextImpl.cs

@ -522,7 +522,7 @@ namespace Avalonia.Skia
_lineHeight = mDescent - mAscent;
// Rendering is relative to baseline
_lineOffset = -metrics.Top;
_lineOffset = (-metrics.Ascent);
string subString;
@ -590,7 +590,7 @@ namespace Avalonia.Skia
else
{
var lastLine = _skiaLines[_skiaLines.Count - 1];
_size = new Size(maxX, lastLine.Top + lastLine.Height + lastLineDescent);
_size = new Size(maxX, lastLine.Top + lastLine.Height);
}
}

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

@ -682,6 +682,7 @@ namespace Avalonia.Win32
}
UnmanagedMethods.ShowWindow(_hwnd, command);
UnmanagedMethods.SetFocus(_hwnd);
}
public void SetIcon(IWindowIconImpl icon)

30
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs

@ -11,6 +11,13 @@ using Avalonia.Data;
using Avalonia.Logging;
using Avalonia.UnitTests;
using Xunit;
using System.Threading.Tasks;
using Avalonia.Platform;
using System.Threading;
using Moq;
using System.Reactive.Disposables;
using System.Reactive.Concurrency;
using Avalonia.Threading;
namespace Avalonia.Base.UnitTests
{
@ -356,6 +363,29 @@ namespace Avalonia.Base.UnitTests
Assert.True(called);
}
}
[Fact]
public async void Bind_With_Scheduler_Executes_On_Scheduler()
{
var target = new Class1();
var source = new Subject<object>();
var currentThreadId = Thread.CurrentThread.ManagedThreadId;
var threadingInterfaceMock = new Mock<IPlatformThreadingInterface>();
threadingInterfaceMock.SetupGet(mock => mock.CurrentThreadIsLoopThread)
.Returns(() => Thread.CurrentThread.ManagedThreadId == currentThreadId);
using (AvaloniaLocator.EnterScope())
{
AvaloniaLocator.CurrentMutable.Bind<IPlatformThreadingInterface>().ToConstant(threadingInterfaceMock.Object);
AvaloniaLocator.CurrentMutable.Bind<IScheduler>().ToConstant(AvaloniaScheduler.Instance);
target.Bind(Class1.QuxProperty, source);
await Task.Run(() => source.OnNext(6.7));
}
}
/// <summary>
/// Returns an observable that returns a single value but does not complete.

29
tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

@ -1,6 +1,8 @@
// 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.Reactive.Linq;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Data;
@ -200,6 +202,33 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact]
public void Setting_Text_Updates_CaretPosition()
{
using (UnitTestApplication.Start(Services))
{
var target = new TextBox
{
Text = "Initial Text",
CaretIndex = 11
};
var invoked = false;
target.GetObservable(TextBox.TextProperty).Skip(1).Subscribe(_ =>
{
// Caret index should be set before Text changed notification, as we don't want
// to notify with an invalid CaretIndex.
Assert.Equal(7, target.CaretIndex);
invoked = true;
});
target.Text = "Changed";
Assert.True(invoked);
}
}
private static TestServices Services => TestServices.MockThreadingInterface.With(
standardCursorFactory: Mock.Of<IStandardCursorFactory>());

1
tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj

@ -99,6 +99,7 @@
<Compile Include="AvaloniaObjectTests.cs" />
<Compile Include="ControlTests.cs" />
<Compile Include="ExpressionObserverTests.cs" />
<Compile Include="MemberSelectorTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>

47
tests/Avalonia.LeakTests/MemberSelectorTests.cs

@ -0,0 +1,47 @@
using Avalonia.Markup.Xaml.Templates;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace Avalonia.LeakTests
{
public class MemberSelectorTests
{
[Fact]
public void Should_Not_Hold_Reference_To_Object()
{
WeakReference dataRef = null;
var selector = new MemberSelector() { MemberName = "Child.StringValue" };
Action run = () =>
{
var data = new Item()
{
Child = new Item() { StringValue = "Value1" }
};
Assert.Same("Value1", selector.Select(data));
dataRef = new WeakReference(data);
};
run();
GC.Collect();
Assert.False(dataRef.IsAlive);
}
private class Item
{
public Item Child { get; set; }
public int IntValue { get; set; }
public string StringValue { get; set; }
}
}
}

26
tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs

@ -9,32 +9,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.Templates
{
public class MemberSelectorTests
{
[Fact]
public void Should_Not_Hold_Reference_To_Object()
{
WeakReference dataRef = null;
var selector = new MemberSelector() { MemberName = "Child.StringValue" };
Action run = () =>
{
var data = new Item()
{
Child = new Item() { StringValue = "Value1" }
};
Assert.Same("Value1", selector.Select(data));
dataRef = new WeakReference(data);
};
run();
GC.Collect();
Assert.False(dataRef.IsAlive);
}
[Fact]
public void Should_Select_Child_Property_Value()
{

Loading…
Cancel
Save