Selaa lähdekoodia

Merge branch 'master' into ghosting-fix

Jeremy Koritzinsky 7 vuotta sitten
vanhempi
sitoutus
0aa9ec6458
73 muutettua tiedostoa jossa 1362 lisäystä ja 1876 poistoa
  1. 2 0
      .gitignore
  2. 1 1
      .travis.yml
  3. 2 603
      Avalonia.sln
  4. 0 6
      appveyor.yml
  5. 18 58
      build.cake
  6. 8 0
      build/AndroidWorkarounds.props
  7. 1 1
      build/Base.props
  8. 1 4
      build/Binding.props
  9. 0 14
      build/Markup.props
  10. 0 5
      build/Sprache.props
  11. 1 1
      build/System.Memory.props
  12. 5 0
      build/iOSWorkarounds.props
  13. 0 2
      build/readme.md
  14. 25 0
      dirs.proj
  15. 6 0
      global.json
  16. 26 48
      packages.cake
  17. 12 19
      parameters.cake
  18. 2 1
      samples/BindingDemo/BindingDemo.csproj
  19. 2 1
      samples/ControlCatalog.Android/ControlCatalog.Android.csproj
  20. 2 1
      samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj
  21. 2 2
      samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj
  22. 2 1
      samples/RenderDemo/RenderDemo.csproj
  23. 2 1
      samples/VirtualizationDemo/VirtualizationDemo.csproj
  24. 14 130
      src/Android/Avalonia.Android/Avalonia.Android.csproj
  25. 0 30
      src/Android/Avalonia.Android/Properties/AssemblyInfo.cs
  26. 3 2
      src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj
  27. 2 1
      src/Avalonia.Base/Avalonia.Base.csproj
  28. 49 0
      src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs
  29. 1 0
      src/Avalonia.Base/Data/Core/ExpressionParseException.cs
  30. 10 3
      src/Avalonia.Base/Threading/Dispatcher.cs
  31. 8 3
      src/Avalonia.Base/Threading/IDispatcher.cs
  32. 120 48
      src/Avalonia.Base/Threading/JobRunner.cs
  33. 53 12
      src/Avalonia.Base/Utilities/CharacterReader.cs
  34. 6 11
      src/Avalonia.Base/Utilities/IdentifierParser.cs
  35. 1 0
      src/Avalonia.Controls/Window.cs
  36. 22 4
      src/Avalonia.Input/InputElement.cs
  37. 13 11
      src/Avalonia.Input/MouseDevice.cs
  38. 0 1
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  39. 1 2
      src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs
  40. 4 1
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  41. 5 5
      src/Markup/Avalonia.Markup.Xaml/Parsers/PropertyParser.cs
  42. 2 2
      src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
  43. 19 2
      src/Markup/Avalonia.Markup/Data/Binding.cs
  44. 5 8
      src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs
  45. 3 3
      src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs
  46. 80 60
      src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs
  47. 283 112
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs
  48. 54 64
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs
  49. 13 1
      src/OSX/Avalonia.MonoMac/PopupImpl.cs
  50. 8 8
      src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs
  51. 0 19
      src/Shared/SharedAssemblyInfo.cs
  52. 0 20
      src/Shared/avalonia.platform.targets
  53. 22 116
      src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj
  54. 0 36
      src/Windows/Avalonia.Win32.Interop/Properties/AssemblyInfo.cs
  55. 0 13
      src/Windows/Avalonia.Win32/Avalonia.Win32.Shared.shproj
  56. 14 107
      src/iOS/Avalonia.iOS/Avalonia.iOS.csproj
  57. 0 36
      src/iOS/Avalonia.iOS/Properties/AssemblyInfo.cs
  58. 0 17
      src/iOS/Avalonia.iOS/iOSPlatform.cs
  59. 2 1
      src/iOS/Avalonia.iOSTestApplication/Avalonia.iOSTestApplication.csproj
  60. 20 106
      src/tools/Avalonia.Designer.HostApp.NetFX/Avalonia.Designer.HostApp.NetFX.csproj
  61. 2 0
      tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
  62. 34 0
      tests/Avalonia.Benchmarks/Markup/Parsing.cs
  63. 1 3
      tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj
  64. 139 20
      tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs
  65. 0 1
      tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj
  66. 146 0
      tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs
  67. 22 22
      tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs
  68. 3 1
      tests/Avalonia.Markup.UnitTests/Parsers/SelectorParserTests.cs
  69. 0 1
      tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
  70. 30 44
      tests/Avalonia.Markup.Xaml.UnitTests/Parsers/PropertyParserTests.cs
  71. 22 0
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs
  72. 6 0
      tests/Avalonia.UnitTests/ImmediateDispatcher.cs
  73. 0 20
      tests/run-tests.sh

+ 2 - 0
.gitignore

@@ -187,3 +187,5 @@ project.lock.json
 ## BenchmarkDotNet
 ##################
 BenchmarkDotNet.Artifacts/
+
+dirs.sln

+ 1 - 1
.travis.yml

@@ -11,7 +11,7 @@ mono:
   - 5.2.0
 dotnet: 2.1.200
 script:
-  - ./build.sh --target "Travis" --platform "NetCoreOnly" --configuration "Release"
+  - ./build.sh --target "Travis" --configuration "Release"
 notifications:
   email: false
   webhooks:

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 2 - 603
Avalonia.sln


+ 0 - 6
appveyor.yml

@@ -11,10 +11,6 @@ environment:
   MYGET_API_URL: https://www.myget.org/F/avalonia-ci/api/v2/package
 init:
 - ps: if (Test-Path env:nuget_address) {[System.IO.File]::AppendAllText("C:\Windows\System32\drivers\etc\hosts", "`n$($env:nuget_address)`tapi.nuget.org")}
-install:
-  - if not exist dotnet-2.0.0.exe appveyor DownloadFile https://download.microsoft.com/download/0/F/D/0FD852A4-7EA1-4E2A-983A-0484AC19B92C/dotnet-sdk-2.0.0-win-x64.exe -FileName "dotnet-2.0.0.exe"
-  - ps: Start-Process -FilePath "dotnet-2.0.0.exe" -ArgumentList "/quiet" -Wait
-  - cmd: set PATH=%programfiles(x86)%\GtkSharp\2.12\bin\;%PATH%
 before_build:
 - git submodule update --init
 build_script:
@@ -25,5 +21,3 @@ artifacts:
   - path: artifacts\nuget\*.nupkg
   - path: artifacts\zip\*.zip
   - path: artifacts\inspectcode.xml
-cache:
-  - dotnet-2.0.0.exe

+ 18 - 58
build.cake

@@ -2,7 +2,6 @@
 // ADDINS
 ///////////////////////////////////////////////////////////////////////////////
 
-#addin "nuget:?package=Polly&version=5.3.1"
 #addin "nuget:?package=NuGet.Core&version=2.14.0"
 #tool "nuget:?package=NuGet.CommandLine&version=4.3.0"
 #tool "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2017.1.20170613.162720"
@@ -22,7 +21,6 @@ using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Linq;
-using Polly;
 using NuGet;
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -57,9 +55,8 @@ Setup<AvaloniaBuildData>(context =>
     var parameters = new Parameters(context);
     var buildContext = new AvaloniaBuildData(parameters, new Packages(context, parameters));
 
-    Information("Building version {0} of Avalonia ({1}, {2}) using version {3} of Cake.", 
+    Information("Building version {0} of Avalonia ({1}) using version {2} of Cake.", 
         parameters.Version,
-        parameters.Platform,
         parameters.Configuration,
         typeof(ICakeContext).Assembly.GetName().Version.ToString());
 
@@ -69,7 +66,6 @@ Setup<AvaloniaBuildData>(context =>
         Information("Repository Branch: " + BuildSystem.AppVeyor.Environment.Repository.Branch);
     }
     Information("Target:" + context.TargetTask.Name);
-    Information("Platform: " + parameters.Platform);
     Information("Configuration: " + parameters.Configuration);
     Information("IsLocalBuild: " + parameters.IsLocalBuild);
     Information("IsRunningOnUnix: " + parameters.IsRunningOnUnix);
@@ -109,60 +105,27 @@ Task("Clean-Impl")
     CleanDirectory(data.Parameters.BinRoot);
 });
 
-Task("Restore-NuGet-Packages-Impl")
-    .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsRunningOnWindows)
-    .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPlatformNetCoreOnly)
-    .Does<AvaloniaBuildData>(data =>
-{
-    var maxRetryCount = 5;
-    var toolTimeout = 2d;
-    Policy
-        .Handle<Exception>()
-        .Retry(maxRetryCount, (exception, retryCount, context) => {
-            if (retryCount == maxRetryCount)
-            {
-                throw exception;
-            }
-            else
-            {
-                Verbose("{0}", exception);
-                toolTimeout+=0.5;
-            }})
-        .Execute(()=> {
-                NuGetRestore(data.Parameters.MSBuildSolution, new NuGetRestoreSettings {
-                    ToolTimeout = TimeSpan.FromMinutes(toolTimeout)
-                });
-        });
-});
-
 void DotNetCoreBuild(Parameters parameters)
 {
     var settings = new DotNetCoreBuildSettings 
     {
         Configuration = parameters.Configuration,
-        MSBuildSettings = new DotNetCoreMSBuildSettings(),
     };
 
-    settings.MSBuildSettings.SetConfiguration(parameters.Configuration);
-    settings.MSBuildSettings.WithProperty("Platform", "\"" + parameters.Platform + "\"");
-
     DotNetCoreBuild(parameters.MSBuildSolution, settings);
 }
 
 Task("Build-Impl")
     .Does<AvaloniaBuildData>(data =>
 {
-    if(data.Parameters.IsRunningOnWindows && !data.Parameters.IsPlatformNetCoreOnly)
+    if(data.Parameters.IsRunningOnWindows)
     {
         MSBuild(data.Parameters.MSBuildSolution, settings => {
             settings.SetConfiguration(data.Parameters.Configuration);
             settings.SetVerbosity(Verbosity.Minimal);
-            settings.WithProperty("Platform", "\"" + data.Parameters.Platform + "\"");
-            settings.WithProperty("UseRoslynPathHack", "true");
+            settings.WithProperty("iOSRoslynPathHackRequired", "true");
             settings.UseToolVersion(MSBuildToolVersion.VS2017);
-            settings.WithProperty("Windows", "True");
-            settings.SetNodeReuse(false);
-            settings.SetMaxCpuCount(0);
+            settings.WithRestore();
         });
     }
     else
@@ -207,9 +170,9 @@ Task("Run-Unit-Tests-Impl")
     RunCoreTest("./tests/Avalonia.Styling.UnitTests", data.Parameters, false);
     RunCoreTest("./tests/Avalonia.Visuals.UnitTests", data.Parameters, false);
     RunCoreTest("./tests/Avalonia.Skia.UnitTests", data.Parameters, false);
-    if (data.Parameters.IsRunningOnWindows && !data.Parameters.IsPlatformNetCoreOnly)
+    if (data.Parameters.IsRunningOnWindows)
     {
-        RunCoreTest("./tests/Avalonia.Direct2D1.UnitTests", data.Parameters, true);
+        RunCoreTest("./tests/Avalonia.Direct2D1.UnitTests", data.Parameters, false);
     }
 });
 
@@ -223,7 +186,6 @@ Task("Run-Designer-Tests-Impl")
 Task("Run-Render-Tests-Impl")
     .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.SkipTests)
     .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsRunningOnWindows)
-    .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPlatformNetCoreOnly)
     .Does<AvaloniaBuildData>(data =>
 {
     RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj", data.Parameters, true);
@@ -233,7 +195,6 @@ Task("Run-Render-Tests-Impl")
 Task("Run-Leak-Tests-Impl")
     .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.SkipTests)
     .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsRunningOnWindows)
-    .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPlatformNetCoreOnly)
     .Does(() =>
 {
     var dotMemoryUnit = Context.Tools.Resolve("dotMemoryUnit.exe");
@@ -266,15 +227,13 @@ Task("Zip-Files-Impl")
 
     Zip(data.Parameters.NugetRoot, data.Parameters.ZipNuGetArtifacts);
 
-    if (!data.Parameters.IsPlatformNetCoreOnly) {
-        Zip(data.Parameters.ZipSourceControlCatalogDesktopDirs, 
-            data.Parameters.ZipTargetControlCatalogDesktopDirs, 
-            GetFiles(data.Parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dll") + 
-            GetFiles(data.Parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.config") + 
-            GetFiles(data.Parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.so") + 
-            GetFiles(data.Parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dylib") + 
-            GetFiles(data.Parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.exe"));
-    }
+    Zip(data.Parameters.ZipSourceControlCatalogDesktopDirs, 
+        data.Parameters.ZipTargetControlCatalogDesktopDirs, 
+        GetFiles(data.Parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dll") + 
+        GetFiles(data.Parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.config") + 
+        GetFiles(data.Parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.so") + 
+        GetFiles(data.Parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dylib") + 
+        GetFiles(data.Parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.exe"));
 });
 
 Task("Create-NuGet-Packages-Impl")
@@ -353,7 +312,6 @@ Task("Publish-NuGet-Impl")
 
 Task("Inspect-Impl")
     .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsRunningOnWindows)
-    .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPlatformNetCoreOnly)
     .Does(() =>
 {
     var badIssues = new []{"PossibleNullReferenceException"};
@@ -392,10 +350,12 @@ Task("Inspect-Impl")
 // TARGETS
 ///////////////////////////////////////////////////////////////////////////////
 
-Task("Run-Tests")
+Task("Build")
     .IsDependentOn("Clean-Impl")
-    .IsDependentOn("Restore-NuGet-Packages-Impl")
-    .IsDependentOn("Build-Impl")
+    .IsDependentOn("Build-Impl");
+
+Task("Run-Tests")
+    .IsDependentOn("Build")
     .IsDependentOn("Run-Unit-Tests-Impl")
     .IsDependentOn("Run-Render-Tests-Impl")
     .IsDependentOn("Run-Designer-Tests-Impl")

+ 8 - 0
build/AndroidWorkarounds.props

@@ -0,0 +1,8 @@
+<Project>
+  <ItemGroup Condition="'$(AndroidApplication)' == 'true'">
+    <!-- WORKAROUND: The packages below are transitively referenced by System.Memory,
+      but Xamarin.Android applications need the newest versions directly referenced for the linker. -->
+    <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.1" />
+    <PackageReference Include="System.Buffers" Version="4.5.0" />
+  </ItemGroup>
+</Project>

+ 1 - 1
build/Base.props

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

+ 1 - 4
build/Binding.props

@@ -1,8 +1,5 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <PackageReference Include="System.ComponentModel.Annotations" Version="4.3.0" />
-    <PackageReference Include="System.ComponentModel.TypeConverter" Version="4.3.0" />
-    <PackageReference Include="System.ComponentModel.Primitives" Version="4.3.0" />
-    <PackageReference Include="System.Runtime.Serialization.Primitives" Version="4.3.0" />
+    <PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />
   </ItemGroup>
 </Project>

+ 0 - 14
build/Markup.props

@@ -1,14 +0,0 @@
-<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <ItemGroup>
-    <PackageReference Include="System.ComponentModel.Annotations" Version="4.3.0" />
-    <PackageReference Include="System.Globalization" Version="4.3.0" />
-    <PackageReference Include="System.Linq" Version="4.3.0" />
-    <PackageReference Include="System.Runtime" Version="4.3.0" />
-    <PackageReference Include="System.Text.RegularExpressions" Version="4.3.0" />
-    <PackageReference Include="System.ComponentModel.TypeConverter" Version="4.3.0" />
-    <PackageReference Include="System.ComponentModel.Primitives" Version="4.3.0" />
-    <PackageReference Include="System.Runtime.Serialization.Primitives" Version="4.3.0" />
-    <PackageReference Include="System.Xml.XmlDocument" Version="4.3.0" />
-    <PackageReference Include="System.Xml.ReaderWriter" Version="4.3.0" />
-  </ItemGroup>
-</Project>

+ 0 - 5
build/Sprache.props

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

+ 1 - 1
build/System.Memory.props

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

+ 5 - 0
build/iOSWorkarounds.props

@@ -0,0 +1,5 @@
+<Project>
+  <PropertyGroup Condition="'$(iOSRoslynPathHackRequired)' == 'true'">
+    <CscToolPath>$(MSBuildToolsPath)\..\Roslyn</CscToolPath>
+  </PropertyGroup>
+</Project>

+ 0 - 2
build/readme.md

@@ -4,7 +4,6 @@
 <Import Project="..\..\build\JetBrains.Annotations.props" />
 <Import Project="..\..\build\JetBrains.dotMemoryUnit.props" />
 <Import Project="..\..\build\Magick.NET-Q16-AnyCPU.props" />
-<Import Project="..\..\build\Markup.props" />
 <Import Project="..\..\build\Microsoft.CSharp.props" />
 <Import Project="..\..\build\Microsoft.Reactive.Testing.props" />
 <Import Project="..\..\build\Moq.props" />
@@ -16,7 +15,6 @@
 <Import Project="..\..\build\SkiaSharp.Desktop.props" />
 <Import Project="..\..\build\SkiaSharp.props" />
 <Import Project="..\..\build\Splat.props" />
-<Import Project="..\..\build\Sprache.props" />
 <Import Project="..\..\build\XUnit.props" />
 ```
 

+ 25 - 0
dirs.proj

@@ -0,0 +1,25 @@
+<Project Sdk="Microsoft.Build.Traversal">
+  <ItemGroup>
+    <ProjectReference Include="src/**/*.*proj" />
+    <ProjectReference Include="samples/**/*.*proj" />
+    <ProjectReference Include="tests/**/*.*proj" />
+    <ProjectReference Remove="**/*.shproj" />
+    <ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/PortableXaml/**/*.*proj" />
+  </ItemGroup>
+  <ItemGroup Condition="!Exists('$(MSBuildExtensionsPath)\Xamarin\Android')">
+    <ProjectReference Remove="src/Android/**/*.*proj" />
+    <ProjectReference Remove="samples/ControlCatalog.Android/ControlCatalog.Android.csproj" />
+  </ItemGroup>
+  <ItemGroup Condition="!Exists('$(MSBuildExtensionsPath)\Xamarin\iOS')">
+    <ProjectReference Remove="src/iOS/**/*.*proj" />
+    <ProjectReference Remove="samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj" />
+  </ItemGroup>
+  <ItemGroup Condition="!$([MSBuild]::IsOsPlatform('Windows')) OR '$(MSBuildRuntimeType)' != 'Full'">
+    <ProjectReference Remove="src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj" />
+    <ProjectReference Remove="samples/interop/**/*.*proj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="SlnGen" Version="2.0.40" PrivateAssets="all" />
+  </ItemGroup>
+</Project>

+ 6 - 0
global.json

@@ -0,0 +1,6 @@
+{
+    "msbuild-sdks": {
+        "Microsoft.Build.Traversal": "1.0.41",
+        "MSBuild.Sdk.Extras": "1.6.46"
+    }
+}

+ 26 - 48
packages.cake

@@ -110,7 +110,6 @@ public class Packages
         var SerilogVersion = packageVersions["Serilog"].FirstOrDefault().Item1;
         var SerilogSinksDebugVersion = packageVersions["Serilog.Sinks.Debug"].FirstOrDefault().Item1;
         var SerilogSinksTraceVersion = packageVersions["Serilog.Sinks.Trace"].FirstOrDefault().Item1;
-        var SpracheVersion = packageVersions["Sprache"].FirstOrDefault().Item1;
         var SystemReactiveVersion = packageVersions["System.Reactive"].FirstOrDefault().Item1;
         var ReactiveUIVersion = packageVersions["reactiveui"].FirstOrDefault().Item1;
         var SystemValueTupleVersion = packageVersions["System.ValueTuple"].FirstOrDefault().Item1;
@@ -121,10 +120,9 @@ public class Packages
         var SharpDXDirect3D11Version = packageVersions["SharpDX.Direct3D11"].FirstOrDefault().Item1;
         var SharpDXDirect3D9Version = packageVersions["SharpDX.Direct3D9"].FirstOrDefault().Item1;
         var SharpDXDXGIVersion = packageVersions["SharpDX.DXGI"].FirstOrDefault().Item1;
-        var SystemComponentModelAnnotationsVersion = packageVersions["System.ComponentModel.Annotations"].FirstOrDefault().Item1;
+        var SystemMemoryVersion = packageVersions["System.Memory"].FirstOrDefault().Item1;
 
         context.Information("Package: Serilog, version: {0}", SerilogVersion);
-        context.Information("Package: Sprache, version: {0}", SpracheVersion);
         context.Information("Package: System.Reactive, version: {0}", SystemReactiveVersion);
         context.Information("Package: reactiveui, version: {0}", ReactiveUIVersion);
         context.Information("Package: System.ValueTuple, version: {0}", SystemValueTupleVersion);
@@ -135,6 +133,7 @@ public class Packages
         context.Information("Package: SharpDX.Direct3D11, version: {0}", SharpDXDirect3D11Version);
         context.Information("Package: SharpDX.Direct3D9, version: {0}", SharpDXDirect3D9Version);
         context.Information("Package: SharpDX.DXGI, version: {0}", SharpDXDXGIVersion);
+        context.Information("Package: System.Memory, version: {0}", SystemMemoryVersion);
 
         var nugetPackagesDir = System.Environment.GetEnvironmentVariable("NUGET_HOME")
             ?? System.IO.Path.Combine(System.Environment.GetEnvironmentVariable("USERPROFILE") ?? System.Environment.GetEnvironmentVariable("HOME"), ".nuget");
@@ -187,9 +186,9 @@ public class Packages
             };
         });
 
-        var win32CoreLibrariesNuSpecContent = coreLibrariesFiles.Select((file) => {
+        var netFrameworkCoreLibrariesNuSpecContent = coreLibrariesFiles.Select((file) => {
             return new NuSpecContent { 
-                Source = file.FullPath, Target = "lib/net45" 
+                Source = file.FullPath, Target = "lib/net461" 
             };
         });
 
@@ -199,10 +198,10 @@ public class Packages
             };
         });
 
-        var net45RuntimePlatform = extensionsToPack.Select(libSuffix => {
+        var netFrameworkRuntimePlatform = extensionsToPack.Select(libSuffix => {
             return new NuSpecContent {
                 Source = ((FilePath)context.File("./src/Avalonia.DotNetFrameworkRuntime/bin/" + parameters.DirSuffix + "/net461/Avalonia.DotNetFrameworkRuntime" + libSuffix)).FullPath, 
-                Target = "lib/net45" 
+                Target = "lib/net461" 
             };
         });
 
@@ -219,26 +218,16 @@ public class Packages
         };
 
         var toolHostAppNetFx = new NuSpecContent{
-            Source = ((FilePath)context.File("./src/tools/Avalonia.Designer.HostApp.NetFx/bin/" + parameters.DirSuffix + "/Avalonia.Designer.HostApp.exe")).FullPath, 
+            Source = ((FilePath)context.File("./src/tools/Avalonia.Designer.HostApp.NetFx/bin/" + parameters.DirSuffix + "/net461/Avalonia.Designer.HostApp.exe")).FullPath, 
             Target = "tools/net461/previewer"
         };
 
-        IList<NuSpecContent> coreFiles;
-
-        if (!parameters.IsPlatformNetCoreOnly) {
-            var toolsContent = new[] { toolHostApp, toolHostAppNetFx };
-            coreFiles = coreLibrariesNuSpecContent
-                .Concat(win32CoreLibrariesNuSpecContent).Concat(net45RuntimePlatform)
-                .Concat(netcoreappCoreLibrariesNuSpecContent).Concat(netCoreRuntimePlatform)
-                .Concat(toolsContent)
-                .ToList();
-        } else {
-            var toolsContent = new[] { toolHostApp };
-            coreFiles = coreLibrariesNuSpecContent
-                .Concat(netcoreappCoreLibrariesNuSpecContent).Concat(netCoreRuntimePlatform)
-                .Concat(toolsContent)
-                .ToList();
-        }
+        var toolsContent = new[] { toolHostApp, toolHostAppNetFx };
+        var coreFiles = coreLibrariesNuSpecContent
+            .Concat(netFrameworkCoreLibrariesNuSpecContent).Concat(netFrameworkRuntimePlatform)
+            .Concat(netcoreappCoreLibrariesNuSpecContent).Concat(netCoreRuntimePlatform)
+            .Concat(toolsContent)
+            .ToList();
 
         var nuspecNuGetSettingsCore = new []
         {
@@ -250,27 +239,16 @@ public class Packages
                 Id = "Avalonia",
                 Dependencies = new DependencyBuilder(this)
                 {
-                    new NuSpecDependency() { Id = "Serilog", Version = SerilogVersion },
-                    new NuSpecDependency() { Id = "Serilog.Sinks.Debug", Version = SerilogSinksDebugVersion },
-                    new NuSpecDependency() { Id = "Serilog.Sinks.Trace", Version = SerilogSinksTraceVersion },
-                    new NuSpecDependency() { Id = "Sprache", Version = SpracheVersion },
-                    new NuSpecDependency() { Id = "System.Reactive", Version = SystemReactiveVersion },
-                    new NuSpecDependency() { Id = "Avalonia.Remote.Protocol", Version = parameters.Version },
-                    new NuSpecDependency() { Id = "System.ComponentModel.Annotations", Version = SystemComponentModelAnnotationsVersion },
-                    //.NET Core
-                    new NuSpecDependency() { Id = "System.Threading.ThreadPool", TargetFramework = "netcoreapp2.0", Version = "4.3.0" },
-                    new NuSpecDependency() { Id = "Microsoft.Extensions.DependencyModel", TargetFramework = "netcoreapp2.0", Version = "1.1.0" },
-                    new NuSpecDependency() { Id = "NETStandard.Library", TargetFramework = "netcoreapp2.0", Version = "1.6.0" },
-                    new NuSpecDependency() { Id = "Serilog", TargetFramework = "netcoreapp2.0", Version = SerilogVersion },
-                    new NuSpecDependency() { Id = "Serilog.Sinks.Debug", TargetFramework = "netcoreapp2.0", Version = SerilogSinksDebugVersion },
-                    new NuSpecDependency() { Id = "Serilog.Sinks.Trace", TargetFramework = "netcoreapp2.0", Version = SerilogSinksTraceVersion },
-                    new NuSpecDependency() { Id = "Sprache", TargetFramework = "netcoreapp2.0", Version = SpracheVersion },
-                    new NuSpecDependency() { Id = "System.Reactive", TargetFramework = "netcoreapp2.0", Version = SystemReactiveVersion },
-                    new NuSpecDependency() { Id = "Avalonia.Remote.Protocol", TargetFramework = "netcoreapp2.0", Version = parameters.Version },
+                    new NuSpecDependency() { Id = "Avalonia.Remote.Protocol", Version = parameters.Version, TargetFramework="netstandard2.0" },
+                    new NuSpecDependency() { Id = "Avalonia.Remote.Protocol", Version = parameters.Version, TargetFramework="netcoreapp2.0" },
+                    new NuSpecDependency() { Id = "Avalonia.Remote.Protocol", Version = parameters.Version, TargetFramework="net461" },
+                    new NuSpecDependency() { Id = "System.ValueTuple", Version = SystemValueTupleVersion, TargetFramework="net461" },
+                    new NuSpecDependency() { Id = "System.ComponentModel.TypeConverter", Version = "4.3.0", TargetFramework="net461" },
+                    new NuSpecDependency() { Id = "NETStandard.Library", Version = "2.0.0", TargetFramework="net461"}
                 }
-                .Deps(new string[]{null, "netcoreapp2.0"},
-                    "System.ValueTuple", "System.ComponentModel.TypeConverter", "System.ComponentModel.Primitives",
-                    "System.Runtime.Serialization.Primitives", "System.Xml.XmlDocument", "System.Xml.ReaderWriter", "System.Memory")
+                .Deps(new string[]{"netstandard2.0", "netcoreapp2.0", "net461"},
+                    "Serilog", "Serilog.Sinks.Debug", "Serilog.Sinks.Trace",
+                    "System.Memory", "System.Reactive", "System.ComponentModel.Annotations")
                 .ToArray(),
                 Files = coreFiles,
                 BasePath = context.Directory("./"),
@@ -325,7 +303,7 @@ public class Packages
                 {
                     new NuSpecContent { Source = "Avalonia.Android.dll", Target = "lib/MonoAndroid10" }
                 },
-                BasePath = context.Directory("./src/Android/Avalonia.Android/bin/" + parameters.DirSuffix),
+                BasePath = context.Directory("./src/Android/Avalonia.Android/bin/" + parameters.DirSuffix + "/monoandroid44/"),
                 OutputDirectory = parameters.NugetRoot
             },
             ///////////////////////////////////////////////////////////////////////////////
@@ -343,7 +321,7 @@ public class Packages
                 {
                     new NuSpecContent { Source = "Avalonia.iOS.dll", Target = "lib/Xamarin.iOS10" }
                 },
-                BasePath = context.Directory("./src/iOS/Avalonia.iOS/bin/" + parameters.DirSuffixIOS),
+                BasePath = context.Directory("./src/iOS/Avalonia.iOS/bin/" + parameters.DirSuffix + "/xamarin.ios10/"),
                 OutputDirectory = parameters.NugetRoot
             }
         };
@@ -495,7 +473,7 @@ public class Packages
             },
             Files = new []
             {
-                new NuSpecContent { Source = "Avalonia.Win32.Interop/bin/" + parameters.DirSuffix + "/Avalonia.Win32.Interop.dll", Target = "lib/net45" }
+                new NuSpecContent { Source = "Avalonia.Win32.Interop/bin/" + parameters.DirSuffix + "/net461/Avalonia.Win32.Interop.dll", Target = "lib/net461" }
             },
             BasePath = context.Directory("./src/Windows"),
             OutputDirectory = parameters.NugetRoot
@@ -506,7 +484,7 @@ public class Packages
         NuspecNuGetSettings.AddRange(nuspecNuGetSettingsCore);
         NuspecNuGetSettings.AddRange(nuspecNuGetSettingsDesktop);
 
-        if (!parameters.IsPlatformNetCoreOnly) {
+        if (parameters.IsRunningOnWindows) {
             NuspecNuGetSettings.Add(nuspecNuGetSettingInterop);
             NuspecNuGetSettings.AddRange(nuspecNuGetSettingsMobile);
         }

+ 12 - 19
parameters.cake

@@ -1,18 +1,15 @@
+using System.Xml.Linq;
+using System.Linq;
+
 public class Parameters
 {
-    public string Platform { get; private set; }
     public string Configuration { get; private set; }
     public bool SkipTests { get; private set; }
     public string MainRepo { get; private set; }
     public string MasterBranch { get; private set; }
-    public string AssemblyInfoPath { get; private set; }
     public string ReleasePlatform { get; private set; }
     public string ReleaseConfiguration { get; private set; }
     public string MSBuildSolution { get; private set; }
-    public bool IsPlatformAnyCPU { get; private set; }
-    public bool IsPlatformX86 { get; private set; }
-    public bool IsPlatformX64 { get; private set; }
-    public bool IsPlatformNetCoreOnly { get; private set; }
     public bool IsLocalBuild { get; private set; }
     public bool IsRunningOnUnix { get; private set; }
     public bool IsRunningOnWindows { get; private set; }
@@ -30,7 +27,6 @@ public class Parameters
     public DirectoryPath ZipRoot { get; private set; }
     public DirectoryPath BinRoot { get; private set; }
     public string DirSuffix { get; private set; }
-    public string DirSuffixIOS { get; private set; }
     public DirectoryPathCollection BuildDirs { get; private set; }
     public string FileZipSuffix { get; private set; }
     public FilePath ZipCoreArtifacts { get; private set; }
@@ -43,23 +39,16 @@ public class Parameters
         var buildSystem = context.BuildSystem();
 
         // ARGUMENTS
-        Platform = context.Argument("platform", "Any CPU");
         Configuration = context.Argument("configuration", "Release");
         SkipTests = context.HasArgument("skip-tests");
 
         // CONFIGURATION
         MainRepo = "AvaloniaUI/Avalonia";
         MasterBranch = "master";
-        AssemblyInfoPath = context.File("./src/Shared/SharedAssemblyInfo.cs");
-        ReleasePlatform = "Any CPU";
         ReleaseConfiguration = "Release";
-        MSBuildSolution = "./Avalonia.sln";
+        MSBuildSolution = "./dirs.proj";
 
         // PARAMETERS
-        IsPlatformAnyCPU = StringComparer.OrdinalIgnoreCase.Equals(Platform, "Any CPU");
-        IsPlatformX86 = StringComparer.OrdinalIgnoreCase.Equals(Platform, "x86");
-        IsPlatformX64 = StringComparer.OrdinalIgnoreCase.Equals(Platform, "x64");
-        IsPlatformNetCoreOnly = StringComparer.OrdinalIgnoreCase.Equals(Platform, "NetCoreOnly");
         IsLocalBuild = buildSystem.IsLocalBuild;
         IsRunningOnUnix = context.IsRunningOnUnix();
         IsRunningOnWindows = context.IsRunningOnWindows();
@@ -69,12 +58,11 @@ public class Parameters
         IsMasterBranch = StringComparer.OrdinalIgnoreCase.Equals(MasterBranch, buildSystem.AppVeyor.Environment.Repository.Branch);
         IsTagged = buildSystem.AppVeyor.Environment.Repository.Tag.IsTag 
                 && !string.IsNullOrWhiteSpace(buildSystem.AppVeyor.Environment.Repository.Tag.Name);
-        IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleasePlatform, Platform) 
-                    && StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration);
+        IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration);
         IsMyGetRelease = !IsTagged && IsReleasable;
 
         // VERSION
-        Version = context.Argument("force-nuget-version", context.ParseAssemblyInfo(AssemblyInfoPath).AssemblyVersion);
+        Version = context.Argument("force-nuget-version", GetVersion());
 
         if (IsRunningOnAppVeyor)
         {
@@ -105,11 +93,16 @@ public class Parameters
         BinRoot = ArtifactsDir.Combine("bin");
         BuildDirs = context.GetDirectories("**/bin") + context.GetDirectories("**/obj");
         DirSuffix = Configuration;
-        DirSuffixIOS = "iPhone" + "/" + Configuration;
         FileZipSuffix = Version + ".zip";
         ZipCoreArtifacts = ZipRoot.CombineWithFilePath("Avalonia-" + FileZipSuffix);
         ZipNuGetArtifacts = ZipRoot.CombineWithFilePath("Avalonia-NuGet-" + FileZipSuffix);
         ZipSourceControlCatalogDesktopDirs = (DirectoryPath)context.Directory("./samples/ControlCatalog.Desktop/bin/" + DirSuffix + "/net461");
         ZipTargetControlCatalogDesktopDirs = ZipRoot.CombineWithFilePath("ControlCatalog.Desktop-" + FileZipSuffix);
     }
+
+    private static string GetVersion()
+    {
+        var xdoc = XDocument.Load("./build/SharedVersion.props");
+        return xdoc.Descendants().First(x => x.Name.LocalName == "Version").Value;
+    }
 }

+ 2 - 1
samples/BindingDemo/BindingDemo.csproj

@@ -31,4 +31,5 @@
   <Import Project="..\..\build\Serilog.props" />
   <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\ReactiveUI.props" />
-</Project>
+  <Import Condition="'$(TargetFramework)'=='net461'" Project="..\..\build\NetFX.props" />
+</Project>

+ 2 - 1
samples/ControlCatalog.Android/ControlCatalog.Android.csproj

@@ -154,4 +154,5 @@
   </ItemGroup>
   <Import Project="..\..\build\Rx.props" />
   <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
-</Project>
+  <Import Project="..\..\build\AndroidWorkarounds.props" />
+</Project>

+ 2 - 1
samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj

@@ -18,4 +18,5 @@
 
   <Import Project="..\..\build\SampleApp.props" />
   <Import Project="..\..\build\Serilog.props" />
-</Project>
+  <Import Project="..\..\build\NetFX.props" />
+</Project>

+ 2 - 2
samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj

@@ -174,6 +174,6 @@
       <Name>ControlCatalog</Name>
     </ProjectReference>
   </ItemGroup>
-  <Import Project="..\..\build\Sprache.props" />
   <Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
-</Project>
+  <Import Project="..\..\build\iOSWorkarounds.props" />
+</Project>

+ 2 - 1
samples/RenderDemo/RenderDemo.csproj

@@ -31,4 +31,5 @@
   <Import Project="..\..\build\Serilog.props" />
   <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\ReactiveUI.props" />
-</Project>
+  <Import Condition="'$(TargetFramework)'=='net461'" Project="..\..\build\NetFX.props" />
+</Project>

+ 2 - 1
samples/VirtualizationDemo/VirtualizationDemo.csproj

@@ -31,4 +31,5 @@
   <Import Project="..\..\build\Serilog.props" />
   <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\ReactiveUI.props" />
-</Project>
+  <Import Condition="'$(TargetFramework)'=='net461'" Project="..\..\build\NetFX.props" />
+</Project>

+ 14 - 130
src/Android/Avalonia.Android/Avalonia.Android.csproj

@@ -1,136 +1,20 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project Sdk="MSBuild.Sdk.Extras">
   <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <ProductVersion>8.0.30703</ProductVersion>
-    <SchemaVersion>2.0</SchemaVersion>
-    <ProjectGuid>{7B92AF71-6287-4693-9DCB-BD5B6E927E23}</ProjectGuid>
-    <ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
-    <OutputType>Library</OutputType>
-    <AppDesignerFolder>Properties</AppDesignerFolder>
-    <RootNamespace>Avalonia.Android</RootNamespace>
-    <AssemblyName>Avalonia.Android</AssemblyName>
-    <FileAlignment>512</FileAlignment>
-    <AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
-    <GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
-    <AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk>
-    <TargetFrameworkVersion>v4.4</TargetFrameworkVersion>
+    <TargetFramework>monoandroid44</TargetFramework>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>false</Optimize>
-    <OutputPath>bin\Debug\</OutputPath>
-    <DefineConstants>DEBUG;TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <DebugType>pdbonly</DebugType>
-    <Optimize>true</Optimize>
-    <OutputPath>bin\Release\</OutputPath>
-    <DefineConstants>TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-  </PropertyGroup>
-  <ItemGroup>
-    <Reference Include="Mono.Android" />
-    <Reference Include="mscorlib" />
-    <Reference Include="System" />
-    <Reference Include="System.Collections" />
-    <Reference Include="System.Core" />
-    <Reference Include="System.IO" />
-    <Reference Include="System.Reflection" />
-    <Reference Include="System.ObjectModel" />
-    <Reference Include="System.Threading.Tasks" />
-    <Reference Include="System.Runtime" />
-    <Reference Include="System.Xml.Linq" />
-    <Reference Include="System.Xml" />
-  </ItemGroup>
-  <ItemGroup>
-    <Compile Include="ActivityTracker.cs" />
-    <Compile Include="AndroidPlatform.cs" />
-    <Compile Include="AndroidThreadingInterface.cs" />
-    <Compile Include="AppBuilder.cs" />
-    <Compile Include="AvaloniaActivity.cs" />
-    <Compile Include="AvaloniaView.cs" />
-    <Compile Include="PlatformIconLoader.cs" />
-    <Compile Include="Platform\ClipboardImpl.cs" />
-    <Compile Include="CursorFactory.cs" />
-    <Compile Include="Platform\Input\AndroidKeyboardDevice.cs" />
-    <Compile Include="Platform\Input\AndroidMouseDevice.cs" />
-    <Compile Include="Platform\SkiaPlatform\AndroidFramebuffer.cs" />
-    <Compile Include="Platform\SkiaPlatform\InvalidationAwareSurfaceView.cs" />
-    <Compile Include="Platform\SkiaPlatform\PopupImpl.cs" />
-    <Compile Include="Platform\SkiaPlatform\TopLevelImpl.cs" />
-    <Compile Include="Platform\Specific\IAndroidView.cs" />
-    <Compile Include="Platform\Specific\Helpers\AndroidTouchEventsHelper.cs" />
-    <Compile Include="Platform\Specific\Helpers\AndroidKeyboardEventsHelper.cs" />
-    <Compile Include="Resources\Resource.Designer.cs" />
-    <Compile Include="Properties\AssemblyInfo.cs" />
-    <Compile Include="RuntimeInfo.cs" />
-    <Compile Include="SystemDialogImpl.cs" />
-  </ItemGroup>
-  <ItemGroup>
-    <None Include="app.config" />
-    <None Include="Resources\AboutResources.txt" />
-  </ItemGroup>
-  <ItemGroup>
-    <AndroidResource Include="Resources\Values\Strings.xml" />
-  </ItemGroup>
   <ItemGroup>
-    <ProjectReference Include="..\..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj">
-      <Project>{3e53a01a-b331-47f3-b828-4a5717e77a24}</Project>
-      <Name>Avalonia.Markup.Xaml</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Animation\Avalonia.Animation.csproj">
-      <Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
-      <Name>Avalonia.Animation</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj">
-      <Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
-      <Name>Avalonia.Base</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Controls\Avalonia.Controls.csproj">
-      <Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
-      <Name>Avalonia.Controls</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj">
-      <Project>{7062ae20-5dcc-4442-9645-8195bdece63e}</Project>
-      <Name>Avalonia.Diagnostics</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Input\Avalonia.Input.csproj">
-      <Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
-      <Name>Avalonia.Input</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Interactivity\Avalonia.Interactivity.csproj">
-      <Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
-      <Name>Avalonia.Interactivity</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Layout\Avalonia.Layout.csproj">
-      <Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
-      <Name>Avalonia.Layout</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj">
-      <Project>{6417b24e-49c2-4985-8db2-3ab9d898ec91}</Project>
-      <Name>Avalonia.ReactiveUI</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj">
-      <Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
-      <Name>Avalonia.Visuals</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Styling\Avalonia.Styling.csproj">
-      <Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
-      <Name>Avalonia.Styling</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj">
-      <Project>{7d2d3083-71dd-4cc9-8907-39a0d86fb322}</Project>
-      <Name>Avalonia.Skia</Name>
-    </ProjectReference>
+    <ProjectReference Include="..\..\Avalonia.Animation\Avalonia.Animation.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Controls\Avalonia.Controls.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Input\Avalonia.Input.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Layout\Avalonia.Layout.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Styling\Avalonia.Styling.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
+    <ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
   </ItemGroup>
-  <ItemGroup />
   <Import Project="..\..\Shared\PlatformSupport\PlatformSupport.projitems" Label="Shared" />
-  <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
   <Import Project="..\..\..\build\Rx.props" />
-</Project>
+  <Import Project="..\..\..\build\AndroidWorkarounds.props" />
+</Project>

+ 0 - 30
src/Android/Avalonia.Android/Properties/AssemblyInfo.cs

@@ -1,30 +0,0 @@
-using System.Reflection;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following 
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-
-[assembly: AssemblyTitle("Avalonia.Android")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("Avalonia.Android")]
-[assembly: AssemblyCopyright("Copyright ©  2015")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-[assembly: ComVisible(false)]
-
-// Version information for an assembly consists of the following four values:
-//
-//      Major Version
-//      Minor Version 
-//      Build Number
-//      Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers 
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]

+ 3 - 2
src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj

@@ -150,6 +150,7 @@
   </ItemGroup>
   <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
   <Import Project="..\..\..\build\Serilog.props" />
-  <Import Project="..\..\..\build\Sprache.props" />
   <Import Project="..\..\..\build\Rx.props" />
-</Project>
+  <Import Project="..\..\..\build\System.Memory.props" />
+  <Import Project="..\..\..\build\AndroidWorkarounds.props" />
+</Project>

+ 2 - 1
src/Avalonia.Base/Avalonia.Base.csproj

@@ -8,4 +8,5 @@
   <Import Project="..\..\build\Binding.props" />
   <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\JetBrains.Annotations.props" />
-</Project>
+  <Import Project="..\..\build\System.Memory.props" />
+</Project>

+ 49 - 0
src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs

@@ -0,0 +1,49 @@
+using System;
+using System.Globalization;
+
+namespace Avalonia.Data.Converters
+{
+    /// <summary>
+    /// A value converter which calls <see cref="string.Format(string, object)"/>
+    /// </summary>
+    public class StringFormatValueConverter : IValueConverter
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="StringFormatValueConverter"/> class.
+        /// </summary>
+        /// <param name="format">The format string.</param>
+        /// <param name="inner">
+        /// An optional inner converter to be called before the format takes place.
+        /// </param>
+        public StringFormatValueConverter(string format, IValueConverter inner)
+        {
+            Contract.Requires<ArgumentNullException>(format != null);
+
+            Format = format;
+            Inner = inner;
+        }
+
+        /// <summary>
+        /// Gets an inner value converter which will be called before the string format takes place.
+        /// </summary>
+        public IValueConverter Inner { get; }
+
+        /// <summary>
+        /// Gets the format string.
+        /// </summary>
+        public string Format { get; }
+
+        /// <inheritdoc/>
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            value = Inner?.Convert(value, targetType, parameter, culture) ?? value;
+            return string.Format(culture, Format, value);
+        }
+
+        /// <inheritdoc/>
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotSupportedException("Two way bindings are not supported with a string format");
+        }
+    }
+}

+ 1 - 0
src/Avalonia.Base/Data/Core/ExpressionParseException.cs

@@ -17,6 +17,7 @@ namespace Avalonia.Data.Core
         /// </summary>
         /// <param name="column">The column position of the error.</param>
         /// <param name="message">The exception message.</param>
+        /// <param name="innerException">The exception that caused the parsing failure.</param>
         public ExpressionParseException(int column, string message, Exception innerException = null)
             : base(message, innerException)
         {

+ 10 - 3
src/Avalonia.Base/Threading/Dispatcher.cs

@@ -69,7 +69,7 @@ namespace Avalonia.Threading
         /// </summary>
         public void RunJobs()
         {
-            _jobRunner?.RunJobs(null);
+            _jobRunner.RunJobs(null);
         }
 
         /// <summary>
@@ -82,14 +82,21 @@ namespace Avalonia.Threading
         public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
         {
             Contract.Requires<ArgumentNullException>(action != null);
-            return _jobRunner?.InvokeAsync(action, priority);
+            return _jobRunner.InvokeAsync(action, priority);
+        }
+        
+        /// <inheritdoc/>
+        public Task<TResult> InvokeAsync<TResult>(Func<TResult> function, DispatcherPriority priority = DispatcherPriority.Normal)
+        {
+            Contract.Requires<ArgumentNullException>(function != null);
+            return _jobRunner.InvokeAsync(function, priority);
         }
 
         /// <inheritdoc/>
         public void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
         {
             Contract.Requires<ArgumentNullException>(action != null);
-            _jobRunner?.Post(action, priority);
+            _jobRunner.Post(action, priority);
         }
 
         /// <summary>

+ 8 - 3
src/Avalonia.Base/Threading/IDispatcher.cs

@@ -28,12 +28,17 @@ namespace Avalonia.Threading
         void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal);
 
         /// <summary>
-        /// Post action that will be invoked on main thread
+        /// Posts an action that will be invoked on the dispatcher thread.
         /// </summary>
         /// <param name="action">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.
-        // Remove this and rename InvokeTaskAsync as InvokeAsync. See #816.
         Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal);
+
+        /// <summary>
+        /// Posts a function that will be invoked on the dispatcher thread.
+        /// </summary>
+        /// <param name="function">The method.</param>
+        /// <param name="priority">The priority with which to invoke the method.</param>
+        Task<TResult> InvokeAsync<TResult>(Func<TResult> function, DispatcherPriority priority = DispatcherPriority.Normal);
     }
 }

+ 120 - 48
src/Avalonia.Base/Threading/JobRunner.cs

@@ -14,32 +14,16 @@ namespace Avalonia.Threading
     /// </summary>
     internal class JobRunner
     {
-
-
         private IPlatformThreadingInterface _platform;
 
-        private Queue<Job>[] _queues = Enumerable.Range(0, (int) DispatcherPriority.MaxValue + 1)
-            .Select(_ => new Queue<Job>()).ToArray();
+        private readonly Queue<IJob>[] _queues = Enumerable.Range(0, (int) DispatcherPriority.MaxValue + 1)
+            .Select(_ => new Queue<IJob>()).ToArray();
 
         public JobRunner(IPlatformThreadingInterface platform)
         {
             _platform = platform;
         }
 
-        Job GetNextJob(DispatcherPriority minimumPriority)
-        {
-            for (int c = (int) DispatcherPriority.MaxValue; c >= (int) minimumPriority; c--)
-            {
-                var q = _queues[c];
-                lock (q)
-                {
-                    if (q.Count > 0)
-                        return q.Dequeue();
-                }
-            }
-            return null;
-        }
-
         /// <summary>
         /// Runs continuations pushed on the loop.
         /// </summary>
@@ -52,24 +36,8 @@ namespace Avalonia.Threading
                 var job = GetNextJob(minimumPriority);
                 if (job == null)
                     return;
-                
 
-                if (job.TaskCompletionSource == null)
-                {
-                    job.Action();
-                }
-                else
-                {
-                    try
-                    {
-                        job.Action();
-                        job.TaskCompletionSource.SetResult(null);
-                    }
-                    catch (Exception e)
-                    {
-                        job.TaskCompletionSource.SetException(e);
-                    }
-                }
+                job.Run();
             }
         }
 
@@ -83,7 +51,20 @@ namespace Avalonia.Threading
         {
             var job = new Job(action, priority, false);
             AddJob(job);
-            return job.TaskCompletionSource.Task;
+            return job.Task;
+        }
+
+        /// <summary>
+        /// Invokes a method on the main loop.
+        /// </summary>
+        /// <param name="function">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>
+        public Task<TResult> InvokeAsync<TResult>(Func<TResult> function, DispatcherPriority priority)
+        {
+            var job = new Job<TResult>(function, priority);
+            AddJob(job);
+            return job.Task;
         }
 
         /// <summary>
@@ -105,9 +86,9 @@ namespace Avalonia.Threading
             _platform = AvaloniaLocator.Current.GetService<IPlatformThreadingInterface>();
         }
 
-        private void AddJob(Job job)
+        private void AddJob(IJob job)
         {
-            var needWake = false;
+            bool needWake;
             var queue = _queues[(int) job.Priority];
             lock (queue)
             {
@@ -118,38 +99,129 @@ namespace Avalonia.Threading
                 _platform?.Signal(job.Priority);
         }
 
+        private IJob GetNextJob(DispatcherPriority minimumPriority)
+        {
+            for (int c = (int) DispatcherPriority.MaxValue; c >= (int) minimumPriority; c--)
+            {
+                var q = _queues[c];
+                lock (q)
+                {
+                    if (q.Count > 0)
+                        return q.Dequeue();
+                }
+            }
+            return null;
+        }
+        
+        private interface IJob
+        {
+            /// <summary>
+            /// Gets the job priority.
+            /// </summary>
+            DispatcherPriority Priority { get; }
+            
+            /// <summary>
+            /// Runs the job.
+            /// </summary>
+            void Run();
+        }
+
         /// <summary>
         /// A job to run.
         /// </summary>
-        private class Job
+        private sealed class Job : IJob
         {
+            /// <summary>
+            /// The method to call.
+            /// </summary>
+            private readonly Action _action;
+            /// <summary>
+            /// The task completion source.
+            /// </summary>
+            private readonly TaskCompletionSource<object> _taskCompletionSource;
+
             /// <summary>
             /// Initializes a new instance of the <see cref="Job"/> class.
             /// </summary>
             /// <param name="action">The method to call.</param>
             /// <param name="priority">The job priority.</param>
-            /// <param name="throwOnUiThread">Do not wrap excepption in TaskCompletionSource</param>
+            /// <param name="throwOnUiThread">Do not wrap exception in TaskCompletionSource</param>
             public Job(Action action, DispatcherPriority priority, bool throwOnUiThread)
             {
-                Action = action;
+                _action = action;
                 Priority = priority;
-                TaskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource<object>();
+                _taskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource<object>();
             }
 
+            /// <inheritdoc/>
+            public DispatcherPriority Priority { get; }
+
             /// <summary>
-            /// Gets the method to call.
+            /// The task.
             /// </summary>
-            public Action Action { get; }
+            public Task Task => _taskCompletionSource?.Task;
+            
+            /// <inheritdoc/>
+            void IJob.Run()
+            {
+                if (_taskCompletionSource == null)
+                {
+                    _action();
+                    return;
+                }
+                try
+                {
+                    _action();
+                    _taskCompletionSource.SetResult(null);
+                }
+                catch (Exception e)
+                {
+                    _taskCompletionSource.SetException(e);
+                }
+            }
+        }
+        
+        /// <summary>
+        /// A job to run.
+        /// </summary>
+        private sealed class Job<TResult> : IJob
+        {
+            private readonly Func<TResult> _function;
+            private readonly TaskCompletionSource<TResult> _taskCompletionSource;
 
             /// <summary>
-            /// Gets the job priority.
+            /// Initializes a new instance of the <see cref="Job"/> class.
             /// </summary>
-            public DispatcherPriority Priority { get; }
+            /// <param name="function">The method to call.</param>
+            /// <param name="priority">The job priority.</param>
+            public Job(Func<TResult> function, DispatcherPriority priority)
+            {
+                _function = function;
+                Priority = priority;
+                _taskCompletionSource = new TaskCompletionSource<TResult>();
+            }
 
+            /// <inheritdoc/>
+            public DispatcherPriority Priority { get; }
+            
             /// <summary>
-            /// Gets the task completion source.
+            /// The task.
             /// </summary>
-            public TaskCompletionSource<object> TaskCompletionSource { get; }
+            public Task<TResult> Task => _taskCompletionSource.Task;
+
+            /// <inheritdoc/>
+            void IJob.Run()
+            {
+                try
+                {
+                    var result = _function();
+                    _taskCompletionSource.SetResult(result);
+                }
+                catch (Exception e)
+                {
+                    _taskCompletionSource.SetException(e);
+                }
+            }
         }
     }
 }

+ 53 - 12
src/Avalonia.Base/Utilities/CharacterReader.cs

@@ -2,30 +2,37 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using System.Globalization;
+using System.Text;
 
 namespace Avalonia.Utilities
 {
-    public class CharacterReader
+    public ref struct CharacterReader
     {
-        private readonly string _s;
-        private int _i;
+        private ReadOnlySpan<char> _s;
 
-        public CharacterReader(string s)
+        public CharacterReader(ReadOnlySpan<char> s)
+            :this()
         {
             _s = s;
         }
 
-        public bool End => _i == _s.Length;
-        public char Peek => _s[_i];
-        public int Position => _i;
-        public char Take() => _s[_i++];
+        public bool End => _s.IsEmpty;
+        public char Peek => _s[0];
+        public int Position { get; private set; }
+        public char Take()
+        {
+            Position++;
+            char taken = _s[0];
+            _s = _s.Slice(1);
+            return taken;
+        }
 
         public void SkipWhitespace()
         {
-            while (!End && char.IsWhiteSpace(Peek))
-            {
-                Take();
-            }
+            var trimmed = _s.TrimStart();
+            Position += _s.Length - trimmed.Length;
+            _s = trimmed;
         }
 
         public bool TakeIf(char c)
@@ -40,5 +47,39 @@ namespace Avalonia.Utilities
                 return false;
             }
         }
+
+        public bool TakeIf(Func<char, bool> condition)
+        {
+            if (condition(Peek))
+            {
+                Take();
+                return true;
+            }
+            return false;
+        }
+
+        public ReadOnlySpan<char> TakeUntil(char c)
+        {
+            int len;
+            for (len = 0; len < _s.Length && _s[len] != c; len++)
+            {
+            }
+            var span = _s.Slice(0, len);
+            _s = _s.Slice(len);
+            Position += len;
+            return span;
+        }
+
+        public ReadOnlySpan<char> TakeWhile(Func<char, bool> condition)
+        {
+            int len;
+            for (len = 0; len < _s.Length && condition(_s[len]); len++)
+            {
+            }
+            var span = _s.Slice(0, len);
+            _s = _s.Slice(len);
+            Position += len;
+            return span;
+        }
     }
 }

+ 6 - 11
src/Avalonia.Base/Utilities/IdentifierParser.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.
 
+using System;
+using System.Collections.Generic;
 using System.Globalization;
 using System.Text;
 
@@ -8,22 +10,15 @@ namespace Avalonia.Utilities
 {
     public static class IdentifierParser
     {
-        public static string Parse(CharacterReader r)
+        public static ReadOnlySpan<char> ParseIdentifier(this ref CharacterReader r)
         {
             if (IsValidIdentifierStart(r.Peek))
             {
-                var result = new StringBuilder();
-
-                while (!r.End && IsValidIdentifierChar(r.Peek))
-                {
-                    result.Append(r.Take());
-                }
-
-                return result.ToString();
+                return r.TakeWhile(IsValidIdentifierChar);
             }
             else
             {
-                return null;
+                return ReadOnlySpan<char>.Empty;
             }
         }
 

+ 1 - 0
src/Avalonia.Controls/Window.cs

@@ -49,6 +49,7 @@ namespace Avalonia.Controls
     /// </summary>
     public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot, INameScope
     {
+        /// <summary>
         /// Defines the <see cref="SizeToContent"/> property.
         /// </summary>
         public static readonly StyledProperty<SizeToContent> SizeToContentProperty =

+ 22 - 4
src/Avalonia.Input/InputElement.cs

@@ -162,8 +162,8 @@ namespace Avalonia.Input
             KeyDownEvent.AddClassHandler<InputElement>(x => x.OnKeyDown);
             KeyUpEvent.AddClassHandler<InputElement>(x => x.OnKeyUp);
             TextInputEvent.AddClassHandler<InputElement>(x => x.OnTextInput);
-            PointerEnterEvent.AddClassHandler<InputElement>(x => x.OnPointerEnter);
-            PointerLeaveEvent.AddClassHandler<InputElement>(x => x.OnPointerLeave);
+            PointerEnterEvent.AddClassHandler<InputElement>(x => x.OnPointerEnterCore);
+            PointerLeaveEvent.AddClassHandler<InputElement>(x => x.OnPointerLeaveCore);
             PointerMovedEvent.AddClassHandler<InputElement>(x => x.OnPointerMoved);
             PointerPressedEvent.AddClassHandler<InputElement>(x => x.OnPointerPressed);
             PointerReleasedEvent.AddClassHandler<InputElement>(x => x.OnPointerReleased);
@@ -445,7 +445,6 @@ namespace Avalonia.Input
         /// <param name="e">The event args.</param>
         protected virtual void OnPointerEnter(PointerEventArgs e)
         {
-            IsPointerOver = true;
         }
 
         /// <summary>
@@ -454,7 +453,6 @@ namespace Avalonia.Input
         /// <param name="e">The event args.</param>
         protected virtual void OnPointerLeave(PointerEventArgs e)
         {
-            IsPointerOver = false;
         }
 
         /// <summary>
@@ -494,6 +492,26 @@ namespace Avalonia.Input
             ((InputElement)e.Sender).UpdateIsEnabledCore();
         }
 
+        /// <summary>
+        /// Called before the <see cref="PointerEnter"/> event occurs.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        private void OnPointerEnterCore(PointerEventArgs e)
+        {
+            IsPointerOver = true;
+            OnPointerEnter(e);
+        }
+
+        /// <summary>
+        /// Called before the <see cref="PointerLeave"/> event occurs.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        private void OnPointerLeaveCore(PointerEventArgs e)
+        {
+            IsPointerOver = false;
+            OnPointerLeave(e);
+        }
+
         /// <summary>
         /// Updates the <see cref="IsEnabledCore"/> property value.
         /// </summary>

+ 13 - 11
src/Avalonia.Input/MouseDevice.cs

@@ -346,12 +346,7 @@ namespace Avalonia.Input
 
             IInputElement branch = null;
 
-            var e = new PointerEventArgs
-            {
-                RoutedEvent = InputElement.PointerEnterEvent,
-                Device = device,
-            };
-
+            var e = new PointerEventArgs { Device = device, };
             var el = element;
 
             while (el != null)
@@ -361,9 +356,6 @@ namespace Avalonia.Input
                     branch = el;
                     break;
                 }
-
-                e.Source = el;
-                el.RaiseEvent(e);
                 el = (IInputElement)el.VisualParent;
             }
 
@@ -373,11 +365,21 @@ namespace Avalonia.Input
             while (el != null && el != branch)
             {
                 e.Source = el;
+                e.Handled = false;
                 el.RaiseEvent(e);
                 el = (IInputElement)el.VisualParent;
             }
 
-            root.PointerOverElement = element;
+            el = root.PointerOverElement = element;
+            e.RoutedEvent = InputElement.PointerEnterEvent;
+
+            while (el != null && el != branch)
+            {
+                e.Source = el;
+                e.Handled = false;
+                el.RaiseEvent(e);
+                el = (IInputElement)el.VisualParent;
+            }
         }
     }
-}
+}

+ 0 - 1
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@@ -77,6 +77,5 @@
     <ProjectReference Include="..\..\Avalonia.Styling\Avalonia.Styling.csproj" />
     <ProjectReference Include="..\Avalonia.Markup\Avalonia.Markup.csproj" />
   </ItemGroup>
-  <Import Project="..\..\..\build\Markup.props" />
   <Import Project="..\..\..\build\Rx.props" />
 </Project>

+ 1 - 2
src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs

@@ -26,8 +26,7 @@ namespace Avalonia.Markup.Xaml.Converters
         {
             var registry = AvaloniaPropertyRegistry.Instance;
             var parser = new PropertyParser();
-            var reader = new CharacterReader((string)value);
-            var (ns, owner, propertyName) = parser.Parse(reader);
+            var (ns, owner, propertyName) = parser.Parse(new CharacterReader(((string)value).AsSpan()));
             var ownerType = TryResolveOwnerByName(context, ns, owner);
             var targetType = context.GetFirstAmbientValue<ControlTemplate>()?.TargetType ??
                 context.GetFirstAmbientValue<Style>()?.Selector?.TargetType ??

+ 4 - 1
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs

@@ -43,8 +43,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
                 Path = Path,
                 Priority = Priority,
                 Source = Source,
+                StringFormat = StringFormat,
                 RelativeSource = RelativeSource,
-                DefaultAnchor = new WeakReference(GetDefaultAnchor((ITypeDescriptorContext)serviceProvider))
+                DefaultAnchor = new WeakReference(GetDefaultAnchor(descriptorContext))
             };
         }
 
@@ -79,6 +80,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
 
         public object Source { get; set; }
 
+        public string StringFormat { get; set; }
+
         public RelativeSource RelativeSource { get; set; }
     }
 }

+ 5 - 5
src/Markup/Avalonia.Markup.Xaml/Parsers/PropertyParser.cs

@@ -22,9 +22,9 @@ namespace Avalonia.Markup.Xaml.Parsers
 
             do
             {
-                var token = IdentifierParser.Parse(r);
+                var token = r.ParseIdentifier();
 
-                if (token == null)
+                if (token.IsEmpty)
                 {
                     if (r.End)
                     {
@@ -47,18 +47,18 @@ namespace Avalonia.Markup.Xaml.Parsers
                 else if (!r.End && r.TakeIf(':'))
                 {
                     ns = ns == null ?
-                        token :
+                        token.ToString() :
                         throw new ExpressionParseException(r.Position, "Unexpected ':'.");
                 }
                 else if (!r.End && r.TakeIf('.'))
                 {
                     owner = owner == null ?
-                        token :
+                        token.ToString() :
                         throw new ExpressionParseException(r.Position, "Unexpected '.'.");
                 }
                 else
                 {
-                    name = token;
+                    name = token.ToString();
                 }
             } while (!r.End);
 

+ 2 - 2
src/Markup/Avalonia.Markup/Avalonia.Markup.csproj

@@ -9,5 +9,5 @@
     <ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
   </ItemGroup>
   <Import Project="..\..\..\build\Rx.props" />
-  <Import Project="..\..\..\build\Sprache.props" />
-</Project>
+  <Import Project="..\..\..\build\System.Memory.props" />
+</Project>

+ 19 - 2
src/Markup/Avalonia.Markup/Data/Binding.cs

@@ -84,6 +84,11 @@ namespace Avalonia.Data
         /// </summary>
         public object Source { get; set; }
 
+        /// <summary>
+        /// Gets or sets the string format.
+        /// </summary>
+        public string StringFormat { get; set; }
+
         public WeakReference DefaultAnchor { get; set; }
 
         /// <summary>
@@ -181,11 +186,23 @@ namespace Avalonia.Data
                 fallback = null;
             }
 
+            var converter = Converter;
+            var targetType = targetProperty?.PropertyType ?? typeof(object);
+
+            // We only respect `StringFormat` if the type of the property we're assigning to will
+            // accept a string. Note that this is slightly different to WPF in that WPF only applies
+            // `StringFormat` for target type `string` (not `object`).
+            if (!string.IsNullOrWhiteSpace(StringFormat) && 
+                (targetType == typeof(string) || targetType == typeof(object)))
+            {
+                converter = new StringFormatValueConverter(StringFormat, converter);
+            }
+
             var subject = new BindingExpression(
                 observer,
-                targetProperty?.PropertyType ?? typeof(object),
+                targetType,
                 fallback,
-                Converter ?? DefaultValueConverter.Instance,
+                converter ?? DefaultValueConverter.Instance,
                 ConverterParameter,
                 Priority);
 

+ 5 - 8
src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs

@@ -11,7 +11,7 @@ namespace Avalonia.Markup.Parsers
 {
     internal static class ArgumentListParser
     {
-        public static IList<string> Parse(CharacterReader r, char open, char close, char delimiter = ',')
+        public static IList<string> ParseArguments(this ref CharacterReader r, char open, char close, char delimiter = ',')
         {
             if (r.Peek == open)
             {
@@ -21,16 +21,13 @@ namespace Avalonia.Markup.Parsers
 
                 while (!r.End)
                 {
-                    var builder = new StringBuilder();
-                    while (!r.End && r.Peek != delimiter && r.Peek != close && !char.IsWhiteSpace(r.Peek))
-                    {
-                        builder.Append(r.Take());
-                    }
-                    if (builder.Length == 0)
+                    var argument = r.TakeWhile(c => c != delimiter && c != close && !char.IsWhiteSpace(c));
+                    if (argument.IsEmpty)
                     {
                         throw new ExpressionParseException(r.Position, "Expected indexer argument.");
                     }
-                    result.Add(builder.ToString());
+
+                    result.Add(argument.ToString());
 
                     r.SkipWhitespace();
 

+ 3 - 3
src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs

@@ -15,10 +15,10 @@ namespace Avalonia.Markup.Parsers
             {
                 return (new EmptyExpressionNode(), default);
             }
-
-            var reader = new CharacterReader(expression);
+            
+            var reader = new CharacterReader(expression.AsSpan());
             var parser = new ExpressionParser(enableValidation, typeResolver);
-            var node = parser.Parse(reader);
+            var node = parser.Parse(ref reader);
 
             if (!reader.End)
             {

+ 80 - 60
src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs

@@ -27,7 +27,7 @@ namespace Avalonia.Markup.Parsers
             _enableValidation = enableValidation;
         }
 
-        public (ExpressionNode Node, SourceMode Mode) Parse(CharacterReader r)
+        public (ExpressionNode Node, SourceMode Mode) Parse(ref CharacterReader r)
         {
             var nodes = new List<ExpressionNode>();
             var state = State.Start;
@@ -38,32 +38,32 @@ namespace Avalonia.Markup.Parsers
                 switch (state)
                 {
                     case State.Start:
-                        state = ParseStart(r, nodes);
+                        state = ParseStart(ref r, nodes);
                         break;
 
                     case State.AfterMember:
-                        state = ParseAfterMember(r, nodes);
+                        state = ParseAfterMember(ref r, nodes);
                         break;
 
                     case State.BeforeMember:
-                        state = ParseBeforeMember(r, nodes);
+                        state = ParseBeforeMember(ref r, nodes);
                         break;
 
                     case State.AttachedProperty:
-                        state = ParseAttachedProperty(r, nodes);
+                        state = ParseAttachedProperty(ref r, nodes);
                         break;
 
                     case State.Indexer:
-                        state = ParseIndexer(r, nodes);
+                        state = ParseIndexer(ref r, nodes);
                         break;
 
                     case State.ElementName:
-                        state = ParseElementName(r, nodes);
+                        state = ParseElementName(ref r, nodes);
                         mode = SourceMode.Control;
                         break;
 
                     case State.RelativeSource:
-                        state = ParseRelativeSource(r, nodes);
+                        state = ParseRelativeSource(ref r, nodes);
                         mode = SourceMode.Control;
                         break;
                 }
@@ -82,36 +82,37 @@ namespace Avalonia.Markup.Parsers
             return (nodes.FirstOrDefault(), mode);
         }
 
-        private State ParseStart(CharacterReader r, IList<ExpressionNode> nodes)
+        private State ParseStart(ref CharacterReader r, IList<ExpressionNode> nodes)
         {
-            if (ParseNot(r))
+            if (ParseNot(ref r))
             {
                 nodes.Add(new LogicalNotNode());
                 return State.Start;
             }
-            else if (ParseSharp(r))
+
+            else if (ParseSharp(ref r))
             {
                 return State.ElementName;
             }
-            else if (ParseDollarSign(r))
+            else if (ParseDollarSign(ref r))
             {
                 return State.RelativeSource;
             }
-            else if (ParseOpenBrace(r))
+            else if (ParseOpenBrace(ref r))
             {
                 return State.AttachedProperty;
             }
-            else if (PeekOpenBracket(r))
+            else if (PeekOpenBracket(ref r))
             {
                 return State.Indexer;
             }
             else
             {
-                var identifier = IdentifierParser.Parse(r);
+                var identifier = r.ParseIdentifier();
 
-                if (identifier != null)
+                if (!identifier.IsEmpty)
                 {
-                    nodes.Add(new PropertyAccessorNode(identifier, _enableValidation));
+                    nodes.Add(new PropertyAccessorNode(identifier.ToString(), _enableValidation));
                     return State.AfterMember;
                 }
             }
@@ -119,18 +120,18 @@ namespace Avalonia.Markup.Parsers
             return State.End;
         }
 
-        private static State ParseAfterMember(CharacterReader r, IList<ExpressionNode> nodes)
+        private static State ParseAfterMember(ref CharacterReader r, IList<ExpressionNode> nodes)
         {
-            if (ParseMemberAccessor(r))
+            if (ParseMemberAccessor(ref r))
             {
                 return State.BeforeMember;
             }
-            else if (ParseStreamOperator(r))
+            else if (ParseStreamOperator(ref r))
             {
                 nodes.Add(new StreamNode());
                 return State.AfterMember;
             }
-            else if (PeekOpenBracket(r))
+            else if (PeekOpenBracket(ref r))
             {
                 return State.Indexer;
             }
@@ -138,19 +139,19 @@ namespace Avalonia.Markup.Parsers
             return State.End;
         }
 
-        private State ParseBeforeMember(CharacterReader r, IList<ExpressionNode> nodes)
+        private State ParseBeforeMember(ref CharacterReader r, IList<ExpressionNode> nodes)
         {
-            if (ParseOpenBrace(r))
+            if (ParseOpenBrace(ref r))
             {
                 return State.AttachedProperty;
             }
             else
             {
-                var identifier = IdentifierParser.Parse(r);
+                var identifier = r.ParseIdentifier();
 
-                if (identifier != null)
+                if (!identifier.IsEmpty)
                 {
-                    nodes.Add(new PropertyAccessorNode(identifier, _enableValidation));
+                    nodes.Add(new PropertyAccessorNode(identifier.ToString(), _enableValidation));
                     return State.AfterMember;
                 }
 
@@ -158,16 +159,16 @@ namespace Avalonia.Markup.Parsers
             }
         }
 
-        private State ParseAttachedProperty(CharacterReader r, List<ExpressionNode> nodes)
+        private State ParseAttachedProperty(ref CharacterReader r, List<ExpressionNode> nodes)
         {
-            var (ns, owner) = ParseTypeName(r);
+            var (ns, owner) = ParseTypeName(ref r);
 
             if (r.End || !r.TakeIf('.'))
             {
                 throw new ExpressionParseException(r.Position, "Invalid attached property name.");
             }
 
-            var name = IdentifierParser.Parse(r);
+            var name = r.ParseIdentifier();
 
             if (r.End || !r.TakeIf(')'))
             {
@@ -179,15 +180,15 @@ namespace Avalonia.Markup.Parsers
                 throw new InvalidOperationException("Cannot parse a binding path with an attached property without a type resolver. Maybe you can use a LINQ Expression binding path instead?");
             }
 
-            var property = AvaloniaPropertyRegistry.Instance.FindRegistered(_typeResolver(ns, owner), name);
+            var property = AvaloniaPropertyRegistry.Instance.FindRegistered(_typeResolver(ns.ToString(), owner.ToString()), name.ToString());
 
             nodes.Add(new AvaloniaPropertyAccessorNode(property, _enableValidation));
             return State.AfterMember;
         }
 
-        private State ParseIndexer(CharacterReader r, List<ExpressionNode> nodes)
+        private State ParseIndexer(ref CharacterReader r, List<ExpressionNode> nodes)
         {
-            var args = ArgumentListParser.Parse(r, '[', ']');
+            var args = r.ParseArguments('[', ']');
 
             if (args.Count == 0)
             {
@@ -197,36 +198,35 @@ namespace Avalonia.Markup.Parsers
             nodes.Add(new StringIndexerNode(args));
             return State.AfterMember;
         }
-        
-        private State ParseElementName(CharacterReader r, List<ExpressionNode> nodes)
+
+        private State ParseElementName(ref CharacterReader r, List<ExpressionNode> nodes)
         {
-            var name = IdentifierParser.Parse(r);
+            var name = r.ParseIdentifier();
 
             if (name == null)
             {
                 throw new ExpressionParseException(r.Position, "Element name expected after '#'.");
             }
 
-            nodes.Add(new ElementNameNode(name));
+            nodes.Add(new ElementNameNode(name.ToString()));
             return State.AfterMember;
         }
 
-
-        private State ParseRelativeSource(CharacterReader r, List<ExpressionNode> nodes)
+        private State ParseRelativeSource(ref CharacterReader r, List<ExpressionNode> nodes)
         {
-            var mode = IdentifierParser.Parse(r);
+            var mode = r.ParseIdentifier();
 
-            if (mode == "self")
+            if (mode.Equals("self".AsSpan(), StringComparison.InvariantCulture))
             {
                 nodes.Add(new SelfNode());
             }
-            else if (mode == "parent")
+            else if (mode.Equals("parent".AsSpan(), StringComparison.InvariantCulture))
             {
                 Type ancestorType = null;
                 var ancestorLevel = 0;
-                if (PeekOpenBracket(r))
+                if (PeekOpenBracket(ref r))
                 {
-                    var args = ArgumentListParser.Parse(r, '[', ']', ';');
+                    var args = r.ParseArguments('[', ']', ';');
                     if (args.Count > 2 || args.Count == 0)
                     {
                         throw new ExpressionParseException(r.Position, "Too many arguments in RelativeSource syntax sugar");
@@ -240,14 +240,16 @@ namespace Avalonia.Markup.Parsers
                         }
                         else
                         {
-                            var typeName = ParseTypeName(new CharacterReader(args[0]));
-                            ancestorType = _typeResolver(typeName.ns, typeName.typeName);
+                            var reader = new CharacterReader(args[0].AsSpan());
+                            var typeName = ParseTypeName(ref reader);
+                            ancestorType = _typeResolver(typeName.Namespace.ToString(), typeName.Type.ToString());
                         }
                     }
                     else
                     {
-                        var typeName = ParseTypeName(new CharacterReader(args[0]));
-                        ancestorType = _typeResolver(typeName.ns, typeName.typeName);
+                        var reader = new CharacterReader(args[0].AsSpan());
+                        var typeName = ParseTypeName(ref reader);
+                        ancestorType = _typeResolver(typeName.Namespace.ToString(), typeName.Type.ToString());
                         ancestorLevel = int.Parse(args[1]);
                     }
                 }
@@ -261,56 +263,56 @@ namespace Avalonia.Markup.Parsers
             return State.AfterMember;
         }
         
-        private static (string ns, string typeName) ParseTypeName(CharacterReader r)
+        private static TypeName ParseTypeName(ref CharacterReader r)
         {
-            string ns, typeName;
-            ns = string.Empty;
-            var typeNameOrNamespace = IdentifierParser.Parse(r);
+            ReadOnlySpan<char> ns, typeName;
+            ns = ReadOnlySpan<char>.Empty;
+            var typeNameOrNamespace = r.ParseIdentifier();
 
             if (!r.End && r.TakeIf(':'))
             {
                 ns = typeNameOrNamespace;
-                typeName = IdentifierParser.Parse(r);
+                typeName = r.ParseIdentifier();
             }
             else
             {
                 typeName = typeNameOrNamespace;
             }
 
-            return (ns, typeName);
+            return new TypeName(ns, typeName);
         }
       
-        private static bool ParseNot(CharacterReader r)
+        private static bool ParseNot(ref CharacterReader r)
         {
             return !r.End && r.TakeIf('!');
         }
 
-        private static bool ParseMemberAccessor(CharacterReader r)
+        private static bool ParseMemberAccessor(ref CharacterReader r)
         {
             return !r.End && r.TakeIf('.');
         }
 
-        private static bool ParseOpenBrace(CharacterReader r)
+        private static bool ParseOpenBrace(ref CharacterReader r)
         {
             return !r.End && r.TakeIf('(');
         }
 
-        private static bool PeekOpenBracket(CharacterReader r)
+        private static bool PeekOpenBracket(ref CharacterReader r)
         {
             return !r.End && r.Peek == '[';
         }
 
-        private static bool ParseStreamOperator(CharacterReader r)
+        private static bool ParseStreamOperator(ref CharacterReader r)
         {
             return !r.End && r.TakeIf('^');
         }
 
-        private static bool ParseDollarSign(CharacterReader r)
+        private static bool ParseDollarSign(ref CharacterReader r)
         {
             return !r.End && r.TakeIf('$');
         }
 
-        private static bool ParseSharp(CharacterReader r)
+        private static bool ParseSharp(ref CharacterReader r)
         {
             return !r.End && r.TakeIf('#');
         }
@@ -326,5 +328,23 @@ namespace Avalonia.Markup.Parsers
             Indexer,
             End,
         }
+
+        private readonly ref struct TypeName
+        {
+            public TypeName(ReadOnlySpan<char> ns, ReadOnlySpan<char> typeName)
+            {
+                Namespace = ns;
+                Type = typeName;
+            }
+
+            public readonly ReadOnlySpan<char> Namespace;
+            public readonly ReadOnlySpan<char> Type;
+
+            public void Deconstruct(out ReadOnlySpan<char> ns, out ReadOnlySpan<char> typeName)
+            {
+                ns = Namespace;
+                typeName = Type;
+            }
+        }
     }
 }

+ 283 - 112
src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs

@@ -1,9 +1,11 @@
 // 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.Collections.Generic;
 using System.Globalization;
-using Sprache;
+using Avalonia.Data.Core;
+using Avalonia.Utilities;
 
 // Don't need to override GetHashCode as the ISyntax objects will not be stored in a hash; the 
 // only reason they have overridden Equals methods is for unit testing.
@@ -11,123 +13,292 @@ using Sprache;
 
 namespace Avalonia.Markup.Parsers
 {
-    internal class SelectorGrammar
+    internal static class SelectorGrammar
     {
-        public static readonly Parser<char> CombiningCharacter = Parse.Char(
-            c =>
-            {
-                var cat = CharUnicodeInfo.GetUnicodeCategory(c);
-                return cat == UnicodeCategory.NonSpacingMark ||
-                       cat == UnicodeCategory.SpacingCombiningMark;
-            },
-            "Connecting Character");
-
-        public static readonly Parser<char> ConnectingCharacter = Parse.Char(
-            c => CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.ConnectorPunctuation,
-            "Connecting Character");
-
-        public static readonly Parser<char> FormattingCharacter = Parse.Char(
-            c => CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.Format,
-            "Connecting Character");
-
-        public static readonly Parser<char> IdentifierStart = Parse.Letter.Or(Parse.Char('_'));
-
-        public static readonly Parser<char> IdentifierChar = Parse
-            .LetterOrDigit
-            .Or(ConnectingCharacter)
-            .Or(CombiningCharacter)
-            .Or(FormattingCharacter);
-
-        public static readonly Parser<string> Identifier =
-            from start in IdentifierStart.Once().Text()
-            from @char in IdentifierChar.Many().Text()
-            select start + @char;
-
-        public static readonly Parser<string> Namespace =
-            from ns in Parse.Letter.Many().Text()
-            from bar in Parse.Char('|')
-            select ns;
-
-        public static readonly Parser<OfTypeSyntax> OfType =
-            from ns in Namespace.Optional()
-            from identifier in Identifier
-            select new OfTypeSyntax
-            {
-                TypeName = identifier,
-                Xmlns = ns.GetOrDefault(),
-            };
-
-        public static readonly Parser<NameSyntax> Name =
-            from hash in Parse.Char('#')
-            from identifier in Identifier
-            select new NameSyntax { Name = identifier };
-
-        public static readonly Parser<char> ClassStart = Parse.Char('_').Or(Parse.Letter);
-
-        public static readonly Parser<char> ClassChar = ClassStart.Or(Parse.Numeric);
-
-        public static readonly Parser<string> ClassIdentifier =
-            from start in ClassStart.Once().Text()
-            from @char in ClassChar.Many().Text()
-            select start + @char;
-
-        public static readonly Parser<ClassSyntax> StandardClass =
-            from dot in Parse.Char('.').Once()
-            from identifier in ClassIdentifier
-            select new ClassSyntax { Class = identifier };
-
-        public static readonly Parser<ClassSyntax> Pseduoclass =
-            from colon in Parse.Char(':').Once()
-            from identifier in ClassIdentifier
-            select new ClassSyntax { Class = ':' + identifier };
-
-        public static readonly Parser<ClassSyntax> Class = StandardClass.Or(Pseduoclass);
-
-        public static readonly Parser<PropertySyntax> Property =
-            from open in Parse.Char('[').Once()
-            from identifier in Identifier
-            from eq in Parse.Char('=').Once()
-            from value in Parse.CharExcept(']').Many().Text()
-            from close in Parse.Char(']').Once()
-            select new PropertySyntax { Property = identifier, Value = value };
-
-        public static readonly Parser<ChildSyntax> Child = Parse.Char('>').Token().Return(new ChildSyntax());
-
-        public static readonly Parser<DescendantSyntax> Descendant =
-            from child in Parse.WhiteSpace.Many()
-            select new DescendantSyntax();
-
-        public static readonly Parser<TemplateSyntax> Template =
-            from template in Parse.String("/template/").Token()
-            select new TemplateSyntax();
-
-        public static readonly Parser<IsSyntax> Is =
-            from function in Parse.String(":is(")
-            from type in OfType
-            from close in Parse.Char(')')
-            select new IsSyntax { TypeName = type.TypeName, Xmlns = type.Xmlns };
-
-        public static readonly Parser<ISyntax> SingleSelector =
-            OfType
-            .Or<ISyntax>(Is)
-            .Or<ISyntax>(Name)
-            .Or<ISyntax>(Class)
-            .Or<ISyntax>(Property)
-            .Or<ISyntax>(Child)
-            .Or<ISyntax>(Template)
-            .Or<ISyntax>(Descendant);
-
-        public static readonly Parser<IEnumerable<ISyntax>> Selector = SingleSelector.Many().End();
-        
+        private enum State
+        {
+            Start,
+            Middle,
+            Colon,
+            Class,
+            Name,
+            CanHaveType,
+            Traversal,
+            TypeName,
+            Property,
+            Template,
+            End,
+        }
+
+        public static IEnumerable<ISyntax> Parse(string s)
+        {
+            var r = new CharacterReader(s.AsSpan());
+            var state = State.Start;
+            var selector = new List<ISyntax>();
+            while (!r.End && state != State.End)
+            {
+                ISyntax syntax = null;
+                switch (state)
+                {
+                    case State.Start:
+                        state = ParseStart(ref r);
+                        break;
+                    case State.Middle:
+                        state = ParseMiddle(ref r);
+                        break;
+                    case State.CanHaveType:
+                        state = ParseCanHaveType(ref r);
+                        break;
+                    case State.Colon:
+                        (state, syntax) = ParseColon(ref r);
+                        break;
+                    case State.Class:
+                        (state, syntax) = ParseClass(ref r);
+                        break;
+                    case State.Traversal:
+                        (state, syntax) = ParseTraversal(ref r);
+                        break;
+                    case State.TypeName:
+                        (state, syntax) = ParseTypeName(ref r);
+                        break;
+                    case State.Property:
+                        (state, syntax) = ParseProperty(ref r);
+                        break;
+                    case State.Template:
+                        (state, syntax) = ParseTemplate(ref r);
+                        break;
+                    case State.Name:
+                        (state, syntax) = ParseName(ref r);
+                        break;
+                }
+                if (syntax != null)
+                {
+                    selector.Add(syntax);
+                }
+            }
+
+            if (state != State.Start && state != State.Middle && state != State.End && state != State.CanHaveType)
+            {
+                throw new ExpressionParseException(r.Position, "Unexpected end of selector");
+            }
+
+            return selector;
+        }
+
+        private static State ParseStart(ref CharacterReader r)
+        {
+            r.SkipWhitespace();
+            if (r.End)
+            {
+                return State.End;
+            }
+
+            if (r.TakeIf(':'))
+            {
+                return State.Colon;
+            }
+            else if (r.TakeIf('.'))
+            {
+                return State.Class;
+            }
+            else if (r.TakeIf('#'))
+            {
+                return State.Name;
+            }
+            return State.TypeName;
+        }
+
+        private static State ParseMiddle(ref CharacterReader r)
+        {
+            if (r.TakeIf(':'))
+            {
+                return State.Colon;
+            }
+            else if (r.TakeIf('.'))
+            {
+                return State.Class;
+            }
+            else if (r.TakeIf(char.IsWhiteSpace) || r.Peek == '>')
+            {
+                return State.Traversal;
+            }
+            else if (r.TakeIf('/'))
+            {
+                return State.Template;
+            }
+            else if (r.TakeIf('#'))
+            {
+                return State.Name;
+            }
+            return State.TypeName;
+        }
+
+        private static State ParseCanHaveType(ref CharacterReader r)
+        {
+            if (r.TakeIf('['))
+            {
+                return State.Property;
+            }
+            return State.Middle;
+        }
+
+        private static (State, ISyntax) ParseColon(ref CharacterReader r)
+        {
+            var identifier = r.ParseIdentifier();
+
+            if (identifier.IsEmpty)
+            {
+                throw new ExpressionParseException(r.Position, "Expected class name or is selector after ':'.");
+            }
+
+            const string IsKeyword = "is";
+            if (identifier.SequenceEqual(IsKeyword.AsSpan()) && r.TakeIf('('))
+            {
+                var syntax = ParseType(ref r, new IsSyntax());
+                if (r.End || !r.TakeIf(')'))
+                {
+                    throw new ExpressionParseException(r.Position, $"Expected ')', got {r.Peek}");
+                }
+
+                return (State.CanHaveType, syntax);
+            }
+            else
+            {
+                return (
+                    State.CanHaveType,
+                    new ClassSyntax
+                    {
+                        Class = ":" + identifier.ToString()
+                    });
+            }
+        }
+
+        private static (State, ISyntax) ParseTraversal(ref CharacterReader r)
+        {
+            r.SkipWhitespace();
+            if (r.TakeIf('>'))
+            {
+                r.SkipWhitespace();
+                return (State.Middle, new ChildSyntax());
+            }
+            else if (r.TakeIf('/'))
+            {
+                return (State.Template, null);
+            }
+            else if (!r.End)
+            {
+                return (State.Middle, new DescendantSyntax());
+            }
+            else
+            {
+                return (State.End, null);
+            }
+        }
+
+        private static (State, ISyntax) ParseClass(ref CharacterReader r)
+        {
+            var @class = r.ParseIdentifier();
+            if (@class.IsEmpty)
+            {
+                throw new ExpressionParseException(r.Position, $"Expected a class name after '.'.");
+            }
+
+            return (State.CanHaveType, new ClassSyntax { Class = @class.ToString() });
+        }
+
+        private static (State, ISyntax) ParseTemplate(ref CharacterReader r)
+        {
+            var template = r.ParseIdentifier();
+            const string TemplateKeyword = "template";
+            if (!template.SequenceEqual(TemplateKeyword.AsSpan()))
+            {
+                throw new ExpressionParseException(r.Position, $"Expected 'template', got '{template.ToString()}'");
+            }
+            else if (!r.TakeIf('/'))
+            {
+                throw new ExpressionParseException(r.Position, "Expected '/'");
+            }
+            return (State.Start, new TemplateSyntax());
+        }
+
+        private static (State, ISyntax) ParseName(ref CharacterReader r)
+        {
+            var name = r.ParseIdentifier();
+            if (name.IsEmpty)
+            {
+                throw new ExpressionParseException(r.Position, $"Expected a name after '#'.");
+            }
+            return (State.CanHaveType, new NameSyntax { Name = name.ToString() });
+        }
+
+        private static (State, ISyntax) ParseTypeName(ref CharacterReader r)
+        {
+            return (State.CanHaveType, ParseType(ref r, new OfTypeSyntax()));
+        }
+
+        private static (State, ISyntax) ParseProperty(ref CharacterReader r)
+        {
+            var property = r.ParseIdentifier();
+
+            if (!r.TakeIf('='))
+            {
+                throw new ExpressionParseException(r.Position, $"Expected '=', got '{r.Peek}'");
+            }
+
+            var value = r.TakeUntil(']');
+
+            r.Take();
+
+            return (State.CanHaveType, new PropertySyntax { Property = property.ToString(), Value = value.ToString() });
+        }
+
+        private static TSyntax ParseType<TSyntax>(ref CharacterReader r, TSyntax syntax)
+            where TSyntax : ITypeSyntax
+        {
+            ReadOnlySpan<char> ns = null;
+            ReadOnlySpan<char> type;
+            var namespaceOrTypeName = r.ParseIdentifier();
+
+            if (namespaceOrTypeName.IsEmpty)
+            {
+                throw new ExpressionParseException(r.Position, $"Expected an identifier, got '{r.Peek}");
+            }
+
+            if (!r.End && r.TakeIf('|'))
+            {
+                ns = namespaceOrTypeName;
+                if (r.End)
+                {
+                    throw new ExpressionParseException(r.Position, $"Unexpected end of selector.");
+                }
+                type = r.ParseIdentifier();
+            }
+            else
+            {
+                type = namespaceOrTypeName;
+            }
+
+            syntax.Xmlns = ns.ToString();
+            syntax.TypeName = type.ToString();
+            return syntax;
+        }
+
         public interface ISyntax
         {
         }
 
-        public class OfTypeSyntax : ISyntax
+        public interface ITypeSyntax
+        {
+            string TypeName { get; set; }
+
+            string Xmlns { get; set; }
+        }
+
+        public class OfTypeSyntax : ISyntax, ITypeSyntax
         {
             public string TypeName { get; set; }
 
-            public string Xmlns { get; set; }
+            public string Xmlns { get; set; } = string.Empty;
 
             public override bool Equals(object obj)
             {
@@ -136,11 +307,11 @@ namespace Avalonia.Markup.Parsers
             }
         }
 
-        public class IsSyntax : ISyntax
+        public class IsSyntax : ISyntax, ITypeSyntax
         {
             public string TypeName { get; set; }
 
-            public string Xmlns { get; set; }
+            public string Xmlns { get; set; } = string.Empty;
 
             public override bool Equals(object obj)
             {

+ 54 - 64
src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs

@@ -2,10 +2,11 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using System.Collections.Generic;
 using System.Globalization;
+using Avalonia.Data.Core;
 using Avalonia.Styling;
 using Avalonia.Utilities;
-using Sprache;
 
 namespace Avalonia.Markup.Parsers
 {
@@ -36,79 +37,68 @@ namespace Avalonia.Markup.Parsers
         /// <returns>The parsed selector.</returns>
         public Selector Parse(string s)
         {
-            var syntax = SelectorGrammar.Selector.Parse(s);
+            var syntax = SelectorGrammar.Parse(s);
             var result = default(Selector);
 
             foreach (var i in syntax)
             {
-                var ofType = i as SelectorGrammar.OfTypeSyntax;
-                var @is = i as SelectorGrammar.IsSyntax;
-                var @class = i as SelectorGrammar.ClassSyntax;
-                var name = i as SelectorGrammar.NameSyntax;
-                var property = i as SelectorGrammar.PropertySyntax;
-                var child = i as SelectorGrammar.ChildSyntax;
-                var descendant = i as SelectorGrammar.DescendantSyntax;
-                var template = i as SelectorGrammar.TemplateSyntax;
-
-                if (ofType != null)
-                {
-                    result = result.OfType(_typeResolver(ofType.Xmlns, ofType.TypeName));
-                }
-                if (@is != null)
-                {
-                    result = result.Is(_typeResolver(@is.Xmlns, @is.TypeName));
-                }
-                else if (@class != null)
-                {
-                    result = result.Class(@class.Class);
-                }
-                else if (name != null)
-                {
-                    result = result.Name(name.Name);
-                }
-                else if (property != null)
+                switch (i)
                 {
-                    var type = result?.TargetType;
 
-                    if (type == null)
-                    {
-                        throw new InvalidOperationException("Property selectors must be applied to a type.");
-                    }
+                    case SelectorGrammar.OfTypeSyntax ofType:
+                        result = result.OfType(_typeResolver(ofType.Xmlns, ofType.TypeName));
+                        break;
+                    case SelectorGrammar.IsSyntax @is:
+                        result = result.Is(_typeResolver(@is.Xmlns, @is.TypeName));
+                        break;
+                    case SelectorGrammar.ClassSyntax @class:
+                        result = result.Class(@class.Class);
+                        break;
+                    case SelectorGrammar.NameSyntax name:
+                        result = result.Name(name.Name);
+                        break;
+                    case SelectorGrammar.PropertySyntax property:
+                        {
+                            var type = result?.TargetType;
 
-                    var targetProperty = AvaloniaPropertyRegistry.Instance.FindRegistered(type, property.Property);
+                            if (type == null)
+                            {
+                                throw new InvalidOperationException("Property selectors must be applied to a type.");
+                            }
 
-                    if (targetProperty == null)
-                    {
-                        throw new InvalidOperationException($"Cannot find '{property.Property}' on '{type}");
-                    }
+                            var targetProperty = AvaloniaPropertyRegistry.Instance.FindRegistered(type, property.Property);
 
-                    object typedValue;
+                            if (targetProperty == null)
+                            {
+                                throw new InvalidOperationException($"Cannot find '{property.Property}' on '{type}");
+                            }
 
-                    if (TypeUtilities.TryConvert(
-                            targetProperty.PropertyType, 
-                            property.Value, 
-                            CultureInfo.InvariantCulture,
-                            out typedValue))
-                    {
-                        result = result.PropertyEquals(targetProperty, typedValue);
-                    }
-                    else
-                    {
-                        throw new InvalidOperationException(
-                            $"Could not convert '{property.Value}' to '{targetProperty.PropertyType}");
-                    }
-                }
-                else if (child != null)
-                {
-                    result = result.Child();
-                }
-                else if (descendant != null)
-                {
-                    result = result.Descendant();
-                }
-                else if (template != null)
-                {
-                    result = result.Template();
+                            object typedValue;
+
+                            if (TypeUtilities.TryConvert(
+                                    targetProperty.PropertyType,
+                                    property.Value,
+                                    CultureInfo.InvariantCulture,
+                                    out typedValue))
+                            {
+                                result = result.PropertyEquals(targetProperty, typedValue);
+                            }
+                            else
+                            {
+                                throw new InvalidOperationException(
+                                    $"Could not convert '{property.Value}' to '{targetProperty.PropertyType}");
+                            }
+                            break;
+                        }
+                    case SelectorGrammar.ChildSyntax child:
+                        result = result.Child();
+                        break;
+                    case SelectorGrammar.DescendantSyntax descendant:
+                        result = result.Descendant();
+                        break;
+                    case SelectorGrammar.TemplateSyntax template:
+                        result = result.Template();
+                        break;
                 }
             }
 

+ 13 - 1
src/OSX/Avalonia.MonoMac/PopupImpl.cs

@@ -8,11 +8,23 @@ namespace Avalonia.MonoMac
         public PopupImpl()
         {
             UpdateStyle();
+            Window.Level = NSWindowLevel.PopUpMenu;
         }
 
         protected override NSWindowStyle GetStyle()
         {
             return NSWindowStyle.Borderless;
         }
+
+        protected override CustomWindow CreateCustomWindow() => new CustomPopupWindow(this);
+
+        private class CustomPopupWindow : CustomWindow
+        {
+            public CustomPopupWindow(WindowBaseImpl impl)
+                : base(impl)
+            { }
+
+            public override bool WorksWhenModal() => true;
+        }
     }
-}
+}

+ 8 - 8
src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs

@@ -19,14 +19,13 @@ namespace Avalonia.MonoMac
         public WindowBaseImpl()
         {
             _managedDrag = new ManagedWindowResizeDragHelper(this, _ => { }, ResizeForManagedDrag);
-            Window = new CustomWindow(this)
-            {
-                StyleMask = NSWindowStyle.Titled,
-                BackingType = NSBackingStore.Buffered,
-                ContentView = View,
-                // ReSharper disable once VirtualMemberCallInConstructor
-                Delegate = CreateWindowDelegate()
-            };
+            // ReSharper disable once VirtualMemberCallInConstructor
+            Window = CreateCustomWindow();
+            Window.StyleMask = NSWindowStyle.Titled;
+            Window.BackingType = NSBackingStore.Buffered;
+            Window.ContentView = View;
+            // ReSharper disable once VirtualMemberCallInConstructor
+            Window.Delegate = CreateWindowDelegate();
         }
 
         public class CustomWindow : NSWindow
@@ -57,6 +56,7 @@ namespace Avalonia.MonoMac
             public void SetCanBecomeKeyAndMain() => _canBecomeKeyAndMain = true;
         }
 
+        protected virtual CustomWindow CreateCustomWindow() => new CustomWindow(this);
         protected virtual NSWindowDelegate CreateWindowDelegate() => new WindowBaseDelegate(this);
 
         public class WindowBaseDelegate : NSWindowDelegate

+ 0 - 19
src/Shared/SharedAssemblyInfo.cs

@@ -1,19 +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.Reflection;
-using System.Resources;
-using System.Runtime.CompilerServices;
-
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCopyright("Copyright AvaloniaUI 2016")]
-[assembly: AssemblyCulture("")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyProduct("Avalonia")]
-[assembly: AssemblyTrademark("")]
-[assembly: NeutralResourcesLanguage("en")]
-
-[assembly: AssemblyVersion("0.6.2")]
-[assembly: AssemblyFileVersion("0.6.2")]
-[assembly: AssemblyInformationalVersion("0.6.2")]

+ 0 - 20
src/Shared/avalonia.platform.targets

@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Includes the Avalonia platform-specific libraries in an application's output directory -->
-<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <ItemGroup Condition="'$(MSBuildThisFileDirectory)' != '' And HasTrailingSlash('$(MSBuildThisFileDirectory)')">
-    <PlatformLib Include="$(MSBuildThisFileDirectory)..\Gtk\Avalonia.Cairo\bin\$(Configuration)\Avalonia.Cairo.dll" />
-    <PlatformLib Include="$(MSBuildThisFileDirectory)..\Gtk\Avalonia.Gtk\bin\$(Configuration)\Avalonia.Gtk.dll" />
-    <PlatformLib Include="$(MSBuildThisFileDirectory)..\Windows\Avalonia.Direct2D1\bin\$(Configuration)\Avalonia.Direct2D1.dll" />
-    <PlatformLib Include="$(MSBuildThisFileDirectory)..\Windows\Avalonia.Direct2D1\bin\$(Configuration)\SharpDX.dll" />
-    <PlatformLib Include="$(MSBuildThisFileDirectory)..\Windows\Avalonia.Direct2D1\bin\$(Configuration)\SharpDX.Direct2D1.dll" />
-    <PlatformLib Include="$(MSBuildThisFileDirectory)..\Windows\Avalonia.Direct2D1\bin\$(Configuration)\SharpDX.DXGI.dll" />
-    <PlatformLib Include="$(MSBuildThisFileDirectory)..\Windows\Avalonia.Win32\bin\$(Configuration)\Avalonia.Win32.dll" />
-  </ItemGroup>
-  <PropertyGroup>
-    <PrepareForRunDependsOn>$(PrepareForRunDependsOn);CopyPlatformLibs</PrepareForRunDependsOn>
-  </PropertyGroup>
-  <Target Name="CopyPlatformLibs">
-    <Copy SourceFiles="@(PlatformLib)" DestinationFolder="$(OutDir)" ContinueOnError="true"/>
-  </Target>
-</Project>

+ 22 - 116
src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj

@@ -1,123 +1,29 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+<Project Sdk="MSBuild.Sdk.Extras">
   <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <ProjectGuid>{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}</ProjectGuid>
-    <OutputType>Library</OutputType>
-    <AppDesignerFolder>Properties</AppDesignerFolder>
-    <RootNamespace>Avalonia.Win32.Interop</RootNamespace>
-    <AssemblyName>Avalonia.Win32.Interop</AssemblyName>
-    <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
-    <FileAlignment>512</FileAlignment>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>false</Optimize>
-    <OutputPath>bin\Debug\</OutputPath>
-    <DefineConstants>DEBUG;TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <DebugType>pdbonly</DebugType>
-    <Optimize>true</Optimize>
-    <OutputPath>bin\Release\</OutputPath>
-    <DefineConstants>TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
+    <TargetFramework>net461</TargetFramework>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <ExtrasEnableWpfProjectSetup>true</ExtrasEnableWpfProjectSetup>
+    <ExtrasEnableImplicitWinFormsReferences>true</ExtrasEnableImplicitWinFormsReferences>
+    <UseDirect3D9>true</UseDirect3D9>
   </PropertyGroup>
   <ItemGroup>
-    <Reference Include="PresentationCore" />
-    <Reference Include="PresentationFramework" />
-    <Reference Include="System" />
-    <Reference Include="System.Core" />
-    <Reference Include="System.Windows.Forms" />
-    <Reference Include="System.Xaml" />
-    <Reference Include="System.Xml.Linq" />
-    <Reference Include="System.Data.DataSetExtensions" />
-    <Reference Include="Microsoft.CSharp" />
-    <Reference Include="System.Data" />
-    <Reference Include="System.Net.Http" />
-    <Reference Include="System.Xml" />
-    <Reference Include="WindowsBase" />
-  </ItemGroup>
-  <ItemGroup>
-    <Compile Include="..\Avalonia.Win32\Interop\UnmanagedMethods.cs">
-      <Link>UnmanagedMethods.cs</Link>
-    </Compile>
-    <Compile Include="Properties\AssemblyInfo.cs" />
-    <Compile Include="WinForms\WinFormsAvaloniaControlHost.cs">
-      <SubType>Component</SubType>
-    </Compile>
-    <Compile Include="Wpf\CursorShim.cs" />
-    <Compile Include="Wpf\Direct2DImageSurface.cs" />
-    <Compile Include="Wpf\IntSize.cs" />
-    <Compile Include="Wpf\WpfInteropExtensions.cs" />
-    <Compile Include="Wpf\WpfAvaloniaHost.cs" />
-    <Compile Include="Wpf\WpfMouseDevice.cs" />
-    <Compile Include="Wpf\WpfTopLevelImpl.cs" />
-    <Compile Include="Wpf\WritableBitmapSurface.cs" />
+    <ProjectReference Include="..\..\Avalonia.Animation\Avalonia.Animation.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Controls\Avalonia.Controls.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Input\Avalonia.Input.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Layout\Avalonia.Layout.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Styling\Avalonia.Styling.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
+    <ProjectReference Include="..\..\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />
+    <ProjectReference Include="..\..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
+    <ProjectReference Include="..\..\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj" />
+    <ProjectReference Include="..\Avalonia.Win32\Avalonia.Win32.csproj" />
+    <ProjectReference Include="..\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" />
   </ItemGroup>
   <ItemGroup>
-    <ProjectReference Include="..\..\Avalonia.Animation\Avalonia.Animation.csproj">
-      <Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
-      <Name>Avalonia.Animation</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj">
-      <Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
-      <Name>Avalonia.Base</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Controls\Avalonia.Controls.csproj">
-      <Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
-      <Name>Avalonia.Controls</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj">
-      <Project>{4a1abb09-9047-4bd5-a4ad-a055e52c5ee0}</Project>
-      <Name>Avalonia.DotNetFrameworkRuntime</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Input\Avalonia.Input.csproj">
-      <Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
-      <Name>Avalonia.Input</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Interactivity\Avalonia.Interactivity.csproj">
-      <Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
-      <Name>Avalonia.Interactivity</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Layout\Avalonia.Layout.csproj">
-      <Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
-      <Name>Avalonia.Layout</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Styling\Avalonia.Styling.csproj">
-      <Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
-      <Name>Avalonia.Styling</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj">
-      <Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
-      <Name>Avalonia.Visuals</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj">
-      <Project>{3e53a01a-b331-47f3-b828-4a5717e77a24}</Project>
-      <Name>Avalonia.Markup.Xaml</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Markup\Avalonia.Markup\Avalonia.Markup.csproj">
-      <Project>{6417e941-21bc-467b-a771-0de389353ce6}</Project>
-      <Name>Avalonia.Markup</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj">
-      <Project>{3e908f67-5543-4879-a1dc-08eace79b3cd}</Project>
-      <Name>Avalonia.Direct2D1</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\Avalonia.Win32\Avalonia.Win32.csproj">
-      <Project>{811a76cf-1cf6-440f-963b-bbe31bd72a82}</Project>
-      <Name>Avalonia.Win32</Name>
-    </ProjectReference>
+    <Compile Include="..\Avalonia.Win32\Interop\UnmanagedMethods.cs" Link="UnmanagedMethods.cs" />
   </ItemGroup>
-  <PropertyGroup>
-    <UseDirect3D9>true</UseDirect3D9>
-  </PropertyGroup>
+
   <Import Project="..\..\..\build\SharpDX.props" />
-  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
-</Project>
+</Project>

+ 0 - 36
src/Windows/Avalonia.Win32.Interop/Properties/AssemblyInfo.cs

@@ -1,36 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("Avalonia.Win32.Interop")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("Avalonia.Win32.Interop")]
-[assembly: AssemblyCopyright("Copyright ©  2017")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components.  If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("cbc4ff2f-92d4-420b-be21-9fe0b930b04e")]
-
-// Version information for an assembly consists of the following four values:
-//
-//      Major Version
-//      Minor Version
-//      Build Number
-//      Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]

+ 0 - 13
src/Windows/Avalonia.Win32/Avalonia.Win32.Shared.shproj

@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <PropertyGroup Label="Globals">
-    <ProjectGuid>9defc6b7-845b-4d8f-afc0-d32bf0032b8c</ProjectGuid>
-    <MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
-  </PropertyGroup>
-  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
-  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
-  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
-  <PropertyGroup />
-  <Import Project="Avalonia.Win32.Shared.projitems" Label="Shared" />
-  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
-</Project>

+ 14 - 107
src/iOS/Avalonia.iOS/Avalonia.iOS.csproj

@@ -1,114 +1,21 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project>
+  <Import Project="Sdk.props" Sdk="MSBuild.Sdk.Extras" />
   <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">iPhoneSimulator</Platform>
-    <ProductVersion>8.0.30703</ProductVersion>
-    <SchemaVersion>2.0</SchemaVersion>
-    <ProjectGuid>{4488AD85-1495-4809-9AA4-DDFE0A48527E}</ProjectGuid>
-    <ProjectTypeGuids>{FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
-    <OutputType>Library</OutputType>
-    <RootNamespace>Avalonia.iOS</RootNamespace>
-    <IPhoneResourcePrefix>Resources</IPhoneResourcePrefix>
-    <AssemblyName>Avalonia.iOS</AssemblyName>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>false</Optimize>
-    <OutputPath>bin\iPhone\Debug</OutputPath>
-    <DefineConstants>DEBUG</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <ConsolePause>false</ConsolePause>
-    <MtouchDebug>true</MtouchDebug>
-    <CodesignKey>iPhone Developer</CodesignKey>
-    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <DebugType>none</DebugType>
-    <Optimize>true</Optimize>
-    <OutputPath>bin\iPhone\Release</OutputPath>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <ConsolePause>false</ConsolePause>
-    <CodesignKey>iPhone Developer</CodesignKey>
+    <TargetFramework>xamarin.ios10</TargetFramework>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
-  <PropertyGroup>
-    <DisableSourceLink>true</DisableSourceLink>
-  </PropertyGroup>
   <ItemGroup>
-    <Compile Include="AppBuilder.cs" />
-    <Compile Include="AvaloniaRootViewController.cs" />
-    <Compile Include="AvaloniaView.cs" />
-    <Compile Include="AvaloniaWindow.cs" />
-    <Compile Include="Clipboard.cs" />
-    <Compile Include="CursorFactory.cs" />
-    <Compile Include="DisplayLinkRenderLoop.cs" />
-    <Compile Include="EmbeddableImpl.cs" />
-    <Compile Include="EmulatedFramebuffer.cs" />
-    <Compile Include="Extensions.cs" />
-    <Compile Include="iOSPlatform.cs" />
-    <Compile Include="TopLevelImpl.cs" />
-    <Compile Include="PlatformIconLoader.cs" />
-    <Compile Include="PlatformSettings.cs" />
-    <Compile Include="PlatformThreadingInterface.cs" />
-    <Compile Include="Properties\AssemblyInfo.cs" />
-    <Compile Include="RuntimeInfo.cs" />
-    <Compile Include="Specific\KeyboardEventsHelper.cs" />
-    <Compile Include="WindowingPlatformImpl.cs" />
-  </ItemGroup>
-  <ItemGroup>
-    <ProjectReference Include="..\..\Avalonia.Animation\Avalonia.Animation.csproj">
-      <Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
-      <Name>Avalonia.Animation</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj">
-      <Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
-      <Name>Avalonia.Base</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Controls\Avalonia.Controls.csproj">
-      <Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
-      <Name>Avalonia.Controls</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Input\Avalonia.Input.csproj">
-      <Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
-      <Name>Avalonia.Input</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Interactivity\Avalonia.Interactivity.csproj">
-      <Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
-      <Name>Avalonia.Interactivity</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Layout\Avalonia.Layout.csproj">
-      <Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
-      <Name>Avalonia.Layout</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj">
-      <Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
-      <Name>Avalonia.Visuals</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Styling\Avalonia.Styling.csproj">
-      <Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
-      <Name>Avalonia.Styling</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj">
-      <Project>{7d2d3083-71dd-4cc9-8907-39a0d86fb322}</Project>
-      <Name>Avalonia.Skia</Name>
-      <IsAppExtension>false</IsAppExtension>
-      <IsWatchApp>false</IsWatchApp>
-    </ProjectReference>
-  </ItemGroup>
-  <ItemGroup>
-    <Reference Include="System" />
-    <Reference Include="System.Xml" />
-    <Reference Include="System.Core" />
-    <Reference Include="Xamarin.iOS" />
+    <ProjectReference Include="..\..\Avalonia.Animation\Avalonia.Animation.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Controls\Avalonia.Controls.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Input\Avalonia.Input.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Layout\Avalonia.Layout.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Styling\Avalonia.Styling.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
   </ItemGroup>
   <Import Project="..\..\Shared\PlatformSupport\PlatformSupport.projitems" Label="Shared" />
-  <Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
   <Import Project="..\..\..\build\Rx.props" />
-  <PropertyGroup Condition="'$(UseRoslynPathHack)' == 'true'">
-    <CscToolPath>$(MSBuildToolsPath)\..\Roslyn</CscToolPath>
-  </PropertyGroup>
-</Project>
+  <Import Project="Sdk.targets" Sdk="MSBuild.Sdk.Extras" />
+  <Import Project="..\..\..\build\iOSWorkarounds.props" />
+</Project>

+ 0 - 36
src/iOS/Avalonia.iOS/Properties/AssemblyInfo.cs

@@ -1,36 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following 
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("Avalonia.iOS")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("Avalonia.iOS")]
-[assembly: AssemblyCopyright("Copyright ©  2015")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible 
-// to COM components.  If you need to access a type in this assembly from 
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("4488ad85-1495-4809-9aa4-ddfe0a48527e")]
-
-// Version information for an assembly consists of the following four values:
-//
-//      Major Version
-//      Minor Version 
-//      Build Number
-//      Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers 
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]

+ 0 - 17
src/iOS/Avalonia.iOS/iOSPlatform.cs

@@ -5,7 +5,6 @@ using Avalonia.Input.Platform;
 using Avalonia.iOS;
 using Avalonia.Platform;
 using Avalonia.Shared.PlatformSupport;
-using Avalonia.Skia;
 using UIKit;
 using Avalonia.Controls;
 using Avalonia.Rendering;
@@ -19,22 +18,6 @@ namespace Avalonia
             builder.UseWindowingSubsystem(iOSPlatform.Initialize, "iOS");
             return builder;
         }
-        /*
-        // TODO: Can we merge this with UseSkia somehow once HW/platform cleanup is done?
-        public static T UseSkiaViewHost<T>(this T builder) where T : AppBuilderBase<T>, new()
-        {
-            var window = new UIWindow(UIScreen.MainScreen.Bounds);
-            var controller = new AvaloniaViewController(window);
-            window.RootViewController = controller;
-            window.MakeKeyAndVisible();
-
-            AvaloniaLocator.CurrentMutable
-                .Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformImpl(controller.AvaloniaView));
-
-            SkiaPlatform.Initialize();
-
-            return builder;
-        }*/
     }
 }
 

+ 2 - 1
src/iOS/Avalonia.iOSTestApplication/Avalonia.iOSTestApplication.csproj

@@ -186,4 +186,5 @@
   </ItemGroup>
   <Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
   <Import Project="..\..\..\build\Rx.props" />
-</Project>
+  <Import Project="..\..\..\build\iOSWorkarounds.props" />
+</Project>

+ 20 - 106
src/tools/Avalonia.Designer.HostApp.NetFX/Avalonia.Designer.HostApp.NetFX.csproj

@@ -1,111 +1,25 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <ProjectGuid>{4ADA61C8-D191-428D-9066-EF4F0D86520F}</ProjectGuid>
-    <OutputType>WinExe</OutputType>
-    <AppDesignerFolder>Properties</AppDesignerFolder>
-    <RootNamespace>Avalonia.Designer.HostApp</RootNamespace>
+    <OutputType>Exe</OutputType>
+    <TargetFrameworks>net461</TargetFrameworks>
     <AssemblyName>Avalonia.Designer.HostApp</AssemblyName>
-    <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
-    <FileAlignment>512</FileAlignment>
-    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
-    <TargetFrameworkProfile />
-    <RestoreProjectStyle>PackageReference</RestoreProjectStyle>
   </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <PlatformTarget>AnyCPU</PlatformTarget>
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>false</Optimize>
-    <OutputPath>bin\Debug\</OutputPath>
-    <DefineConstants>DEBUG;TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <PlatformTarget>x86</PlatformTarget>
-    <DebugType>pdbonly</DebugType>
-    <Optimize>true</Optimize>
-    <OutputPath>bin\Release\</OutputPath>
-    <DefineConstants>TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-  </PropertyGroup>
-  <PropertyGroup>
-    <StartupObject />
-  </PropertyGroup>
-  <ItemGroup>
-    <Reference Include="System" />
-    <Reference Include="System.Core" />
-    <Reference Include="System.Xml.Linq" />
-    <Reference Include="System.Data.DataSetExtensions" />
-    <Reference Include="Microsoft.CSharp" />
-    <Reference Include="System.Data" />
-    <Reference Include="System.Xml" />
-  </ItemGroup>
-  <ItemGroup>
-    <Compile Include="Program.cs" />
-  </ItemGroup>
   <ItemGroup>
-    <ProjectReference Include="..\..\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj">
-      <Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project>
-      <Name>Avalonia.DesignerSupport</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj">
-      <Project>{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}</Project>
-      <Name>Avalonia.DotNetFrameworkRuntime</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj">
-      <Project>{3E53A01A-B331-47F3-B828-4A5717E77A24}</Project>
-      <Name>Avalonia.Markup.Xaml</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Markup\Avalonia.Markup\Avalonia.Markup.csproj">
-      <Project>{6417E941-21BC-467B-A771-0DE389353CE6}</Project>
-      <Name>Avalonia.Markup</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Animation\Avalonia.Animation.csproj">
-      <Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
-      <Name>Avalonia.Animation</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj">
-      <Project>{B09B78D8-9B26-48B0-9149-D64A2F120F3F}</Project>
-      <Name>Avalonia.Base</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Controls\Avalonia.Controls.csproj">
-      <Project>{D2221C82-4A25-4583-9B43-D791E3F6820C}</Project>
-      <Name>Avalonia.Controls</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Input\Avalonia.Input.csproj">
-      <Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
-      <Name>Avalonia.Input</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Interactivity\Avalonia.Interactivity.csproj">
-      <Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
-      <Name>Avalonia.Interactivity</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Layout\Avalonia.Layout.csproj">
-      <Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
-      <Name>Avalonia.Layout</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Logging.Serilog\Avalonia.Logging.Serilog.csproj">
-      <Project>{B61B66A3-B82D-4875-8001-89D3394FE0C9}</Project>
-      <Name>Avalonia.Logging.Serilog</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj">
-      <Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
-      <Name>Avalonia.Visuals</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Styling\Avalonia.Styling.csproj">
-      <Project>{F1BAA01A-F176-4C6A-B39D-5B40BB1B148F}</Project>
-      <Name>Avalonia.Styling</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj">
-      <Project>{3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}</Project>
-      <Name>Avalonia.Themes.Default</Name>
-    </ProjectReference>
+    <ProjectReference Include="..\..\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" />
+    <ProjectReference Include="..\..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
+    <ProjectReference Include="..\..\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Animation\Avalonia.Animation.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Controls\Avalonia.Controls.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Input\Avalonia.Input.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Layout\Avalonia.Layout.csproj" />
+    <ProjectReference Include="..\..\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Styling\Avalonia.Styling.csproj" />
+    <ProjectReference Include="..\..\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
+    <ProjectReference Include="..\..\..\samples\ControlCatalog.Desktop\ControlCatalog.Desktop.csproj" />
   </ItemGroup>
-  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
-</Project>
+  <Import Condition="'$(TargetFramework)'=='net461'" Project="..\..\..\build\NetFX.props" />
+</Project>

+ 2 - 0
tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj

@@ -1,5 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFrameworks>netcoreapp2.0</TargetFrameworks>
     <TargetFramework>netcoreapp2.0</TargetFramework>
     <OutputType>Exe</OutputType>
   </PropertyGroup>

+ 34 - 0
tests/Avalonia.Benchmarks/Markup/Parsing.cs

@@ -0,0 +1,34 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Parsers;
+using BenchmarkDotNet.Attributes;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Avalonia.Benchmarks.Markup
+{
+    [MemoryDiagnoser]
+    public class Parsing
+    {
+        [Benchmark]
+        public void ParseComplexSelector()
+        {
+            var selectorString = "ListBox > TextBox /template/ TextBlock[IsFocused=True]";
+            var parser = new SelectorParser((ns, s) =>
+            {
+                switch (s)
+                {
+                    case "ListBox":
+                        return typeof(ListBox);
+                    case "TextBox":
+                        return typeof(TextBox);
+                    case "TextBlock":
+                        return typeof(TextBlock);
+                    default:
+                        return null;
+                }
+            });
+            var selector = parser.Parse(selectorString);
+        }
+    }
+}

+ 1 - 3
tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj

@@ -9,9 +9,7 @@
   <Import Project="..\..\build\XUnit.props" />
   <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\Microsoft.Reactive.Testing.props" />
-  <ItemGroup>
-    <PackageReference Include="System.ValueTuple" Version="4.3.1" />
-  </ItemGroup>
+  <Import Project="..\..\build\Base.props" />
   <ItemGroup>
     <ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
     <ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />

+ 139 - 20
tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs

@@ -1,10 +1,12 @@
 using Avalonia.Controls;
 using Avalonia.Input.Raw;
+using Avalonia.Interactivity;
 using Avalonia.Rendering;
 using Avalonia.UnitTests;
 using Avalonia.VisualTree;
 using Moq;
 using System;
+using System.Collections.Generic;
 using Xunit;
 
 namespace Avalonia.Input.UnitTests
@@ -30,7 +32,7 @@ namespace Avalonia.Input.UnitTests
         }
 
         [Fact]
-        public void MouseMove_Should_Update_PointerOver()
+        public void MouseMove_Should_Update_IsPointerOver()
         {
             var renderer = new Mock<IRenderer>();
 
@@ -59,40 +61,157 @@ namespace Avalonia.Input.UnitTests
                     }
                 };
 
-                renderer.Setup(x => x.HitTest(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>()))
-                    .Returns(new[] { decorator });
-
-                inputManager.ProcessInput(new RawMouseEventArgs(
-                    root.MouseDevice,
-                    0,
-                    root,
-                    RawMouseEventType.Move,
-                    new Point(),
-                    InputModifiers.None));
+                SetHit(renderer, decorator);
+                SendMouseMove(inputManager, root);
 
                 Assert.True(decorator.IsPointerOver);
                 Assert.True(border.IsPointerOver);
                 Assert.False(canvas.IsPointerOver);
                 Assert.True(root.IsPointerOver);
 
-                renderer.Setup(x => x.HitTest(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>()))
-                    .Returns(new[] { canvas });
+                SetHit(renderer, canvas);
+                SendMouseMove(inputManager, root);
+
+                Assert.False(decorator.IsPointerOver);
+                Assert.False(border.IsPointerOver);
+                Assert.True(canvas.IsPointerOver);
+                Assert.True(root.IsPointerOver);
+            }
+        }
+
+        [Fact]
+        public void IsPointerOver_Should_Be_Updated_When_Child_Sets_Handled_True()
+        {
+            var renderer = new Mock<IRenderer>();
+
+            using (TestApplication(renderer.Object))
+            {
+                var inputManager = InputManager.Instance;
 
-                inputManager.ProcessInput(new RawMouseEventArgs(
-                    root.MouseDevice,
-                    0,
-                    root,
-                    RawMouseEventType.Move,
-                    new Point(),
-                    InputModifiers.None));
+                Canvas canvas;
+                Border border;
+                Decorator decorator;
+
+                var root = new TestRoot
+                {
+                    MouseDevice = new MouseDevice(),
+                    Renderer = renderer.Object,
+                    Child = new Panel
+                    {
+                        Children =
+                        {
+                            (canvas = new Canvas()),
+                            (border = new Border
+                            {
+                                Child = decorator = new Decorator(),
+                            })
+                        }
+                    }
+                };
+
+                SetHit(renderer, canvas);
+                SendMouseMove(inputManager, root);
 
                 Assert.False(decorator.IsPointerOver);
                 Assert.False(border.IsPointerOver);
                 Assert.True(canvas.IsPointerOver);
                 Assert.True(root.IsPointerOver);
+
+                // Ensure that e.Handled is reset between controls.
+                decorator.PointerEnter += (s, e) => e.Handled = true;
+
+                SetHit(renderer, decorator);
+                SendMouseMove(inputManager, root);
+
+                Assert.True(decorator.IsPointerOver);
+                Assert.True(border.IsPointerOver);
+                Assert.False(canvas.IsPointerOver);
+                Assert.True(root.IsPointerOver);
             }
         }
 
+        [Fact]
+        public void PointerEnter_Leave_Should_Be_Raised_In_Correct_Order()
+        {
+            var renderer = new Mock<IRenderer>();
+            var result = new List<(object, string)>();
+
+            void HandleEvent(object sender, PointerEventArgs e)
+            {
+                result.Add((sender, e.RoutedEvent.Name));
+            }
+
+            using (TestApplication(renderer.Object))
+            {
+                var inputManager = InputManager.Instance;
+
+                Canvas canvas;
+                Border border;
+                Decorator decorator;
+
+                var root = new TestRoot
+                {
+                    MouseDevice = new MouseDevice(),
+                    Renderer = renderer.Object,
+                    Child = new Panel
+                    {
+                        Children =
+                        {
+                            (canvas = new Canvas()),
+                            (border = new Border
+                            {
+                                Child = decorator = new Decorator(),
+                            })
+                        }
+                    }
+                };
+
+                SetHit(renderer, canvas);
+                SendMouseMove(inputManager, root);
+
+                AddEnterLeaveHandlers(HandleEvent, root, canvas, border, decorator);
+                SetHit(renderer, decorator);
+                SendMouseMove(inputManager, root);
+
+                Assert.Equal(
+                    new[]
+                    {
+                        ((object)canvas, "PointerLeave"),
+                        ((object)decorator, "PointerEnter"),
+                        ((object)border, "PointerEnter"),
+                    },
+                    result);
+            }
+        }
+
+        private void AddEnterLeaveHandlers(
+            EventHandler<PointerEventArgs> handler,
+            params IControl[] controls)
+        {
+            foreach (var c in controls)
+            {
+                c.PointerEnter += handler;
+                c.PointerLeave += handler;
+            }
+        }
+
+        private void SendMouseMove(IInputManager inputManager, TestRoot root)
+        {
+            inputManager.ProcessInput(new RawMouseEventArgs(
+                root.MouseDevice,
+                0,
+                root,
+                RawMouseEventType.Move,
+                new Point(),
+                InputModifiers.None));
+        }
+
+        private void SetHit(Mock<IRenderer> renderer, IControl hit)
+        {
+            renderer.Setup(x => x.HitTest(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>()))
+                .Returns(new[] { hit });
+        }
+
         private IDisposable TestApplication(IRenderer renderer)
         {
             return UnitTestApplication.Start(

+ 0 - 1
tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj

@@ -8,7 +8,6 @@
   <Import Project="..\..\build\XUnit.props" />
   <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\Microsoft.Reactive.Testing.props" />
-  <Import Project="..\..\build\Markup.props" />
   <ItemGroup>
     <ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj" />

+ 146 - 0
tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs

@@ -0,0 +1,146 @@
+// 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 Avalonia.Controls;
+using Avalonia.Data;
+using Avalonia.Data.Converters;
+using Avalonia.Data.Core;
+using Xunit;
+
+namespace Avalonia.Markup.UnitTests.Data
+{
+    public class BindingTests_Converters
+    {
+        [Fact]
+        public void Converter_Should_Be_Used()
+        {
+            var textBlock = new TextBlock
+            {
+                DataContext = new Class1(),
+            };
+
+            var target = new Binding(nameof(Class1.Foo))
+            {
+                Converter = StringConverters.NullOrEmpty,
+            };
+
+            var expressionObserver = (BindingExpression)target.Initiate(
+                textBlock, 
+                TextBlock.TextProperty).Observable;
+
+            Assert.Same(StringConverters.NullOrEmpty, expressionObserver.Converter);
+        }
+
+        public class When_Binding_To_String
+        {
+            [Fact]
+            public void StringFormatConverter_Should_Be_Used_When_Binding_Has_StringFormat()
+            {
+                var textBlock = new TextBlock
+                {
+                    DataContext = new Class1(),
+                };
+
+                var target = new Binding(nameof(Class1.Foo))
+                {
+                    StringFormat = "Hello {0}",
+                };
+
+                var expressionObserver = (BindingExpression)target.Initiate(
+                    textBlock,
+                    TextBlock.TextProperty).Observable;
+
+                Assert.IsType<StringFormatValueConverter>(expressionObserver.Converter);
+            }
+        }
+
+        public class When_Binding_To_Object
+        {
+            [Fact]
+            public void StringFormatConverter_Should_Be_Used_When_Binding_Has_StringFormat()
+            {
+                var textBlock = new TextBlock
+                {
+                    DataContext = new Class1(),
+                };
+
+                var target = new Binding(nameof(Class1.Foo))
+                {
+                    StringFormat = "Hello {0}",
+                };
+
+                var expressionObserver = (BindingExpression)target.Initiate(
+                    textBlock,
+                    TextBlock.TagProperty).Observable;
+
+                Assert.IsType<StringFormatValueConverter>(expressionObserver.Converter);
+            }
+        }
+
+        public class When_Binding_To_Non_String_Or_Object
+        {
+            [Fact]
+            public void StringFormatConverter_Should_Not_Be_Used_When_Binding_Has_StringFormat()
+            {
+                var textBlock = new TextBlock
+                {
+                    DataContext = new Class1(),
+                };
+
+                var target = new Binding(nameof(Class1.Foo))
+                {
+                    StringFormat = "Hello {0}",
+                };
+
+                var expressionObserver = (BindingExpression)target.Initiate(
+                    textBlock,
+                    TextBlock.MarginProperty).Observable;
+
+                Assert.Same(DefaultValueConverter.Instance, expressionObserver.Converter);
+            }
+        }
+
+        [Fact]
+        public void StringFormat_Should_Be_Applied()
+        {
+            var textBlock = new TextBlock
+            {
+                DataContext = new Class1(),
+            };
+
+            var target = new Binding(nameof(Class1.Foo))
+            {
+                StringFormat = "Hello {0}",
+            };
+
+            textBlock.Bind(TextBlock.TextProperty, target);
+
+            Assert.Equal("Hello foo", textBlock.Text);
+        }
+
+        [Fact]
+        public void StringFormat_Should_Be_Applied_After_Converter()
+        {
+            var textBlock = new TextBlock
+            {
+                DataContext = new Class1(),
+            };
+
+            var target = new Binding(nameof(Class1.Foo))
+            {
+                Converter = StringConverters.NotNullOrEmpty,
+                StringFormat = "Hello {0}",
+            };
+
+            textBlock.Bind(TextBlock.TextProperty, target);
+
+            Assert.Equal("Hello True", textBlock.Text);
+        }
+
+        private class Class1
+        {
+            public string Foo { get; set; } = "foo";
+        }
+    }
+}

+ 22 - 22
tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs

@@ -2,8 +2,8 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System.Linq;
+using Avalonia.Data.Core;
 using Avalonia.Markup.Parsers;
-using Sprache;
 using Xunit;
 
 namespace Avalonia.Markup.UnitTests.Parsers
@@ -13,17 +13,17 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void OfType()
         {
-            var result = SelectorGrammar.Selector.Parse("Button").ToList();
+            var result = SelectorGrammar.Parse("Button");
 
             Assert.Equal(
-                new[] { new SelectorGrammar.OfTypeSyntax { TypeName = "Button", Xmlns = null } },
+                new[] { new SelectorGrammar.OfTypeSyntax { TypeName = "Button", Xmlns = "" } },
                 result);
         }
 
         [Fact]
         public void NamespacedOfType()
         {
-            var result = SelectorGrammar.Selector.Parse("x|Button").ToList();
+            var result = SelectorGrammar.Parse("x|Button");
 
             Assert.Equal(
                 new[] { new SelectorGrammar.OfTypeSyntax { TypeName = "Button", Xmlns = "x" } },
@@ -33,7 +33,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void Name()
         {
-            var result = SelectorGrammar.Selector.Parse("#foo").ToList();
+            var result = SelectorGrammar.Parse("#foo");
 
             Assert.Equal(
                 new[] { new SelectorGrammar.NameSyntax { Name = "foo" }, },
@@ -43,7 +43,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void OfType_Name()
         {
-            var result = SelectorGrammar.Selector.Parse("Button#foo").ToList();
+            var result = SelectorGrammar.Parse("Button#foo");
 
             Assert.Equal(
                 new SelectorGrammar.ISyntax[]
@@ -57,17 +57,17 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void Is()
         {
-            var result = SelectorGrammar.Selector.Parse(":is(Button)").ToList();
+            var result = SelectorGrammar.Parse(":is(Button)");
 
             Assert.Equal(
-                new[] { new SelectorGrammar.IsSyntax { TypeName = "Button", Xmlns = null } },
+                new[] { new SelectorGrammar.IsSyntax { TypeName = "Button", Xmlns = "" } },
                 result);
         }
 
         [Fact]
         public void Is_Name()
         {
-            var result = SelectorGrammar.Selector.Parse(":is(Button)#foo").ToList();
+            var result = SelectorGrammar.Parse(":is(Button)#foo");
 
             Assert.Equal(
                 new SelectorGrammar.ISyntax[]
@@ -81,7 +81,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void NamespacedIs_Name()
         {
-            var result = SelectorGrammar.Selector.Parse(":is(x|Button)#foo").ToList();
+            var result = SelectorGrammar.Parse(":is(x|Button)#foo");
 
             Assert.Equal(
                 new SelectorGrammar.ISyntax[]
@@ -95,7 +95,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void Class()
         {
-            var result = SelectorGrammar.Selector.Parse(".foo").ToList();
+            var result = SelectorGrammar.Parse(".foo");
 
             Assert.Equal(
                 new[] { new SelectorGrammar.ClassSyntax { Class = "foo" } },
@@ -105,7 +105,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void Pseudoclass()
         {
-            var result = SelectorGrammar.Selector.Parse(":foo").ToList();
+            var result = SelectorGrammar.Parse(":foo");
 
             Assert.Equal(
                 new[] { new SelectorGrammar.ClassSyntax { Class = ":foo" } },
@@ -115,7 +115,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void OfType_Class()
         {
-            var result = SelectorGrammar.Selector.Parse("Button.foo").ToList();
+            var result = SelectorGrammar.Parse("Button.foo");
 
             Assert.Equal(
                 new SelectorGrammar.ISyntax[] 
@@ -129,7 +129,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void OfType_Child_Class()
         {
-            var result = SelectorGrammar.Selector.Parse("Button > .foo").ToList();
+            var result = SelectorGrammar.Parse("Button > .foo");
 
             Assert.Equal(
                 new SelectorGrammar.ISyntax[]
@@ -144,7 +144,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void OfType_Child_Class_No_Spaces()
         {
-            var result = SelectorGrammar.Selector.Parse("Button>.foo").ToList();
+            var result = SelectorGrammar.Parse("Button>.foo");
 
             Assert.Equal(
                 new SelectorGrammar.ISyntax[]
@@ -159,7 +159,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void OfType_Descendant_Class()
         {
-            var result = SelectorGrammar.Selector.Parse("Button .foo").ToList();
+            var result = SelectorGrammar.Parse("Button .foo");
 
             Assert.Equal(
                 new SelectorGrammar.ISyntax[]
@@ -174,7 +174,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void OfType_Template_Class()
         {
-            var result = SelectorGrammar.Selector.Parse("Button /template/ .foo").ToList();
+            var result = SelectorGrammar.Parse("Button /template/ .foo");
 
             Assert.Equal(
                 new SelectorGrammar.ISyntax[]
@@ -189,7 +189,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void OfType_Property()
         {
-            var result = SelectorGrammar.Selector.Parse("Button[Foo=bar]").ToList();
+            var result = SelectorGrammar.Parse("Button[Foo=bar]");
 
             Assert.Equal(
                 new SelectorGrammar.ISyntax[]
@@ -203,25 +203,25 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void Namespace_Alone_Fails()
         {
-            Assert.Throws<ParseException>(() => SelectorGrammar.Selector.Parse("ns|").ToList());
+            Assert.Throws<ExpressionParseException>(() => SelectorGrammar.Parse("ns|"));
         }
 
         [Fact]
         public void Dot_Alone_Fails()
         {
-            Assert.Throws<ParseException>(() => SelectorGrammar.Selector.Parse(". dot").ToList());
+            Assert.Throws<ExpressionParseException>(() => SelectorGrammar.Parse(". dot"));
         }
 
         [Fact]
         public void Invalid_Identifier_Fails()
         {
-            Assert.Throws<ParseException>(() => SelectorGrammar.Selector.Parse("%foo").ToList());
+            Assert.Throws<ExpressionParseException>(() => SelectorGrammar.Parse("%foo"));
         }
 
         [Fact]
         public void Invalid_Class_Fails()
         {
-            Assert.Throws<ParseException>(() => SelectorGrammar.Selector.Parse(".%foo").ToList());
+            Assert.Throws<ExpressionParseException>(() => SelectorGrammar.Parse(".%foo"));
         }
     }
 }

+ 3 - 1
tests/Avalonia.Markup.UnitTests/Parsers/SelectorParserTests.cs

@@ -1,6 +1,8 @@
 using System;
 using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
 using Avalonia.Markup.Parsers;
+using Avalonia.Styling;
 using Xunit;
 
 namespace Avalonia.Markup.UnitTests.Parsers
@@ -10,7 +12,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void Parses_Boolean_Property_Selector()
         {
-            var target = new SelectorParser((type, ns) => typeof(TextBlock));
+            var target = new SelectorParser((ns, type) => typeof(TextBlock));
             var result = target.Parse("TextBlock[IsPointerOver=True]");
         }
     }

+ 0 - 1
tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj

@@ -8,7 +8,6 @@
   <Import Project="..\..\build\XUnit.props" />
   <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\Microsoft.Reactive.Testing.props" />
-  <Import Project="..\..\build\Sprache.props" />
   <ItemGroup>
     <ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
     <ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />

+ 30 - 44
tests/Avalonia.Markup.Xaml.UnitTests/Parsers/PropertyParserTests.cs

@@ -13,7 +13,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Parsers
         public void Parses_Name()
         {
             var target = new PropertyParser();
-            var reader = new CharacterReader("Foo");
+            var reader = new CharacterReader("Foo".AsSpan());
             var (ns, owner, name) = target.Parse(reader);
 
             Assert.Null(ns);
@@ -25,7 +25,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Parsers
         public void Parses_Owner_And_Name()
         {
             var target = new PropertyParser();
-            var reader = new CharacterReader("Foo.Bar");
+            var reader = new CharacterReader("Foo.Bar".AsSpan());
             var (ns, owner, name) = target.Parse(reader);
 
             Assert.Null(ns);
@@ -37,7 +37,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Parsers
         public void Parses_Namespace_Owner_And_Name()
         {
             var target = new PropertyParser();
-            var reader = new CharacterReader("foo:Bar.Baz");
+            var reader = new CharacterReader("foo:Bar.Baz".AsSpan());
             var (ns, owner, name) = target.Parse(reader);
 
             Assert.Equal("foo", ns);
@@ -49,7 +49,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Parsers
         public void Parses_Owner_And_Name_With_Parentheses()
         {
             var target = new PropertyParser();
-            var reader = new CharacterReader("(Foo.Bar)");
+            var reader = new CharacterReader("(Foo.Bar)".AsSpan());
             var (ns, owner, name) = target.Parse(reader);
 
             Assert.Null(ns);
@@ -61,7 +61,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Parsers
         public void Parses_Namespace_Owner_And_Name_With_Parentheses()
         {
             var target = new PropertyParser();
-            var reader = new CharacterReader("(foo:Bar.Baz)");
+            var reader = new CharacterReader("(foo:Bar.Baz)".AsSpan());
             var (ns, owner, name) = target.Parse(reader);
 
             Assert.Equal("foo", ns);
@@ -73,9 +73,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Parsers
         public void Fails_With_Empty_String()
         {
             var target = new PropertyParser();
-            var reader = new CharacterReader("");
 
-            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(reader));
+            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(new CharacterReader(ReadOnlySpan<char>.Empty)));
             Assert.Equal(0, ex.Column);
             Assert.Equal("Expected property name.", ex.Message);
         }
@@ -84,9 +83,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Parsers
         public void Fails_With_Only_Whitespace()
         {
             var target = new PropertyParser();
-            var reader = new CharacterReader("  ");
 
-            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(reader));
+            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(new CharacterReader("  ".AsSpan())));
             Assert.Equal(0, ex.Column);
             Assert.Equal("Unexpected ' '.", ex.Message);
         }
@@ -95,9 +93,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Parsers
         public void Fails_With_Leading_Whitespace()
         {
             var target = new PropertyParser();
-            var reader = new CharacterReader(" Foo");
-
-            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(reader));
+            
+            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(new CharacterReader(" Foo".AsSpan())));
             Assert.Equal(0, ex.Column);
             Assert.Equal("Unexpected ' '.", ex.Message);
         }
@@ -106,9 +103,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Parsers
         public void Fails_With_Trailing_Whitespace()
         {
             var target = new PropertyParser();
-            var reader = new CharacterReader("Foo ");
-
-            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(reader));
+            
+            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(new CharacterReader("Foo ".AsSpan())));
             Assert.Equal(3, ex.Column);
             Assert.Equal("Unexpected ' '.", ex.Message);
         }
@@ -117,9 +113,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Parsers
         public void Fails_With_Invalid_Property_Name()
         {
             var target = new PropertyParser();
-            var reader = new CharacterReader("123");
-
-            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(reader));
+            
+            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(new CharacterReader("123".AsSpan())));
             Assert.Equal(0, ex.Column);
             Assert.Equal("Unexpected '1'.", ex.Message);
         }
@@ -128,9 +123,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Parsers
         public void Fails_With_Trailing_Junk()
         {
             var target = new PropertyParser();
-            var reader = new CharacterReader("Foo%");
-
-            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(reader));
+            
+            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(new CharacterReader("Foo%".AsSpan())));
             Assert.Equal(3, ex.Column);
             Assert.Equal("Unexpected '%'.", ex.Message);
         }
@@ -139,9 +133,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Parsers
         public void Fails_With_Invalid_Property_Name_After_Owner()
         {
             var target = new PropertyParser();
-            var reader = new CharacterReader("Foo.123");
-
-            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(reader));
+            
+            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(new CharacterReader("Foo.123".AsSpan())));
             Assert.Equal(4, ex.Column);
             Assert.Equal("Unexpected '1'.", ex.Message);
         }
@@ -150,9 +143,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Parsers
         public void Fails_With_Whitespace_Between_Owner_And_Name()
         {
             var target = new PropertyParser();
-            var reader = new CharacterReader("Foo. Bar");
-
-            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(reader));
+            
+            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(new CharacterReader("Foo. Bar".AsSpan())));
             Assert.Equal(4, ex.Column);
             Assert.Equal("Unexpected ' '.", ex.Message);
         }
@@ -161,9 +153,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Parsers
         public void Fails_With_Too_Many_Segments()
         {
             var target = new PropertyParser();
-            var reader = new CharacterReader("Foo.Bar.Baz");
-
-            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(reader));
+            
+            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(new CharacterReader("Foo.Bar.Baz".AsSpan())));
             Assert.Equal(8, ex.Column);
             Assert.Equal("Unexpected '.'.", ex.Message);
         }
@@ -172,9 +163,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Parsers
         public void Fails_With_Too_Many_Namespaces()
         {
             var target = new PropertyParser();
-            var reader = new CharacterReader("foo:bar:Baz");
-
-            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(reader));
+            
+            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(new CharacterReader("foo:bar:Baz".AsSpan())));
             Assert.Equal(8, ex.Column);
             Assert.Equal("Unexpected ':'.", ex.Message);
         }
@@ -183,9 +173,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Parsers
         public void Fails_With_Parens_But_No_Owner()
         {
             var target = new PropertyParser();
-            var reader = new CharacterReader("(Foo)");
-
-            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(reader));
+            
+            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(new CharacterReader("(Foo)".AsSpan())));
             Assert.Equal(1, ex.Column);
             Assert.Equal("Expected property owner.", ex.Message);
         }
@@ -194,9 +183,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Parsers
         public void Fails_With_Parens_And_Namespace_But_No_Owner()
         {
             var target = new PropertyParser();
-            var reader = new CharacterReader("(foo:Bar)");
-
-            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(reader));
+            
+            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(new CharacterReader("(foo:Bar)".AsSpan())));
             Assert.Equal(1, ex.Column);
             Assert.Equal("Expected property owner.", ex.Message);
         }
@@ -205,9 +193,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Parsers
         public void Fails_With_Missing_Close_Parens()
         {
             var target = new PropertyParser();
-            var reader = new CharacterReader("(Foo.Bar");
-
-            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(reader));
+            
+            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(new CharacterReader("(Foo.Bar".AsSpan())));
             Assert.Equal(8, ex.Column);
             Assert.Equal("Expected ')'.", ex.Message);
         }
@@ -216,9 +203,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Parsers
         public void Fails_With_Unexpected_Close_Parens()
         {
             var target = new PropertyParser();
-            var reader = new CharacterReader("Foo.Bar)");
 
-            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(reader));
+            var ex = Assert.Throws<ExpressionParseException>(() => target.Parse(new CharacterReader("Foo.Bar)".AsSpan())));
             Assert.Equal(7, ex.Column);
             Assert.Equal("Unexpected ')'.", ex.Message);
         }

+ 22 - 0
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs

@@ -308,5 +308,27 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
                 Assert.Equal(5.6, AttachedPropertyOwner.GetDouble(textBlock));
             }
         }
+
+        [Fact]
+        public void Binding_To_TextBlock_Text_With_StringConverter_Works()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
+    <TextBlock Name='textBlock' Text='{Binding Foo, StringFormat=Hello \{0\}}'/> 
+</Window>"; 
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml); 
+                var textBlock = window.FindControl<TextBlock>("textBlock"); 
+
+                textBlock.DataContext = new { Foo = "world" };
+                window.ApplyTemplate(); 
+
+                Assert.Equal("Hello world", textBlock.Text); 
+            }
+        } 
     }
 }

+ 6 - 0
tests/Avalonia.UnitTests/ImmediateDispatcher.cs

@@ -25,6 +25,12 @@ namespace Avalonia.UnitTests
             return Task.FromResult<object>(null);
         }
 
+        public Task<TResult> InvokeAsync<TResult>(Func<TResult> function, DispatcherPriority priority = DispatcherPriority.Normal)
+        {
+            var result = function();
+            return Task.FromResult(result);
+        }
+
         public void VerifyAccess()
         {
         }

+ 0 - 20
tests/run-tests.sh

@@ -1,20 +0,0 @@
-# !/bin/bash
-
-cd "$(dirname "$0")"
-
-tests=(Avalonia.*.UnitTests/)
-exclude=("*Direct2D*/")
-result=0
-
-for del in ${exclude[@]}; do
-    tests=(${tests[@]/$del})
-done
-
-for test in ${tests[@]}; do
-    echo Running test $test
-    mono ../testrunner/xunit.runner.console.2.1.0/tools/xunit.console.exe ${test}bin/Release/${test%/}.dll  -parallel none
-
-    if [ $? -ne 0 ]; then result=1 ; fi
-done
-
-exit $result

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä