Browse Source

Merge branch 'master' into RemoveNugetWorkaround

Nikita Tsukanov 8 years ago
parent
commit
bc5caecafb
100 changed files with 1776 additions and 377 deletions
  1. 2 1
      .gitignore
  2. 43 0
      Avalonia.sln
  3. 2 0
      appveyor.yml
  4. 13 12
      build.cake
  5. 5 0
      build/Base.props
  6. 1 1
      build/Moq.props
  7. 1 0
      build/SharpDX.props
  8. 1 1
      build/SkiaSharp.props
  9. 4 2
      build/XUnit.props
  10. 1 1
      docs/tutorial/from-wpf.md
  11. 23 1
      packages.cake
  12. 23 0
      samples/ControlCatalog/Pages/MenuPage.xaml
  13. 13 1
      samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml
  14. 14 1
      samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs
  15. 1 1
      samples/interop/WindowsInteropTest/Program.cs
  16. 6 1
      samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
  17. 5 0
      scripts/ReplaceNugetCache.ps1
  18. 7 0
      scripts/ReplaceNugetCache.sh
  19. 0 1
      src/Android/Avalonia.Android/AndroidPlatform.cs
  20. 2 0
      src/Android/Avalonia.Android/Platform/Input/AndroidMouseDevice.cs
  21. 1 1
      src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
  22. 3 0
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  23. 1 1
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
  24. 1 0
      src/Avalonia.Base/Avalonia.Base.csproj
  25. 8 30
      src/Avalonia.Base/Data/BindingNotification.cs
  26. 23 13
      src/Avalonia.Controls/Button.cs
  27. 1 1
      src/Avalonia.Controls/ContextMenu.cs
  28. 9 2
      src/Avalonia.Controls/Control.cs
  29. 6 4
      src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
  30. 1 1
      src/Avalonia.Controls/Menu.cs
  31. 10 1
      src/Avalonia.Controls/MenuItem.cs
  32. 7 0
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  33. 24 14
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  34. 2 2
      src/Avalonia.Controls/Primitives/Popup.cs
  35. 11 0
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  36. 7 2
      src/Avalonia.Controls/TextBox.cs
  37. 34 19
      src/Avalonia.Controls/ToolTip.cs
  38. 3 0
      src/Avalonia.Controls/TopLevel.cs
  39. 21 1
      src/Avalonia.Controls/TreeView.cs
  40. 8 1
      src/Avalonia.Controls/WindowBase.cs
  41. 13 14
      src/Avalonia.Diagnostics/DevTools.xaml.cs
  42. 4 1
      src/Avalonia.HtmlRenderer/Adapters/ControlAdapter.cs
  43. 2 1
      src/Avalonia.HtmlRenderer/HtmlControl.cs
  44. 3 2
      src/Avalonia.Input/FocusManager.cs
  45. 15 0
      src/Avalonia.Input/ICustomKeyboardNavigation.cs
  46. 7 0
      src/Avalonia.Input/IInputDevice.cs
  47. 8 0
      src/Avalonia.Input/IInputRoot.cs
  48. 1 0
      src/Avalonia.Input/InputManager.cs
  49. 3 9
      src/Avalonia.Input/KeyboardDevice.cs
  50. 27 0
      src/Avalonia.Input/KeyboardNavigationHandler.cs
  51. 8 23
      src/Avalonia.Input/MouseDevice.cs
  52. 14 2
      src/Avalonia.Input/Navigation/DirectionalNavigation.cs
  53. 62 25
      src/Avalonia.Input/Navigation/TabNavigation.cs
  54. 10 0
      src/Avalonia.Layout/IEmbeddedLayoutRoot.cs
  55. 66 33
      src/Avalonia.Layout/LayoutManager.cs
  56. 21 4
      src/Avalonia.Layout/Layoutable.cs
  57. 10 0
      src/Avalonia.Styling/LogicalTree/ILogical.cs
  58. 2 2
      src/Avalonia.Styling/Styling/Style.cs
  59. 1 1
      src/Avalonia.Visuals/Platform/ILockedFramebuffer.cs
  60. 11 2
      src/Avalonia.Visuals/Vector.cs
  61. 0 1
      src/Gtk/Avalonia.Gtk/GtkPlatform.cs
  62. 1 1
      src/Gtk/Avalonia.Gtk/SurfaceFramebuffer.cs
  63. 3 0
      src/Gtk/Avalonia.Gtk/TopLevelImpl.cs
  64. 0 1
      src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs
  65. 2 3
      src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs
  66. 1 1
      src/Gtk/Avalonia.Gtk3/Interop/GObject.cs
  67. 18 0
      src/Gtk/Avalonia.Gtk3/Interop/Native.cs
  68. 2 0
      src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs
  69. 2 1
      src/Gtk/Avalonia.Gtk3/SystemDialogs.cs
  70. 14 0
      src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
  71. 1 0
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  72. 3 3
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebuffer.cs
  73. 0 1
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  74. 2 2
      src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs
  75. 1 1
      src/Markup/Avalonia.Markup.Xaml/Data/MultiBinding.cs
  76. 2 2
      src/Markup/Avalonia.Markup/Data/BindingExpression.cs
  77. 2 1
      src/Skia/Avalonia.Skia.Desktop.NetStandard/Avalonia.Skia.Desktop.NetStandard.csproj
  78. 4 1
      src/Skia/Avalonia.Skia.Desktop/Avalonia.Skia.Desktop.csproj
  79. 1 1
      src/Skia/Avalonia.Skia/BitmapImpl.cs
  80. 3 9
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  81. 50 51
      src/Skia/Avalonia.Skia/FormattedTextImpl.cs
  82. 1 1
      src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
  83. 2 0
      src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
  84. 10 5
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  85. 47 0
      src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs
  86. 16 0
      src/Windows/Avalonia.Direct2D1/IExternalDirect2DRenderTargetSurface.cs
  87. 6 1
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  88. 1 1
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WritableWicBitmapImpl.cs
  89. 123 0
      src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj
  90. 36 0
      src/Windows/Avalonia.Win32.Interop/Properties/AssemblyInfo.cs
  91. 38 0
      src/Windows/Avalonia.Win32.Interop/Wpf/CursorShim.cs
  92. 208 0
      src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs
  93. 59 0
      src/Windows/Avalonia.Win32.Interop/Wpf/IntSize.cs
  94. 121 0
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs
  95. 16 0
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfInteropExtensions.cs
  96. 30 0
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfMouseDevice.cs
  97. 241 0
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  98. 73 0
      src/Windows/Avalonia.Win32.Interop/Wpf/WritableBitmapSurface.cs
  99. 0 1
      src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
  100. 0 52
      src/Windows/Avalonia.Win32/Embedding/WpfAvaloniaControlHost.cs

+ 2 - 1
.gitignore

@@ -162,7 +162,8 @@ $RECYCLE.BIN/
 #################
 ## Cake
 #################
-tools/
+tools/*
+!tools/packages.config
 .nuget
 artifacts/
 nuget

+ 43 - 0
Avalonia.sln

@@ -191,6 +191,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.LinuxFramebuffer",
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Direct3DInteropSample", "samples\interop\Direct3DInteropSample\Direct3DInteropSample.csproj", "{638580B0-7910-40EF-B674-DCB34DA308CD}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Win32.Interop", "src\Windows\Avalonia.Win32.Interop\Avalonia.Win32.Interop.csproj", "{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}"
+EndProject
 Global
 	GlobalSection(SharedMSBuildProjectFiles) = preSolution
 		src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{2f59f3d0-748d-4652-b01e-e0d954756308}*SharedItemsImports = 13
@@ -2589,6 +2591,46 @@ Global
 		{638580B0-7910-40EF-B674-DCB34DA308CD}.Release|Mono.Build.0 = Release|Any CPU
 		{638580B0-7910-40EF-B674-DCB34DA308CD}.Release|x86.ActiveCfg = Release|Any CPU
 		{638580B0-7910-40EF-B674-DCB34DA308CD}.Release|x86.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|Mono.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|Mono.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|x86.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|Any CPU.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|iPhone.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|iPhone.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|Mono.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|Mono.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|x86.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|x86.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|iPhone.Build.0 = Debug|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Mono.ActiveCfg = Debug|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Mono.Build.0 = Debug|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|x86.Build.0 = Debug|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Any CPU.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|iPhone.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|iPhone.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Mono.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Mono.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|x86.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -2649,5 +2691,6 @@ Global
 		{4D6FAF79-58B4-482F-9122-0668C346364C} = {74487168-7D91-487E-BF93-055F2251461E}
 		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
 		{638580B0-7910-40EF-B674-DCB34DA308CD} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E} = {B39A8919-9F95-48FE-AD7B-76E08B509888}
 	EndGlobalSection
 EndGlobal

+ 2 - 0
appveyor.yml

@@ -13,6 +13,8 @@ environment:
   MYGET_API_KEY:
     secure: OtVfyN3ErqQrDTnWH2HDfJDlCiu/i4/X4wFmK3ZXXP7HmCiXYPSbTjMPwwdOxRaK
   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 gtk-sharp-2.12.26.msi appveyor DownloadFile http://download.xamarin.com/GTKforWindows/Windows/gtk-sharp-2.12.26.msi
   - if not exist dotnet-1.0.1.exe appveyor DownloadFile https://go.microsoft.com/fwlink/?linkid=843448 -FileName "dotnet-1.0.1.exe"

+ 13 - 12
build.cake

@@ -11,7 +11,7 @@
 // TOOLS
 ///////////////////////////////////////////////////////////////////////////////
 
-#tool "nuget:?package=xunit.runner.console&version=2.1.0"
+#tool "nuget:?package=xunit.runner.console&version=2.2.0"
 #tool "nuget:?package=OpenCover"
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -98,7 +98,6 @@ Task("Clean")
     CleanDirectory(parameters.TestsRoot);
 });
 
-
 Task("Restore-NuGet-Packages")
     .IsDependentOn("Clean")
     .WithCriteria(parameters.IsRunningOnWindows)
@@ -170,23 +169,25 @@ void RunCoreTest(string dir, Parameters parameters, bool net461Only)
             continue;
         Information("Running for " + fw);
         DotNetCoreTest(System.IO.Path.Combine(dir, System.IO.Path.GetFileName(dir)+".csproj"),
-            new DotNetCoreTestSettings{Framework = fw});
+            new DotNetCoreTestSettings {
+                Configuration = parameters.Configuration,
+                Framework = fw
+            });
     }
 }
 
-
 Task("Run-Net-Core-Unit-Tests")
     .IsDependentOn("Clean")
     .Does(() => {
         RunCoreTest("./tests/Avalonia.Base.UnitTests", parameters, false);
-        RunCoreTest("./tests/Avalonia.Controls.UnitTests", parameters, true);
-        RunCoreTest("./tests/Avalonia.Input.UnitTests", parameters, true);
-        RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", parameters, true);
-        RunCoreTest("./tests/Avalonia.Layout.UnitTests", parameters, true);
-        //RunCoreTest("./tests/Avalonia.Markup.UnitTests", parameters, true);
-        //RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", parameters, true);
-        RunCoreTest("./tests/Avalonia.Styling.UnitTests", parameters, true);
-        RunCoreTest("./tests/Avalonia.Visuals.UnitTests", parameters, true);
+        RunCoreTest("./tests/Avalonia.Controls.UnitTests", parameters, false);
+        RunCoreTest("./tests/Avalonia.Input.UnitTests", parameters, false);
+        RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", parameters, false);
+        RunCoreTest("./tests/Avalonia.Layout.UnitTests", parameters, false);
+        RunCoreTest("./tests/Avalonia.Markup.UnitTests", parameters, false);
+        RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", parameters, false);
+        RunCoreTest("./tests/Avalonia.Styling.UnitTests", parameters, false);
+        RunCoreTest("./tests/Avalonia.Visuals.UnitTests", parameters, false);
     });
 
 Task("Run-Unit-Tests")

+ 5 - 0
build/Base.props

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

+ 1 - 1
build/Moq.props

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

+ 1 - 0
build/SharpDX.props

@@ -3,6 +3,7 @@
     <PackageReference Include="SharpDX" Version="3.1.1" />
     <PackageReference Include="SharpDX.Direct2D1" Version="3.1.1" />
     <PackageReference Include="SharpDX.Direct3D11" Version="3.1.1" />
+    <PackageReference Include="SharpDX.Direct3D9" Version="3.1.1" Condition="'$(UseDirect3D9)' == 'true'" />
     <PackageReference Include="SharpDX.DXGI" Version="3.1.1" />
   </ItemGroup>
 </Project>

+ 1 - 1
build/SkiaSharp.props

@@ -1,6 +1,6 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
     <PackageReference Include="SkiaSharp" Version="1.57.1" />
-    <PackageReference Condition="$(TargetFramework.Trim('.').ToLower().StartsWith('netframework'))" Include="Avalonia.Skia.Linux.Natives" Version="1.57.1.3" />
+    <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.57.1.3" />
   </ItemGroup>
 </Project>

+ 4 - 2
build/XUnit.props

@@ -7,7 +7,9 @@
     <PackageReference Include="xunit.extensibility.core" Version="2.2.0" />
     <PackageReference Include="xunit.extensibility.execution" Version="2.2.0" />
     <PackageReference Include="xunit.runner.console" Version="2.2.0" />
-    <PackageReference Condition="'$(TargetFramework)' == 'net461'" Include="xunit.runner.visualstudio" Version="2.2.0" />
-    <PackageReference Condition="'$(TargetFramework)' == 'netcoreapp1.1'" Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
+  </ItemGroup>
+  <ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp1.1'">
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
   </ItemGroup>
 </Project>

+ 1 - 1
docs/tutorial/from-wpf.md

@@ -33,7 +33,7 @@ placed in a `DataTemplates` collection on each control (and on `Application`):
                     <TextBox Text="{Binding Name}"/>
                 </Border>
             </DataTemplate>
-        </UserControl.Styles>
+        </UserControl.DataTemplates>
         <!-- Assuming that DataContext.Foo is an object of type
              MyApp.ViewModels.FooViewModel then a red border with a corner
              radius of 8 containing a TextBox will be displayed here -->

+ 23 - 1
packages.cake

@@ -75,22 +75,26 @@ public class Packages
         var SplatVersion = packageVersions["Splat"].FirstOrDefault().Item1;
         var SpracheVersion = packageVersions["Sprache"].FirstOrDefault().Item1;
         var SystemReactiveVersion = packageVersions["System.Reactive"].FirstOrDefault().Item1;
+        var SystemValueTupleVersion = packageVersions["System.ValueTuple"].FirstOrDefault().Item1;
         SkiaSharpVersion = packageVersions["SkiaSharp"].FirstOrDefault().Item1;
 		SkiaSharpLinuxVersion = packageVersions["Avalonia.Skia.Linux.Natives"].FirstOrDefault().Item1;
         var SharpDXVersion = packageVersions["SharpDX"].FirstOrDefault().Item1;
         var SharpDXDirect2D1Version = packageVersions["SharpDX.Direct2D1"].FirstOrDefault().Item1;
         var SharpDXDirect3D11Version = packageVersions["SharpDX.Direct3D11"].FirstOrDefault().Item1;
+        var SharpDXDirect3D9Version = packageVersions["SharpDX.Direct3D9"].FirstOrDefault().Item1;
         var SharpDXDXGIVersion = packageVersions["SharpDX.DXGI"].FirstOrDefault().Item1;
 
         context.Information("Package: Serilog, version: {0}", SerilogVersion);
         context.Information("Package: Splat, version: {0}", SplatVersion);
         context.Information("Package: Sprache, version: {0}", SpracheVersion);
         context.Information("Package: System.Reactive, version: {0}", SystemReactiveVersion);
+        context.Information("Package: System.ValueTuple, version: {0}", SystemValueTupleVersion);
         context.Information("Package: SkiaSharp, version: {0}", SkiaSharpVersion);
         context.Information("Package: Avalonia.Skia.Linux.Natives, version: {0}", SkiaSharpLinuxVersion);
         context.Information("Package: SharpDX, version: {0}", SharpDXVersion);
         context.Information("Package: SharpDX.Direct2D1, version: {0}", SharpDXDirect2D1Version);
         context.Information("Package: SharpDX.Direct3D11, version: {0}", SharpDXDirect3D11Version);
+        context.Information("Package: SharpDX.Direct3D9, version: {0}", SharpDXDirect3D9Version);
         context.Information("Package: SharpDX.DXGI, version: {0}", SharpDXDXGIVersion);
 
         var nugetPackagesDir = System.Environment.GetEnvironmentVariable("NUGET_HOME")
@@ -197,6 +201,7 @@ public class Packages
                     new NuSpecDependency() { Id = "Splat", Version = SplatVersion },
                     new NuSpecDependency() { Id = "Sprache", Version = SpracheVersion },
                     new NuSpecDependency() { Id = "System.Reactive", Version = SystemReactiveVersion },
+                    new NuSpecDependency() { Id = "System.ValueTuple", Version = SystemValueTupleVersion },
                     //.NET Core
                     new NuSpecDependency() { Id = "System.Threading.ThreadPool", TargetFramework = "netcoreapp1.0", Version = "4.3.0" },
                     new NuSpecDependency() { Id = "Microsoft.Extensions.DependencyModel", TargetFramework = "netcoreapp1.0", Version = "1.1.0" },
@@ -204,7 +209,8 @@ public class Packages
                     new NuSpecDependency() { Id = "Splat", TargetFramework = "netcoreapp1.0", Version = SplatVersion },
                     new NuSpecDependency() { Id = "Serilog", TargetFramework = "netcoreapp1.0", Version = SerilogVersion },
                     new NuSpecDependency() { Id = "Sprache", TargetFramework = "netcoreapp1.0", Version = SpracheVersion },
-                    new NuSpecDependency() { Id = "System.Reactive", TargetFramework = "netcoreapp1.0", Version = SystemReactiveVersion }
+                    new NuSpecDependency() { Id = "System.Reactive", TargetFramework = "netcoreapp1.0", Version = SystemReactiveVersion },
+                    new NuSpecDependency() { Id = "System.ValueTuple", TargetFramework = "netcoreapp1.0", Version = SystemValueTupleVersion }
                 },
                 Files = coreLibrariesNuSpecContent
                     .Concat(win32CoreLibrariesNuSpecContent).Concat(net45RuntimePlatform)
@@ -465,6 +471,22 @@ public class Packages
                 BasePath = context.Directory("./"),
                 OutputDirectory = parameters.NugetRoot
             },
+            new NuGetPackSettings()
+            {
+                Id = "Avalonia.Win32.Interoperability",
+                Dependencies = new []
+                {
+                    new NuSpecDependency() { Id = "Avalonia.Win32", Version = parameters.Version },
+                    new NuSpecDependency() { Id = "Avalonia.Direct2D1", Version = parameters.Version },
+                    new NuSpecDependency() { Id = "SharpDX.Direct3D9", Version = SharpDXDirect3D9Version },
+                },
+                Files = new []
+                {
+                    new NuSpecContent { Source = "Avalonia.Win32.Interop/bin/" + parameters.DirSuffix + "/Avalonia.Win32.Interop.dll", Target = "lib/net45" }
+                },
+                BasePath = context.Directory("./src/Windows"),
+                OutputDirectory = parameters.NugetRoot
+            },
             ///////////////////////////////////////////////////////////////////////////////
             // Avalonia.LinuxFramebuffer
             ///////////////////////////////////////////////////////////////////////////////

+ 23 - 0
samples/ControlCatalog/Pages/MenuPage.xaml

@@ -31,5 +31,28 @@
         </MenuItem>
       </Menu>
     </StackPanel>
+
+    <TextBlock Classes="h2" Text="A context menu (right click)">
+      <TextBlock.ContextMenu>
+        <ContextMenu>
+          <MenuItem Header="Standard _Menu Item"/>
+          <Separator/>
+          <MenuItem Header="Menu with _Submenu">
+            <MenuItem Header="Submenu _1"/>
+            <MenuItem Header="Submenu _2"/>
+          </MenuItem>
+          <MenuItem Header="Menu Item with _Icon">
+            <MenuItem.Icon>
+              <Image Source="resm:ControlCatalog.Assets.github_icon.png"/>
+            </MenuItem.Icon>
+          </MenuItem>
+          <MenuItem Header="Menu Item with _Checkbox">
+            <MenuItem.Icon>
+              <CheckBox BorderThickness="0" IsHitTestVisible="False" IsChecked="True"/>
+            </MenuItem.Icon>
+          </MenuItem>
+        </ContextMenu>
+      </TextBlock.ContextMenu>
+    </TextBlock>
   </StackPanel>
 </UserControl>

+ 13 - 1
samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml

@@ -1,10 +1,12 @@
 <Window x:Class="WindowsInteropTest.EmbedToWpfDemo"
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:av="clr-namespace:Avalonia.Controls;assembly=Avalonia.Controls"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
              xmlns:local="clr-namespace:WindowsInteropTest"
              xmlns:embedding="clr-namespace:Avalonia.Win32.Embedding;assembly=Avalonia.Win32"
+             xmlns:wpf="clr-namespace:Avalonia.Win32.Interop.Wpf;assembly=Avalonia.Win32.Interop"
              mc:Ignorable="d" 
              d:DesignHeight="400" d:DesignWidth="400" MinWidth="500" MinHeight="400">
     <DockPanel>
@@ -14,8 +16,18 @@
                 <Calendar/>
             </StackPanel>
         </GroupBox>
+        <GroupBox Header="Avalonia button" DockPanel.Dock="Bottom">
+            <wpf:WpfAvaloniaHost >
+                <av:Button Content="Avalonia button"/>
+            </wpf:WpfAvaloniaHost>
+        </GroupBox>
+        <GroupBox Header="AvBtn" DockPanel.Dock="Right">
+            <wpf:WpfAvaloniaHost x:Name="RightBtn">
+                <av:Button Content="Avalonia button 2"/>
+            </wpf:WpfAvaloniaHost>
+        </GroupBox>
         <GroupBox Header="Avalonia">
-            <embedding:WpfAvaloniaControlHost x:Name="Host"/>
+            <wpf:WpfAvaloniaHost x:Name="Host"/>
         </GroupBox>
     </DockPanel>
 </Window>

+ 14 - 1
samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs

@@ -11,7 +11,9 @@ using System.Windows.Media;
 using System.Windows.Media.Imaging;
 using System.Windows.Navigation;
 using System.Windows.Shapes;
+using Avalonia;
 using Avalonia.Controls;
+using Avalonia.VisualTree;
 using ControlCatalog;
 using Window = System.Windows.Window;
 
@@ -25,7 +27,18 @@ namespace WindowsInteropTest
         public EmbedToWpfDemo()
         {
             InitializeComponent();
-            Host.Content =  new MainView();
+            var view = new MainView();
+            view.AttachedToVisualTree += delegate
+            {
+                ((TopLevel) view.GetVisualRoot()).AttachDevTools(); 
+            };
+            Host.Content = view;
+            var btn = (Avalonia.Controls.Button) RightBtn.Content;
+            btn.Click += delegate
+            {
+                btn.Content += "!";
+            };
+
         }
     }
 }

+ 1 - 1
samples/interop/WindowsInteropTest/Program.cs

@@ -15,7 +15,7 @@ namespace WindowsInteropTest
         {
             System.Windows.Forms.Application.EnableVisualStyles();
             System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
-            AppBuilder.Configure<App>().UseWin32().UseSkia().SetupWithoutStarting();
+            AppBuilder.Configure<App>().UseWin32().UseDirect2D1().SetupWithoutStarting();
             System.Windows.Forms.Application.Run(new SelectorForm());
         }
     }

+ 6 - 1
samples/interop/WindowsInteropTest/WindowsInteropTest.csproj

@@ -14,7 +14,7 @@
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <PlatformTarget>AnyCPU</PlatformTarget>
+    <PlatformTarget>x86</PlatformTarget>
     <DebugSymbols>true</DebugSymbols>
     <DebugType>full</DebugType>
     <Optimize>false</Optimize>
@@ -164,6 +164,10 @@
       <Project>{3e908f67-5543-4879-a1dc-08eace79b3cd}</Project>
       <Name>Avalonia.Direct2D1</Name>
     </ProjectReference>
+    <ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32.Interop\Avalonia.Win32.Interop.csproj">
+      <Project>{cbc4ff2f-92d4-420b-be21-9fe0b930b04e}</Project>
+      <Name>Avalonia.Win32.Interop</Name>
+    </ProjectReference>
     <ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj">
       <Project>{811a76cf-1cf6-440f-963b-bbe31bd72a82}</Project>
       <Name>Avalonia.Win32</Name>
@@ -181,4 +185,5 @@
   </ItemGroup>
   <Import Project="..\..\..\build\Rx.props" />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Import Project="..\..\..\build\SkiaSharp.props" />
 </Project>

+ 5 - 0
scripts/ReplaceNugetCache.ps1

@@ -0,0 +1,5 @@
+copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp1.0\
+copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard1.1\
+copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia.gtk3\$args\lib\netstandard1.1\
+copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia.skia.desktop\$args\lib\netstandard1.3\
+copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard1.1\

+ 7 - 0
scripts/ReplaceNugetCache.sh

@@ -0,0 +1,7 @@
+ #!/usr/bin/env bash
+ 
+ cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netcoreapp1.0/
+ cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netstandard1.1/
+ cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia.gtk3/$1/lib/netstandard1.1/
+ cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia.skia.desktop/$1/lib/netstandard1.3/
+ 

+ 0 - 1
src/Android/Avalonia.Android/AndroidPlatform.cs

@@ -51,7 +51,6 @@ namespace Avalonia.Android
                 .Bind<IClipboard>().ToTransient<ClipboardImpl>()
                 .Bind<IStandardCursorFactory>().ToTransient<CursorFactory>()
                 .Bind<IKeyboardDevice>().ToSingleton<AndroidKeyboardDevice>()
-                .Bind<IMouseDevice>().ToSingleton<AndroidMouseDevice>()
                 .Bind<IPlatformSettings>().ToConstant(Instance)
                 .Bind<IRendererFactory>().ToConstant(ImmediateRenderer.Factory)
                 .Bind<IPlatformThreadingInterface>().ToConstant(new AndroidThreadingInterface())

+ 2 - 0
src/Android/Avalonia.Android/Platform/Input/AndroidMouseDevice.cs

@@ -4,6 +4,8 @@ namespace Avalonia.Android.Platform.Input
 {
     public class AndroidMouseDevice : MouseDevice
     {
+        public static AndroidMouseDevice Instance { get; } = new AndroidMouseDevice();
+
         public AndroidMouseDevice()
         {
 

+ 1 - 1
src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs

@@ -44,7 +44,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
         public int Width { get; }
         public int Height { get; }
         public int RowBytes { get; }
-        public Size Dpi { get; } = new Size(96, 96);
+        public Vector Dpi { get; } = new Vector(96, 96);
         public PixelFormat Format { get; }
 
         [DllImport("android")]

+ 3 - 0
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@@ -10,6 +10,7 @@ using Avalonia.Platform;
 using System;
 using System.Collections.Generic;
 using System.Reactive.Disposables;
+using Avalonia.Android.Platform.Input;
 using Avalonia.Controls;
 using Avalonia.Controls.Platform.Surfaces;
 
@@ -65,6 +66,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
             }
         }
 
+        public IMouseDevice MouseDevice => AndroidMouseDevice.Instance;
+
         public Action Closed { get; set; }
 
         public Action<RawInputEventArgs> Input { get; set; }

+ 1 - 1
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs

@@ -71,7 +71,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
                 if (x <= _point.X && r >= _point.X && y <= _point.Y && b >= _point.Y)
                 {
                     var inputRoot = _getInputRoot();
-                    var mouseDevice = MouseDevice.Instance;
+                    var mouseDevice = Avalonia.Android.Platform.Input.AndroidMouseDevice.Instance;
 
                     //in order the controls to work in a predictable way
                     //we need to generate mouse move before first mouse down event

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

@@ -30,6 +30,7 @@
       <Link>Properties\SharedAssemblyInfo.cs</Link>
     </Compile>
   </ItemGroup>
+  <Import Project="..\..\build\Base.props" />
   <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\JetBrains.Annotations.props" />
 </Project>

+ 8 - 30
src/Avalonia.Base/Data/BindingNotification.cs

@@ -44,11 +44,7 @@ namespace Avalonia.Data
         public static readonly BindingNotification UnsetValue =
             new BindingNotification(AvaloniaProperty.UnsetValue);
 
-        // Null cannot be held in WeakReference as it's indistinguishable from an expired value so
-        // use this value in its place.
-        private static readonly object NullValue = new object();
-
-        private WeakReference<object> _value;
+        private object _value;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="BindingNotification"/> class.
@@ -56,7 +52,7 @@ namespace Avalonia.Data
         /// <param name="value">The binding value.</param>
         public BindingNotification(object value)
         {
-            _value = new WeakReference<object>(value ?? NullValue);
+            _value = value;
         }
 
         /// <summary>
@@ -73,6 +69,7 @@ namespace Avalonia.Data
 
             Error = error;
             ErrorType = errorType;
+            _value = AvaloniaProperty.UnsetValue;
         }
 
         /// <summary>
@@ -84,7 +81,7 @@ namespace Avalonia.Data
         public BindingNotification(Exception error, BindingErrorType errorType, object fallbackValue)
             : this(error, errorType)
         {
-            _value = new WeakReference<object>(fallbackValue ?? NullValue);
+            _value = fallbackValue;
         }
 
         /// <summary>
@@ -95,31 +92,12 @@ namespace Avalonia.Data
         /// If this property is read when <see cref="HasValue"/> is false then it will return
         /// <see cref="AvaloniaProperty.UnsetValue"/>.
         /// </remarks>
-        public object Value
-        {
-            get
-            {
-                if (_value != null)
-                {
-                    object result;
-
-                    if (_value.TryGetTarget(out result))
-                    {
-                        return result == NullValue ? null : result;
-                    }
-                }
-
-                // There's the possibility of a race condition in that HasValue can return true,
-                // and then the value is GC'd before Value is read. We should be ok though as
-                // we return UnsetValue which should be a safe alternative.
-                return AvaloniaProperty.UnsetValue;
-            }
-        }
+        public object Value => _value;
 
         /// <summary>
         /// Gets a value indicating whether <see cref="Value"/> should be pushed to the target.
         /// </summary>
-        public bool HasValue => _value != null;
+        public bool HasValue => _value != AvaloniaProperty.UnsetValue;
 
         /// <summary>
         /// Gets the error that occurred on the source, if any.
@@ -248,7 +226,7 @@ namespace Avalonia.Data
         /// </summary>
         public void ClearValue()
         {
-            _value = null;
+            _value = AvaloniaProperty.UnsetValue;
         }
 
         /// <summary>
@@ -256,7 +234,7 @@ namespace Avalonia.Data
         /// </summary>
         public void SetValue(object value)
         {
-            _value = new WeakReference<object>(value ?? NullValue);
+            _value = value;
         }
 
         /// <inheritdoc/>

+ 23 - 13
src/Avalonia.Controls/Button.cs

@@ -207,7 +207,11 @@ namespace Avalonia.Controls
         /// <param name="e">The event args.</param>
         protected virtual void OnClick(RoutedEventArgs e)
         {
-            Command?.Execute(CommandParameter);
+            if (Command != null)
+            {
+                Command.Execute(CommandParameter);
+                e.Handled = true;
+            }
         }
 
         /// <inheritdoc/>
@@ -215,13 +219,16 @@ namespace Avalonia.Controls
         {
             base.OnPointerPressed(e);
 
-            PseudoClasses.Add(":pressed");
-            e.Device.Capture(this);
-            e.Handled = true;
-
-            if (ClickMode == ClickMode.Press)
+            if (e.MouseButton == MouseButton.Left)
             {
-                RaiseClickEvent();
+                PseudoClasses.Add(":pressed");
+                e.Device.Capture(this);
+                e.Handled = true;
+
+                if (ClickMode == ClickMode.Press)
+                {
+                    RaiseClickEvent();
+                }
             }
         }
 
@@ -230,13 +237,16 @@ namespace Avalonia.Controls
         {
             base.OnPointerReleased(e);
 
-            e.Device.Capture(null);
-            PseudoClasses.Remove(":pressed");
-            e.Handled = true;
-
-            if (ClickMode == ClickMode.Release && Classes.Contains(":pointerover"))
+            if (e.MouseButton == MouseButton.Left)
             {
-                RaiseClickEvent();
+                e.Device.Capture(null);
+                PseudoClasses.Remove(":pressed");
+                e.Handled = true;
+
+                if (ClickMode == ClickMode.Release && new Rect(Bounds.Size).Contains(e.GetPosition(this)))
+                {
+                    RaiseClickEvent();
+                }
             }
         }
 

+ 1 - 1
src/Avalonia.Controls/ContextMenu.cs

@@ -19,7 +19,7 @@ namespace Avalonia.Controls
         {
             ContextMenuProperty.Changed.Subscribe(ContextMenuChanged);
 
-            MenuItem.ClickEvent.AddClassHandler<ContextMenu>(x => x.OnContextMenuClick);            
+            MenuItem.ClickEvent.AddClassHandler<ContextMenu>(x => x.OnContextMenuClick, handledEventsToo: true);            
         }
 
         /// <summary>

+ 9 - 2
src/Avalonia.Controls/Control.cs

@@ -118,6 +118,7 @@ namespace Avalonia.Controls
         public Control()
         {
             _nameScope = this as INameScope;
+            _isAttachedToLogicalTree = this is IStyleRoot;
         }
 
         /// <summary>
@@ -369,6 +370,12 @@ namespace Avalonia.Controls
             }
         }
 
+        /// <inheritdoc/>
+        void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
+        {
+            this.OnAttachedToLogicalTreeCore(e);
+        }
+
         /// <inheritdoc/>
         void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
         {
@@ -418,7 +425,7 @@ namespace Avalonia.Controls
 
                 if (_isAttachedToLogicalTree)
                 {
-                    var oldRoot = FindStyleRoot(old);
+                    var oldRoot = FindStyleRoot(old) ?? this as IStyleRoot;
 
                     if (oldRoot == null)
                     {
@@ -436,7 +443,7 @@ namespace Avalonia.Controls
 
                 _parent = (IControl)parent;
 
-                if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true)
+                if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true || this is IStyleRoot)
                 {
                     var newRoot = FindStyleRoot(this);
 

+ 6 - 4
src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs

@@ -22,6 +22,8 @@ namespace Avalonia.Controls.Embedding
         [CanBeNull]
         public new IEmbeddableWindowImpl PlatformImpl => (IEmbeddableWindowImpl) base.PlatformImpl;
 
+        protected bool EnforceClientSize { get; set; } = true;
+
         public void Prepare()
         {
             EnsureInitialized();
@@ -38,12 +40,12 @@ namespace Avalonia.Controls.Embedding
                 init.EndInit();
             }
         }
-
+        
         protected override Size MeasureOverride(Size availableSize)
         {
-            var cs = PlatformImpl?.ClientSize ?? default(Size);
-            base.MeasureOverride(cs);
-            return cs;
+            if (EnforceClientSize)
+                availableSize = PlatformImpl?.ClientSize ?? default(Size);
+            return base.MeasureOverride(availableSize);
         }
 
         private readonly NameScope _nameScope = new NameScope();

+ 1 - 1
src/Avalonia.Controls/Menu.cs

@@ -47,7 +47,7 @@ namespace Avalonia.Controls
         static Menu()
         {
             ItemsPanelProperty.OverrideDefaultValue(typeof(Menu), DefaultPanel);
-            MenuItem.ClickEvent.AddClassHandler<Menu>(x => x.OnMenuClick);
+            MenuItem.ClickEvent.AddClassHandler<Menu>(x => x.OnMenuClick, handledEventsToo: true);
             MenuItem.SubmenuOpenedEvent.AddClassHandler<Menu>(x => x.OnSubmenuOpened);
         }
 

+ 10 - 1
src/Avalonia.Controls/MenuItem.cs

@@ -102,6 +102,11 @@ namespace Avalonia.Controls
             AccessKeyHandler.AccessKeyPressedEvent.AddClassHandler<MenuItem>(x => x.AccessKeyPressed);
         }
 
+        public MenuItem()
+        {
+
+        }
+
         /// <summary>
         /// Occurs when a <see cref="MenuItem"/> without a submenu is clicked.
         /// </summary>
@@ -192,7 +197,11 @@ namespace Avalonia.Controls
         /// <param name="e">The click event args.</param>
         protected virtual void OnClick(RoutedEventArgs e)
         {
-            Command?.Execute(CommandParameter);
+            if (Command != null)
+            {
+                Command.Execute(CommandParameter);
+                e.Handled = true;
+            }
         }
 
         /// <summary>

+ 7 - 0
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

@@ -5,6 +5,7 @@ using System;
 using System.Collections.Generic;
 using Avalonia.Input;
 using Avalonia.Input.Raw;
+using JetBrains.Annotations;
 
 namespace Avalonia.Platform
 {
@@ -93,5 +94,11 @@ namespace Avalonia.Platform
         /// Gets or sets a method called when the underlying implementation is destroyed.
         /// </summary>
         Action Closed { get; set; }
+
+        /// <summary>
+        /// Gets a mouse device associated with toplevel
+        /// </summary>
+        [CanBeNull]
+        IMouseDevice MouseDevice { get; }
     }
 }

+ 24 - 14
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@@ -8,6 +8,7 @@ using Avalonia.Controls.Templates;
 using Avalonia.Layout;
 using Avalonia.LogicalTree;
 using Avalonia.Media;
+using Avalonia.VisualTree;
 
 namespace Avalonia.Controls.Presenters
 {
@@ -88,6 +89,7 @@ namespace Avalonia.Controls.Presenters
         static ContentPresenter()
         {
             ContentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
+            ContentTemplateProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
             TemplatedParentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.TemplatedParentChanged);
         }
 
@@ -313,27 +315,22 @@ namespace Avalonia.Controls.Presenters
 
             if (content != null && newChild == null)
             {
-                // We have content and it isn't a control, so first try to recycle the existing
-                // child control to display the new data by querying if the template that created
-                // the child can recycle items and that it also matches the new data.
-                if (oldChild != null &&
-                    _dataTemplate != null &&
-                    _dataTemplate.SupportsRecycling &&
-                    _dataTemplate.Match(content))
+                var dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default;
+
+                // We have content and it isn't a control, so if the new data template is the same
+                // as the old data template, try to recycle the existing child control to display
+                // the new data.
+                if (dataTemplate == _dataTemplate && dataTemplate.SupportsRecycling)
                 {
                     newChild = oldChild;
                 }
                 else
                 {
-                    // We couldn't recycle an existing control so find a data template for the data
-                    // and use it to create a control.
-                    _dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default;
+                    _dataTemplate = dataTemplate;
                     newChild = _dataTemplate.Build(content);
 
-                    // Try to give the new control its own name scope.
-                    var controlResult = newChild as Control;
-
-                    if (controlResult != null)
+                    // Give the new control its own name scope.
+                    if (newChild is Control controlResult)
                     {
                         NameScope.SetNameScope(controlResult, new NameScope());
                     }
@@ -424,6 +421,19 @@ namespace Avalonia.Controls.Presenters
         private void ContentChanged(AvaloniaPropertyChangedEventArgs e)
         {
             _createdChild = false;
+
+            if (((ILogical)this).IsAttachedToLogicalTree)
+            {
+                UpdateChild();
+            }
+            else if (Child != null)
+            {
+                VisualChildren.Remove(Child);
+                LogicalChildren.Remove(Child);
+                Child = null;
+                _dataTemplate = null;
+            }
+
             InvalidateMeasure();
         }
 

+ 2 - 2
src/Avalonia.Controls/Primitives/Popup.cs

@@ -340,11 +340,11 @@ namespace Avalonia.Controls.Primitives
             switch (mode)
             {
                 case PlacementMode.Pointer:
-                    if (MouseDevice.Instance != null)
+                    if(PopupRoot != null)
                     {
                         // Scales the Horizontal and Vertical offset to screen co-ordinates.
                         var screenOffset = new Point(HorizontalOffset * (PopupRoot as ILayoutRoot).LayoutScaling, VerticalOffset * (PopupRoot as ILayoutRoot).LayoutScaling);
-                        return MouseDevice.Instance.Position + screenOffset;
+                        return (((IInputRoot)PopupRoot)?.MouseDevice?.Position ?? default(Point)) + screenOffset;
                     }
 
                     return default(Point);

+ 11 - 0
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@@ -285,6 +285,17 @@ namespace Avalonia.Controls.Primitives
             return this;
         }
 
+        /// <inheritdoc/>
+        protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
+        {
+            if (VisualChildren.Count > 0)
+            {
+                ((ILogical)VisualChildren[0]).NotifyAttachedToLogicalTree(e);
+            }
+
+            base.OnAttachedToLogicalTree(e);
+        }
+
         /// <inheritdoc/>
         protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
         {

+ 7 - 2
src/Avalonia.Controls/TextBox.cs

@@ -236,6 +236,11 @@ namespace Avalonia.Controls
         {
             _presenter = e.NameScope.Get<TextPresenter>("PART_TextPresenter");
             _presenter.Cursor = new Cursor(StandardCursorType.Ibeam);
+
+            if(IsFocused)
+            {
+                _presenter.ShowCaret();
+            }
         }
 
         protected override void OnGotFocus(GotFocusEventArgs e)
@@ -254,7 +259,7 @@ namespace Avalonia.Controls
             }
             else
             {
-                _presenter.ShowCaret();
+                _presenter?.ShowCaret();
             }
         }
 
@@ -263,7 +268,7 @@ namespace Avalonia.Controls
             base.OnLostFocus(e);
             SelectionStart = 0;
             SelectionEnd = 0;
-            _presenter.HideCaret();
+            _presenter?.HideCaret();
         }
 
         protected override void OnTextInput(TextInputEventArgs e)

+ 34 - 19
src/Avalonia.Controls/ToolTip.cs

@@ -105,21 +105,29 @@ namespace Avalonia.Controls
         {
             if (control != null && control.IsVisible && control.GetVisualRoot() != null)
             {
-                if (s_popup != null)
-                {
-                    throw new AvaloniaInternalException("Previous ToolTip not disposed.");
-                }
+                var cp = (control.GetVisualRoot() as IInputRoot)?.MouseDevice?.GetPosition(control);
 
-                var cp = MouseDevice.Instance?.GetPosition(control);
-                var position = control.PointToScreen(cp ?? new Point(0, 0)) + new Vector(0, 22);
+                if (cp.HasValue && control.IsVisible && new Rect(control.Bounds.Size).Contains(cp.Value))
+                {
+                    var position = control.PointToScreen(cp.Value) + new Vector(0, 22);
+
+                    if (s_popup == null)
+                    {
+                        s_popup = new PopupRoot();
+                        s_popup.Content = new ToolTip();
+                    }
+                    else
+                    {
+                        ((ISetLogicalParent)s_popup).SetParent(null);
+                    }
 
-                s_popup = new PopupRoot();
                 ((ISetLogicalParent)s_popup).SetParent(control);
-                s_popup.Content = new ToolTip { Content = GetTip(control) };
-                s_popup.Position = position;
-                s_popup.Show();
+                    ((ToolTip)s_popup.Content).Content = GetTip(control);
+                    s_popup.Position = position;
+                    s_popup.Show();
 
-                s_current = control;
+                    s_current = control;
+                }
             }
         }
 
@@ -147,16 +155,23 @@ namespace Avalonia.Controls
             {
                 if (s_popup != null)
                 {
-                    // Clear the ToolTip's Content in case it has control content: this will
-                    // reset its visual parent allowing it to be used again.
-                    ((ToolTip)s_popup.Content).Content = null;
-
-                    // Dispose of the popup.
-                    s_popup.Dispose();
-                    s_popup = null;
+                    DisposeTooltip();
+                    s_show.OnNext(null);
                 }
+            }
+        }
+
+        private static void DisposeTooltip()
+        {
+            if (s_popup != null)
+            {
+                // Clear the ToolTip's Content in case it has control content: this will
+                // reset its visual parent allowing it to be used again.
+                ((ToolTip)s_popup.Content).Content = null;
 
-                s_show.OnNext(null);
+                // Dispose of the popup.
+                s_popup.Dispose();
+                s_popup = null;
             }
         }
     }

+ 3 - 0
src/Avalonia.Controls/TopLevel.cs

@@ -163,6 +163,9 @@ namespace Avalonia.Controls
             set { SetValue(PointerOverElementProperty, value); }
         }
 
+        /// <inheritdoc/>
+        IMouseDevice IInputRoot.MouseDevice => PlatformImpl?.MouseDevice;
+
         /// <summary>
         /// Gets or sets a value indicating whether access keys are shown in the window.
         /// </summary>

+ 21 - 1
src/Avalonia.Controls/TreeView.cs

@@ -16,7 +16,7 @@ namespace Avalonia.Controls
     /// <summary>
     /// Displays a hierachical tree of data.
     /// </summary>
-    public class TreeView : ItemsControl
+    public class TreeView : ItemsControl, ICustomKeyboardNavigation
     {
         /// <summary>
         /// Defines the <see cref="AutoScrollToSelectedItem"/> property.
@@ -90,6 +90,26 @@ namespace Avalonia.Controls
             }
         }
 
+        (bool handled, IInputElement next) ICustomKeyboardNavigation.GetNext(IInputElement element, NavigationDirection direction)
+        {
+            if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous)
+            {
+                if (!this.IsVisualAncestorOf(element))
+                {
+                    IControl result = _selectedItem != null ?
+                        ItemContainerGenerator.Index.ContainerFromItem(_selectedItem) :
+                        ItemContainerGenerator.ContainerFromIndex(0);
+                    return (true, result);
+                }
+                else
+                {
+                    return (true, null);
+                }
+            }
+
+            return (false, null);
+        }
+
         /// <inheritdoc/>
         protected override IItemContainerGenerator CreateItemContainerGenerator()
         {

+ 8 - 1
src/Avalonia.Controls/WindowBase.cs

@@ -29,6 +29,7 @@ namespace Avalonia.Controls
         public static readonly DirectProperty<WindowBase, bool> IsActiveProperty =
             AvaloniaProperty.RegisterDirect<WindowBase, bool>(nameof(IsActive), o => o.IsActive);
 
+        private bool _hasExecutedInitialLayoutPass;
         private bool _isActive;
         private bool _ignoreVisibilityChange;
 
@@ -136,7 +137,13 @@ namespace Avalonia.Controls
             {
                 EnsureInitialized();
                 IsVisible = true;
-                LayoutManager.Instance.ExecuteInitialLayoutPass(this);
+
+                if (!_hasExecutedInitialLayoutPass)
+                {
+                    LayoutManager.Instance.ExecuteInitialLayoutPass(this);
+                    _hasExecutedInitialLayoutPass = true;
+                }
+
                 PlatformImpl?.Show();
             }
             finally

+ 13 - 14
src/Avalonia.Diagnostics/DevTools.xaml.cs

@@ -14,11 +14,11 @@ using Avalonia.VisualTree;
 
 namespace Avalonia
 {
-	public static class WindowExtensions
+	public static class DevToolsExtensions
 	{
-		public static void AttachDevTools(this Window window)
+		public static void AttachDevTools(this TopLevel control)
 		{
-			Avalonia.Diagnostics.DevTools.Attach(window);
+			Avalonia.Diagnostics.DevTools.Attach(control);
 		}
 	}
 }
@@ -27,7 +27,7 @@ namespace Avalonia.Diagnostics
 {
 	public class DevTools : UserControl
     {
-        private static Dictionary<Window, Window> s_open = new Dictionary<Window, Window>();
+        private static Dictionary<TopLevel, Window> s_open = new Dictionary<TopLevel, Window>();
         private IDisposable _keySubscription;
 
         public DevTools(IControl root)
@@ -43,9 +43,9 @@ namespace Avalonia.Diagnostics
 
         public IControl Root { get; }
 
-        public static IDisposable Attach(Window window)
+        public static IDisposable Attach(TopLevel control)
         {
-            return window.AddHandler(
+            return control.AddHandler(
                 KeyDownEvent,
                 WindowPreviewKeyDown,
                 RoutingStrategies.Tunnel);
@@ -55,16 +55,16 @@ namespace Avalonia.Diagnostics
         {
             if (e.Key == Key.F12)
             {
-                var window = (Window)sender;
+                var control = (TopLevel)sender;
                 var devToolsWindow = default(Window);
 
-                if (s_open.TryGetValue(window, out devToolsWindow))
+                if (s_open.TryGetValue(control, out devToolsWindow))
                 {
                     devToolsWindow.Activate();
                 }
                 else
                 {
-                    var devTools = new DevTools(window);
+                    var devTools = new DevTools(control);
 
                     devToolsWindow = new Window
                     {
@@ -78,7 +78,7 @@ namespace Avalonia.Diagnostics
                     };
 
                     devToolsWindow.Closed += devTools.DevToolsClosed;
-                    s_open.Add((Window)sender, devToolsWindow);
+                    s_open.Add(control, devToolsWindow);
                     devToolsWindow.Show();
                 }
             }
@@ -88,9 +88,7 @@ namespace Avalonia.Diagnostics
         {
             var devToolsWindow = (Window)sender;
             var devTools = (DevTools)devToolsWindow.Content;
-            var window = (Window)devTools.Root;
-
-            s_open.Remove(window);
+            s_open.Remove((TopLevel)devTools.Root);
             _keySubscription.Dispose();
             devToolsWindow.Closed -= DevToolsClosed;
         }
@@ -106,7 +104,8 @@ namespace Avalonia.Diagnostics
 
             if ((e.Modifiers) == modifiers)
             {
-                var point = MouseDevice.Instance.GetPosition(Root);
+
+                var point = (Root.VisualRoot as IInputRoot)?.MouseDevice?.GetPosition(Root) ?? default(Point);
                 var control = Root.GetVisualsAt(point, x => (!(x is AdornerLayer) && x.IsVisible))
                     .FirstOrDefault();
 

+ 4 - 1
src/Avalonia.HtmlRenderer/Adapters/ControlAdapter.cs

@@ -10,9 +10,11 @@
 // - Sun Tsu,
 // "The Art of War"
 
+using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls.Html;
 using Avalonia.Input;
+using Avalonia.VisualTree;
 using TheArtOfDev.HtmlRenderer.Adapters;
 using TheArtOfDev.HtmlRenderer.Adapters.Entities;
 using TheArtOfDev.HtmlRenderer.Core.Utils;
@@ -54,7 +56,8 @@ namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters
         {
             get
             {
-                return Util.Convert(MouseDevice.Instance.GetPosition(_control));
+                var pos = (_control.GetVisualRoot() as IInputRoot)?.MouseDevice?.Position ?? default(Point);
+                return Util.Convert(pos);
             }
         }
 

+ 2 - 1
src/Avalonia.HtmlRenderer/HtmlControl.cs

@@ -17,6 +17,7 @@ using Avalonia.Input;
 using Avalonia.Interactivity;
 using Avalonia.Media;
 using Avalonia.Threading;
+using Avalonia.VisualTree;
 using TheArtOfDev.HtmlRenderer.Core;
 using TheArtOfDev.HtmlRenderer.Core.Entities;
 using TheArtOfDev.HtmlRenderer.Avalonia;
@@ -512,7 +513,7 @@ namespace Avalonia.Controls.Html
         protected virtual void InvokeMouseMove()
         {
 
-            _htmlContainer.HandleMouseMove(this, MouseDevice.Instance?.GetPosition(this) ?? default(Point));
+            _htmlContainer.HandleMouseMove(this, (this.GetVisualRoot() as IInputRoot)?.MouseDevice?.GetPosition(this) ?? default(Point));
         }
 
         /// <summary>

+ 3 - 2
src/Avalonia.Input/FocusManager.cs

@@ -176,9 +176,10 @@ namespace Avalonia.Input
         /// <param name="e">The event args.</param>
         private void OnPreviewPointerPressed(object sender, RoutedEventArgs e)
         {
-            if (sender == e.Source)
+            var ev = (PointerPressedEventArgs)e;
+
+            if (sender == e.Source && ev.MouseButton == MouseButton.Left)
             {
-                var ev = (PointerPressedEventArgs)e;
                 var element = (ev.Device?.Captured as IInputElement) ?? (e.Source as IInputElement);
 
                 if (element == null || !CanFocus(element))

+ 15 - 0
src/Avalonia.Input/ICustomKeyboardNavigation.cs

@@ -0,0 +1,15 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+
+namespace Avalonia.Input
+{
+    /// <summary>
+    /// Designates a control as handling its own keyboard navigation.
+    /// </summary>
+    public interface ICustomKeyboardNavigation
+    {
+        (bool handled, IInputElement next) GetNext(IInputElement element, NavigationDirection direction);
+    }
+}

+ 7 - 0
src/Avalonia.Input/IInputDevice.cs

@@ -1,9 +1,16 @@
 // 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 Avalonia.Input.Raw;
+
 namespace Avalonia.Input
 {
     public interface IInputDevice
     {
+        /// <summary>
+        /// Processes raw event. Is called after preprocessing by InputManager
+        /// </summary>
+        /// <param name="ev"></param>
+        void ProcessRawEvent(RawInputEventArgs ev);
     }
 }

+ 8 - 0
src/Avalonia.Input/IInputRoot.cs

@@ -1,6 +1,8 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
+using JetBrains.Annotations;
+
 namespace Avalonia.Input
 {
     /// <summary>
@@ -27,5 +29,11 @@ namespace Avalonia.Input
         /// Gets or sets a value indicating whether access keys are shown in the window.
         /// </summary>
         bool ShowAccessKeys { get; set; }
+
+        /// <summary>
+        /// Gets associated mouse device
+        /// </summary>
+        [CanBeNull]
+        IMouseDevice MouseDevice { get; }
     }
 }

+ 1 - 0
src/Avalonia.Input/InputManager.cs

@@ -35,6 +35,7 @@ namespace Avalonia.Input
         public void ProcessInput(RawInputEventArgs e)
         {
             _preProcess.OnNext(e);
+            e.Device?.ProcessRawEvent(e);
             _process.OnNext(e);
             _postProcess.OnNext(e);
         }

+ 3 - 9
src/Avalonia.Input/KeyboardDevice.cs

@@ -16,14 +16,6 @@ namespace Avalonia.Input
     {
         private IInputElement _focusedElement;
 
-        public KeyboardDevice()
-        {
-            InputManager.Process
-                .OfType<RawInputEventArgs>()
-                .Where(e => e.Device == this && !e.Handled)
-                .Subscribe(ProcessRawEvent);
-        }
-
         public event PropertyChangedEventHandler PropertyChanged;
 
         public static IKeyboardDevice Instance => AvaloniaLocator.Current.GetService<IKeyboardDevice>();
@@ -77,8 +69,10 @@ namespace Avalonia.Input
             PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
         }
 
-        private void ProcessRawEvent(RawInputEventArgs e)
+        public void ProcessRawEvent(RawInputEventArgs e)
         {
+            if(e.Handled)
+                return;
             IInputElement element = FocusedElement;
 
             if (element != null)

+ 27 - 0
src/Avalonia.Input/KeyboardNavigationHandler.cs

@@ -2,7 +2,9 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using System.Linq;
 using Avalonia.Input.Navigation;
+using Avalonia.VisualTree;
 
 namespace Avalonia.Input
 {
@@ -52,6 +54,31 @@ namespace Avalonia.Input
         {
             Contract.Requires<ArgumentNullException>(element != null);
 
+            var customHandler = element.GetSelfAndVisualAncestors()
+                .OfType<ICustomKeyboardNavigation>()
+                .FirstOrDefault();
+
+            if (customHandler != null)
+            {
+                var (handled, next) = customHandler.GetNext(element, direction);
+
+                if (handled)
+                {
+                    if (next != null)
+                    {
+                        return next;
+                    }
+                    else if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous)
+                    {
+                        return TabNavigation.GetNextInTabOrder((IInputElement)customHandler, direction, true);
+                    }
+                    else
+                    {
+                        return null;
+                    }
+                }
+            }
+
             if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous)
             {
                 return TabNavigation.GetNextInTabOrder(element, direction);

+ 8 - 23
src/Avalonia.Input/MouseDevice.cs

@@ -20,23 +20,7 @@ namespace Avalonia.Input
         private int _clickCount;
         private Rect _lastClickRect;
         private uint _lastClickTime;
-
-        /// <summary>
-        /// Intializes a new instance of <see cref="MouseDevice"/>.
-        /// </summary>
-        public MouseDevice()
-        {
-            InputManager.Process
-                .OfType<RawMouseEventArgs>()
-                .Where(e => e.Device == this && !e.Handled)
-                .Subscribe(ProcessRawEvent);
-        }
-
-        /// <summary>
-        /// Gets the current mouse device instance.
-        /// </summary>
-        public static IMouseDevice Instance => AvaloniaLocator.Current.GetService<IMouseDevice>();
-
+       
         /// <summary>
         /// Gets the control that is currently capturing by the mouse, if any.
         /// </summary>
@@ -50,12 +34,7 @@ namespace Avalonia.Input
             get;
             protected set;
         }
-
-        /// <summary>
-        /// Gets the application's input manager.
-        /// </summary>
-        public IInputManager InputManager => AvaloniaLocator.Current.GetService<IInputManager>();
-
+        
         /// <summary>
         /// Gets the mouse position, in screen coordinates.
         /// </summary>
@@ -102,6 +81,12 @@ namespace Avalonia.Input
             return root.PointToClient(Position) - p;
         }
 
+        public void ProcessRawEvent(RawInputEventArgs e)
+        {
+            if (!e.Handled && e is RawMouseEventArgs margs)
+                ProcessRawEvent(margs);
+        }
+
         private void ProcessRawEvent(RawMouseEventArgs e)
         {
             Contract.Requires<ArgumentNullException>(e != null);

+ 14 - 2
src/Avalonia.Input/Navigation/DirectionalNavigation.cs

@@ -41,7 +41,7 @@ namespace Avalonia.Input.Navigation
                 {
                     case KeyboardNavigationMode.Continue:
                         return GetNextInContainer(element, container, direction) ??
-                               GetFirstInNextContainer(element, direction);
+                               GetFirstInNextContainer(element, element, direction);
                     case KeyboardNavigationMode.Cycle:
                         return GetNextInContainer(element, container, direction) ??
                                GetFocusableDescendant(container, direction);
@@ -173,10 +173,12 @@ namespace Avalonia.Input.Navigation
         /// <summary>
         /// Gets the first item that should be focused in the next container.
         /// </summary>
+        /// <param name="element">The element being navigated away from.</param>
         /// <param name="container">The container.</param>
         /// <param name="direction">The direction of the search.</param>
         /// <returns>The first element, or null if there are no more elements.</returns>
         private static IInputElement GetFirstInNextContainer(
+            IInputElement element,
             IInputElement container,
             NavigationDirection direction)
         {
@@ -200,6 +202,16 @@ namespace Avalonia.Input.Navigation
 
                 if (sibling != null)
                 {
+                    if (sibling is ICustomKeyboardNavigation custom)
+                    {
+                        var (handled, customNext) = custom.GetNext(element, direction);
+
+                        if (handled)
+                        {
+                            return customNext;
+                        }
+                    }
+
                     if (sibling.CanFocus())
                     {
                         next = sibling;
@@ -214,7 +226,7 @@ namespace Avalonia.Input.Navigation
 
                 if (next == null)
                 {
-                    next = GetFirstInNextContainer(parent, direction);
+                    next = GetFirstInNextContainer(element, parent, direction);
                 }
             }
             else

+ 62 - 25
src/Avalonia.Input/Navigation/TabNavigation.cs

@@ -18,13 +18,17 @@ namespace Avalonia.Input.Navigation
         /// </summary>
         /// <param name="element">The element.</param>
         /// <param name="direction">The tab direction. Must be Next or Previous.</param>
+        /// <param name="outsideElement">
+        /// If true will not descend into <paramref name="element"/> to find next control.
+        /// </param>
         /// <returns>
         /// The next element in the specified direction, or null if <paramref name="element"/>
         /// was the last in the requested direction.
         /// </returns>
         public static IInputElement GetNextInTabOrder(
             IInputElement element,
-            NavigationDirection direction)
+            NavigationDirection direction,
+            bool outsideElement = false)
         {
             Contract.Requires<ArgumentNullException>(element != null);
             Contract.Requires<ArgumentException>(
@@ -40,20 +44,20 @@ namespace Avalonia.Input.Navigation
                 switch (mode)
                 {
                     case KeyboardNavigationMode.Continue:
-                        return GetNextInContainer(element, container, direction) ??
-                               GetFirstInNextContainer(element, direction);
+                        return GetNextInContainer(element, container, direction, outsideElement) ??
+                               GetFirstInNextContainer(element, element, direction);
                     case KeyboardNavigationMode.Cycle:
-                        return GetNextInContainer(element, container, direction) ??
+                        return GetNextInContainer(element, container, direction, outsideElement) ??
                                GetFocusableDescendant(container, direction);
                     case KeyboardNavigationMode.Contained:
-                        return GetNextInContainer(element, container, direction);
+                        return GetNextInContainer(element, container, direction, outsideElement);
                     default:
-                        return GetFirstInNextContainer(container, direction);
+                        return GetFirstInNextContainer(element, container, direction);
                 }
             }
             else
             {
-                return GetFocusableDescendants(element).FirstOrDefault();
+                return GetFocusableDescendants(element, direction).FirstOrDefault();
             }
         }
 
@@ -66,16 +70,17 @@ namespace Avalonia.Input.Navigation
         private static IInputElement GetFocusableDescendant(IInputElement container, NavigationDirection direction)
         {
             return direction == NavigationDirection.Next ?
-                GetFocusableDescendants(container).FirstOrDefault() :
-                GetFocusableDescendants(container).LastOrDefault();
+                GetFocusableDescendants(container, direction).FirstOrDefault() :
+                GetFocusableDescendants(container, direction).LastOrDefault();
         }
 
         /// <summary>
         /// Gets the focusable descendants of the specified element.
         /// </summary>
         /// <param name="element">The element.</param>
+        /// <param name="direction">The tab direction. Must be Next or Previous.</param>
         /// <returns>The element's focusable descendants.</returns>
-        private static IEnumerable<IInputElement> GetFocusableDescendants(IInputElement element)
+        private static IEnumerable<IInputElement> GetFocusableDescendants(IInputElement element, NavigationDirection direction)
         {
             var mode = KeyboardNavigation.GetTabNavigation((InputElement)element);
 
@@ -103,16 +108,25 @@ namespace Avalonia.Input.Navigation
 
             foreach (var child in children)
             {
-                if (child.CanFocus())
+                var customNext = GetCustomNext(child, direction);
+
+                if (customNext.handled)
                 {
-                    yield return child;
+                    yield return customNext.next;
                 }
-
-                if (child.CanFocusDescendants())
+                else
                 {
-                    foreach (var descendant in GetFocusableDescendants(child))
+                    if (child.CanFocus())
                     {
-                        yield return descendant;
+                        yield return child;
+                    }
+
+                    if (child.CanFocusDescendants())
+                    {
+                        foreach (var descendant in GetFocusableDescendants(child, direction))
+                        {
+                            yield return descendant;
+                        }
                     }
                 }
             }
@@ -124,15 +138,19 @@ namespace Avalonia.Input.Navigation
         /// <param name="element">The starting element/</param>
         /// <param name="container">The container.</param>
         /// <param name="direction">The direction.</param>
+        /// <param name="outsideElement">
+        /// If true will not descend into <paramref name="element"/> to find next control.
+        /// </param>
         /// <returns>The next element, or null if the element is the last.</returns>
         private static IInputElement GetNextInContainer(
             IInputElement element,
             IInputElement container,
-            NavigationDirection direction)
+            NavigationDirection direction,
+            bool outsideElement)
         {
-            if (direction == NavigationDirection.Next)
+            if (direction == NavigationDirection.Next && !outsideElement)
             {
-                var descendant = GetFocusableDescendants(element).FirstOrDefault();
+                var descendant = GetFocusableDescendants(element, direction).FirstOrDefault();
 
                 if (descendant != null)
                 {
@@ -167,7 +185,7 @@ namespace Avalonia.Input.Navigation
 
                 if (element != null && direction == NavigationDirection.Previous)
                 {
-                    var descendant = GetFocusableDescendants(element).LastOrDefault();
+                    var descendant = GetFocusableDescendants(element, direction).LastOrDefault();
 
                     if (descendant != null)
                     {
@@ -184,10 +202,12 @@ namespace Avalonia.Input.Navigation
         /// <summary>
         /// Gets the first item that should be focused in the next container.
         /// </summary>
+        /// <param name="element">The element being navigated away from.</param>
         /// <param name="container">The container.</param>
         /// <param name="direction">The direction of the search.</param>
         /// <returns>The first element, or null if there are no more elements.</returns>
         private static IInputElement GetFirstInNextContainer(
+            IInputElement element,
             IInputElement container,
             NavigationDirection direction)
         {
@@ -210,6 +230,13 @@ namespace Avalonia.Input.Navigation
 
                 if (sibling != null)
                 {
+                    var customNext = GetCustomNext(sibling, direction);
+
+                    if (customNext.handled)
+                    {
+                        return customNext.next;
+                    }
+
                     if (sibling.CanFocus())
                     {
                         next = sibling;
@@ -217,24 +244,34 @@ namespace Avalonia.Input.Navigation
                     else
                     {
                         next = direction == NavigationDirection.Next ?
-                            GetFocusableDescendants(sibling).FirstOrDefault() :
-                            GetFocusableDescendants(sibling).LastOrDefault();
+                            GetFocusableDescendants(sibling, direction).FirstOrDefault() :
+                            GetFocusableDescendants(sibling, direction).LastOrDefault();
                     }
                 }
 
                 if (next == null)
                 {
-                    next = GetFirstInNextContainer(parent, direction);
+                    next = GetFirstInNextContainer(element, parent, direction);
                 }
             }
             else
             {
                 next = direction == NavigationDirection.Next ?
-                    GetFocusableDescendants(container).FirstOrDefault() :
-                    GetFocusableDescendants(container).LastOrDefault();
+                    GetFocusableDescendants(container, direction).FirstOrDefault() :
+                    GetFocusableDescendants(container, direction).LastOrDefault();
             }
 
             return next;
         }
+
+        private static (bool handled, IInputElement next) GetCustomNext(IInputElement element, NavigationDirection direction)
+        {
+            if (element is ICustomKeyboardNavigation custom)
+            {
+                return custom.GetNext(element, direction);
+            }
+
+            return (false, null);
+        }
     }
 }

+ 10 - 0
src/Avalonia.Layout/IEmbeddedLayoutRoot.cs

@@ -0,0 +1,10 @@
+namespace Avalonia.Layout
+{
+    /// <summary>
+    /// A special layout root with enforced size for Arrange pass
+    /// </summary>
+    public interface IEmbeddedLayoutRoot : ILayoutRoot
+    {
+        Size AllocatedSize { get; }
+    }
+}

+ 66 - 33
src/Avalonia.Layout/LayoutManager.cs

@@ -14,8 +14,8 @@ namespace Avalonia.Layout
     /// </summary>
     public class LayoutManager : ILayoutManager
     {
-        private readonly HashSet<ILayoutable> _toMeasure = new HashSet<ILayoutable>();
-        private readonly HashSet<ILayoutable> _toArrange = new HashSet<ILayoutable>();
+        private readonly Queue<ILayoutable> _toMeasure = new Queue<ILayoutable>();
+        private readonly Queue<ILayoutable> _toArrange = new Queue<ILayoutable>();
         private bool _queued;
         private bool _running;
 
@@ -30,8 +30,18 @@ namespace Avalonia.Layout
             Contract.Requires<ArgumentNullException>(control != null);
             Dispatcher.UIThread.VerifyAccess();
 
-            _toMeasure.Add(control);
-            _toArrange.Add(control);
+            if (!control.IsAttachedToVisualTree)
+            {
+#if DEBUG
+                throw new AvaloniaInternalException(
+                    "LayoutManager.InvalidateMeasure called on a control that is detached from the visual tree.");
+#else
+                return;
+#endif
+            }
+
+            _toMeasure.Enqueue(control);
+            _toArrange.Enqueue(control);
             QueueLayoutPass();
         }
 
@@ -41,7 +51,17 @@ namespace Avalonia.Layout
             Contract.Requires<ArgumentNullException>(control != null);
             Dispatcher.UIThread.VerifyAccess();
 
-            _toArrange.Add(control);
+            if (!control.IsAttachedToVisualTree)
+            {
+#if DEBUG
+                throw new AvaloniaInternalException(
+                    "LayoutManager.InvalidateArrange called on a control that is detached from the visual tree.");
+#else
+                return;
+#endif
+            }
+
+            _toArrange.Enqueue(control);
             QueueLayoutPass();
         }
 
@@ -108,8 +128,12 @@ namespace Avalonia.Layout
         {
             while (_toMeasure.Count > 0)
             {
-                var next = _toMeasure.First();
-                Measure(next);
+                var control = _toMeasure.Dequeue();
+
+                if (!control.IsMeasureValid && control.IsAttachedToVisualTree)
+                {
+                    Measure(control);
+                }
             }
         }
 
@@ -117,53 +141,62 @@ namespace Avalonia.Layout
         {
             while (_toArrange.Count > 0 && _toMeasure.Count == 0)
             {
-                var next = _toArrange.First();
-                Arrange(next);
+                var control = _toArrange.Dequeue();
+
+                if (!control.IsArrangeValid && control.IsAttachedToVisualTree)
+                {
+                    Arrange(control);
+                }
             }
         }
 
         private void Measure(ILayoutable control)
         {
-            var root = control as ILayoutRoot;
-            var parent = control.VisualParent as ILayoutable;
-
-            if (root != null)
-            {
-                root.Measure(root.MaxClientSize);
-            }
-            else if (parent != null)
+            // Controls closest to the visual root need to be arranged first. We don't try to store
+            // ordered invalidation lists, instead we traverse the tree upwards, measuring the
+            // controls closest to the root first. This has been shown by benchmarks to be the
+            // fastest and most memory-efficent algorithm.
+            if (control.VisualParent is ILayoutable parent)
             {
                 Measure(parent);
             }
 
-            if (!control.IsMeasureValid)
+            // If the control being measured has IsMeasureValid == true here then its measure was
+            // handed by an ancestor and can be ignored. The measure may have also caused the
+            // control to be removed.
+            if (!control.IsMeasureValid && control.IsAttachedToVisualTree)
             {
-                control.Measure(control.PreviousMeasure.Value);
+                if (control is ILayoutRoot root)
+                {
+                    root.Measure(Size.Infinity);
+                }
+                else
+                {
+                    control.Measure(control.PreviousMeasure.Value);
+                }
             }
-
-            _toMeasure.Remove(control);
         }
 
         private void Arrange(ILayoutable control)
         {
-            var root = control as ILayoutRoot;
-            var parent = control.VisualParent as ILayoutable;
-
-            if (root != null)
-            {
-                root.Arrange(new Rect(root.DesiredSize));
-            }
-            else if (parent != null)
+            if (control.VisualParent is ILayoutable parent)
             {
                 Arrange(parent);
             }
 
-            if (control.PreviousArrange.HasValue)
+            if (!control.IsArrangeValid && control.IsAttachedToVisualTree)
             {
-                control.Arrange(control.PreviousArrange.Value);
+                if (control is IEmbeddedLayoutRoot embeddedRoot)
+                    control.Arrange(new Rect(embeddedRoot.AllocatedSize));
+                else if (control is ILayoutRoot root)
+                    control.Arrange(new Rect(root.DesiredSize));
+                else if (control.PreviousArrange != null)
+                {
+                    // Has been observed that PreviousArrange sometimes is null, probably a bug somewhere else.
+                    // Condition observed: control.VisualParent is Scrollbar, control is Border.
+                    control.Arrange(control.PreviousArrange.Value);
+                }
             }
-
-            _toArrange.Remove(control);
         }
 
         private void QueueLayoutPass()

+ 21 - 4
src/Avalonia.Layout/Layoutable.cs

@@ -367,6 +367,14 @@ namespace Avalonia.Layout
             }
         }
 
+
+        /// <summary>
+        /// Called by InvalidateMeasure
+        /// </summary>
+        protected virtual void OnMeasureInvalidated()
+        {
+        }
+
         /// <summary>
         /// Invalidates the measurement of the control and queues a new layout pass.
         /// </summary>
@@ -378,8 +386,13 @@ namespace Avalonia.Layout
 
                 IsMeasureValid = false;
                 IsArrangeValid = false;
-                LayoutManager.Instance?.InvalidateMeasure(this);
-                InvalidateVisual();
+
+                if (((ILayoutable)this).IsAttachedToVisualTree)
+                {
+                    LayoutManager.Instance?.InvalidateMeasure(this);
+                    InvalidateVisual();
+                }
+                OnMeasureInvalidated();
             }
         }
 
@@ -393,8 +406,12 @@ namespace Avalonia.Layout
                 Logger.Verbose(LogArea.Layout, this, "Invalidated arrange");
 
                 IsArrangeValid = false;
-                LayoutManager.Instance?.InvalidateArrange(this);
-                InvalidateVisual();
+
+                if (((ILayoutable)this).IsAttachedToVisualTree)
+                {
+                    LayoutManager.Instance?.InvalidateArrange(this);
+                    InvalidateVisual();
+                }
             }
         }
 

+ 10 - 0
src/Avalonia.Styling/LogicalTree/ILogical.cs

@@ -36,6 +36,16 @@ namespace Avalonia.LogicalTree
         /// </summary>
         IAvaloniaReadOnlyList<ILogical> LogicalChildren { get; }
 
+        /// <summary>
+        /// Notifies the control that it is being attached to a rooted logical tree.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        /// <remarks>
+        /// This method will be called automatically by the framework, you should not need to call
+        /// this method yourself.
+        /// </remarks>
+        void NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e);
+
         /// <summary>
         /// Notifies the control that it is being detached from a rooted logical tree.
         /// </summary>

+ 2 - 2
src/Avalonia.Styling/Styling/Style.cs

@@ -61,12 +61,12 @@ namespace Avalonia.Styling
         }
 
         /// <summary>
-        /// Gets or sets style's selector.
+        /// Gets or sets the style's selector.
         /// </summary>
         public Selector Selector { get; set; }
 
         /// <summary>
-        /// Gets or sets style's setters.
+        /// Gets or sets the style's setters.
         /// </summary>
         [Content]
         public IEnumerable<ISetter> Setters { get; set; } = new List<ISetter>();

+ 1 - 1
src/Avalonia.Visuals/Platform/ILockedFramebuffer.cs

@@ -27,7 +27,7 @@ namespace Avalonia.Platform
         /// <summary>
         /// DPI of underling screen
         /// </summary>
-        Size Dpi { get; }
+        Vector Dpi { get; }
         
         /// <summary>
         /// Pixel format

+ 11 - 2
src/Avalonia.Visuals/Vector.cs

@@ -52,8 +52,6 @@ namespace Avalonia
             return new Point(a._x, a._y);
         }
 
-        
-
         /// <summary>
         /// Calculates the dot product of two vectors
         /// </summary>
@@ -65,6 +63,17 @@ namespace Avalonia
             return a.X*b.X + a.Y*b.Y;
         }
 
+        /// <summary>
+        /// Scales a vector.
+        /// </summary>
+        /// <param name="vector">The vector</param>
+        /// <param name="scale">The scaling factor.</param>
+        /// <returns>The scaled vector.</returns>
+        public static Vector operator *(Vector vector, double scale)
+        {
+            return new Vector(vector._x * scale, vector._y * scale);
+        }
+
         /// <summary>
         /// Length of the vector
         /// </summary>

+ 0 - 1
src/Gtk/Avalonia.Gtk/GtkPlatform.cs

@@ -51,7 +51,6 @@ namespace Avalonia.Gtk
                 .Bind<IClipboard>().ToSingleton<ClipboardImpl>()
                 .Bind<IStandardCursorFactory>().ToConstant(CursorFactory.Instance)
                 .Bind<IKeyboardDevice>().ToConstant(GtkKeyboardDevice.Instance)
-                .Bind<IMouseDevice>().ToConstant(GtkMouseDevice.Instance)
                 .Bind<IPlatformSettings>().ToConstant(s_instance)
                 .Bind<IPlatformThreadingInterface>().ToConstant(s_instance)
                 .Bind<IRendererFactory>().ToConstant(s_instance)

+ 1 - 1
src/Gtk/Avalonia.Gtk/SurfaceFramebuffer.cs

@@ -48,7 +48,7 @@ namespace Avalonia.Gtk
         public int Height => _surface.Height;
         public int RowBytes => _surface.Stride;
         //TODO: Proper DPI detect
-        public Size Dpi => new Size(96, 96);
+        public Vector Dpi => new Vector(96, 96);
         public PixelFormat Format => PixelFormat.Bgra8888;
     }
 }

+ 3 - 0
src/Gtk/Avalonia.Gtk/TopLevelImpl.cs

@@ -75,6 +75,8 @@ namespace Avalonia.Gtk
             }
         }
 
+        public IMouseDevice MouseDevice => GtkMouseDevice.Instance;
+
         public Avalonia.Controls.WindowState WindowState
         {
             get
@@ -114,6 +116,7 @@ namespace Avalonia.Gtk
 
         public Action Closed { get; set; }
 
+
         public Action Deactivated { get; set; }
 
         public Action<RawInputEventArgs> Input { get; set; }

+ 0 - 1
src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs

@@ -34,7 +34,6 @@ namespace Avalonia.Gtk3
                 .Bind<IClipboard>().ToSingleton<ClipboardImpl>()
                 .Bind<IStandardCursorFactory>().ToConstant(new CursorFactory())
                 .Bind<IKeyboardDevice>().ToConstant(Keyboard)
-                .Bind<IMouseDevice>().ToConstant(Mouse)
                 .Bind<IPlatformSettings>().ToConstant(Instance)
                 .Bind<IPlatformThreadingInterface>().ToConstant(Instance)
                 .Bind<ISystemDialogImpl>().ToSingleton<SystemDialog>()

+ 2 - 3
src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs

@@ -52,12 +52,11 @@ namespace Avalonia.Gtk3
         public int RowBytes { get; }
 
         
-        public Size Dpi
+        public Vector Dpi
         {
             get
             {
-                
-                return new Size(96, 96) * _factor;
+                return new Vector(96, 96) * _factor;
             }
         }
 

+ 1 - 1
src/Gtk/Avalonia.Gtk3/Interop/GObject.cs

@@ -41,7 +41,7 @@ namespace Avalonia.Gtk3.Interop
 
     class GtkWindow : GtkWidget
     {
-        
+        public static GtkWindow Null { get; } = new GtkWindow();
     }
 
     class GtkImContext : GObject

+ 18 - 0
src/Gtk/Avalonia.Gtk3/Interop/Native.cs

@@ -500,6 +500,24 @@ namespace Avalonia.Gtk3.Interop
         public gdouble delta_y;
     }
 
+    [StructLayout(LayoutKind.Sequential)]
+    unsafe  struct GdkEventCrossing 
+    {
+        public GdkEventType type;
+        public IntPtr window;
+        public gint8 send_event;
+        public IntPtr subwindow;
+        public guint32 time;
+        public gdouble x;
+        public gdouble y;
+        public gdouble x_root;
+        public gdouble y_root;
+        public int mode;
+        public int detail;
+        public bool focus;
+        public GdkModifierType state;
+    };
+    
     [StructLayout(LayoutKind.Sequential)]
     unsafe struct GdkEventWindowState
     {

+ 2 - 0
src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs

@@ -11,6 +11,8 @@ namespace Avalonia.Gtk3.Interop
             
         public Utf8Buffer(string s) : base(IntPtr.Zero, true)
         {
+            if (s == null)
+                return;
             _data = Encoding.UTF8.GetBytes(s);
             _gchandle = GCHandle.Alloc(_data, GCHandleType.Pinned);
             handle = _gchandle.AddrOfPinnedObject();

+ 2 - 1
src/Gtk/Avalonia.Gtk3/SystemDialogs.cs

@@ -18,7 +18,8 @@ namespace Avalonia.Gtk3
             bool multiselect, string initialFileName)
         {
             GtkFileChooser dlg;
-            using (var name = title != null ? new Utf8Buffer(title) : null)
+            parent = parent ?? GtkWindow.Null;
+            using (var name = new Utf8Buffer(title))
                 dlg = Native.GtkFileChooserDialogNew(name, parent, action, IntPtr.Zero);
             if (multiselect)
                 Native.GtkFileChooserSetSelectMultiple(dlg, true);

+ 14 - 0
src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs

@@ -45,6 +45,7 @@ namespace Avalonia.Gtk3
             ConnectEvent("window-state-event", OnStateChanged);
             ConnectEvent("key-press-event", OnKeyEvent);
             ConnectEvent("key-release-event", OnKeyEvent);
+            ConnectEvent("leave-notify-event", OnLeaveNotifyEvent);
             Connect<Native.D.signal_generic>("destroy", OnDestroy);
             Native.GtkWidgetRealize(gtkWidget);
             _lastSize = ClientSize;
@@ -194,6 +195,18 @@ namespace Avalonia.Gtk3
             return true;
         }
 
+        private unsafe bool OnLeaveNotifyEvent(IntPtr w, IntPtr pev, IntPtr userData)
+        {
+            var evnt = (GdkEventCrossing*) pev;
+            var position = new Point(evnt->x, evnt->y);
+            Input(new RawMouseEventArgs(Gtk3Platform.Mouse,
+                evnt->time,
+                _inputRoot,
+                RawMouseEventType.Move,
+                position, GetModifierKeys(evnt->state)));
+            return true;
+        }
+
         private unsafe bool OnCommit(IntPtr gtkwidget, IntPtr utf8string, IntPtr userdata)
         {
             Input(new RawTextInputEventArgs(Gtk3Platform.Keyboard, _lastKbdEvent, Utf8Buffer.StringFromPtr(utf8string)));
@@ -233,6 +246,7 @@ namespace Avalonia.Gtk3
             }
         }
 
+        public IMouseDevice MouseDevice => Gtk3Platform.Mouse;
 
         public double Scaling => (double) 1 / (Native.GtkWidgetGetScaleFactor?.Invoke(GtkWidget) ?? 1);
 

+ 1 - 0
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@@ -56,6 +56,7 @@ namespace Avalonia.LinuxFramebuffer
         }
 
         public Size ClientSize => _fb.PixelSize;
+        public IMouseDevice MouseDevice => LinuxFramebufferPlatform.MouseDevice;
         public double Scaling => 1;
         public IEnumerable<object> Surfaces => new object[] {_fb};
         public Action<RawInputEventArgs> Input { get; set; }

+ 3 - 3
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebuffer.cs

@@ -13,16 +13,16 @@ namespace Avalonia.LinuxFramebuffer
 {
     public sealed unsafe class LinuxFramebuffer : IFramebufferPlatformSurface, IDisposable
     {
-        private readonly Size _dpi;
+        private readonly Vector _dpi;
         private int _fd;
         private fb_fix_screeninfo _fixedInfo;
         private fb_var_screeninfo _varInfo;
         private IntPtr _mappedLength;
         private IntPtr _mappedAddress;
 
-        public LinuxFramebuffer(string fileName = null, Size? dpi = null)
+        public LinuxFramebuffer(string fileName = null, Vector? dpi = null)
         {
-            _dpi = dpi ?? new Size(96, 96);
+            _dpi = dpi ?? new Vector(96, 96);
             fileName = fileName ?? Environment.GetEnvironmentVariable("FRAMEBUFFER") ?? "/dev/fb0";
             _fd = NativeUnsafeMethods.open(fileName, 2, 0);
             if (_fd <= 0)

+ 0 - 1
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@@ -33,7 +33,6 @@ namespace Avalonia.LinuxFramebuffer
             AvaloniaLocator.CurrentMutable
                 .Bind<IStandardCursorFactory>().ToTransient<CursorFactoryStub>()
                 .Bind<IKeyboardDevice>().ToConstant(KeyboardDevice)
-                .Bind<IMouseDevice>().ToConstant(MouseDevice)
                 .Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
                 .Bind<IRendererFactory>().ToConstant(ImmediateRenderer.Factory)
                 .Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance)

+ 2 - 2
src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs

@@ -11,7 +11,7 @@ namespace Avalonia.LinuxFramebuffer
         private fb_var_screeninfo _varInfo;
         private readonly IntPtr _address;
 
-        public LockedFramebuffer(int fb, fb_fix_screeninfo fixedInfo, fb_var_screeninfo varInfo, IntPtr address, Size dpi)
+        public LockedFramebuffer(int fb, fb_fix_screeninfo fixedInfo, fb_var_screeninfo varInfo, IntPtr address, Vector dpi)
         {
             _fb = fb;
             _fixedInfo = fixedInfo;
@@ -41,7 +41,7 @@ namespace Avalonia.LinuxFramebuffer
         public int Width => (int)_varInfo.xres;
         public int Height => (int) _varInfo.yres;
         public int RowBytes => (int) _fixedInfo.line_length;
-        public Size Dpi { get; }
+        public Vector Dpi { get; }
         public PixelFormat Format => _varInfo.blue.offset == 16 ? PixelFormat.Rgba8888 : PixelFormat.Bgra8888;
     }
 }

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/Data/MultiBinding.cs

@@ -102,7 +102,7 @@ namespace Avalonia.Markup.Xaml.Data
 
         private object ConvertValue(IList<object> values, Type targetType)
         {
-            var converted = Converter.Convert(values, targetType, null, CultureInfo.CurrentUICulture);
+            var converted = Converter.Convert(values, targetType, null, CultureInfo.CurrentCulture);
 
             if (converted == AvaloniaProperty.UnsetValue && FallbackValue != null)
             {

+ 2 - 2
src/Markup/Avalonia.Markup/Data/BindingExpression.cs

@@ -122,7 +122,7 @@ namespace Avalonia.Markup.Data
                         value,
                         type,
                         ConverterParameter,
-                        CultureInfo.CurrentUICulture);
+                        CultureInfo.CurrentCulture);
 
                     if (converted == AvaloniaProperty.UnsetValue)
                     {
@@ -186,7 +186,7 @@ namespace Avalonia.Markup.Data
                     value,
                     _targetType,
                     ConverterParameter,
-                    CultureInfo.CurrentUICulture);
+                    CultureInfo.CurrentCulture);
 
                 notification = converted as BindingNotification;
 

+ 2 - 1
src/Skia/Avalonia.Skia.Desktop.NetStandard/Avalonia.Skia.Desktop.NetStandard.csproj

@@ -5,6 +5,7 @@
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <RootNamespace>Avalonia.Skia.Desktop</RootNamespace>
     <AssemblyName>Avalonia.Skia.Desktop</AssemblyName>
+    <IncludeLinuxSkia>true</IncludeLinuxSkia>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
     <DebugSymbols>true</DebugSymbols>
@@ -42,4 +43,4 @@
   <Import Project="..\..\..\build\SkiaSharp.props" />
   <Import Project="..\Avalonia.Skia\Avalonia.Skia.projitems" Label="Shared" />
   <Import Project="..\..\Shared\RenderHelpers\RenderHelpers.projitems" Label="Shared" />
-</Project>
+</Project>

+ 4 - 1
src/Skia/Avalonia.Skia.Desktop/Avalonia.Skia.Desktop.csproj

@@ -54,6 +54,9 @@
     <ErrorReport>prompt</ErrorReport>
     <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
   </PropertyGroup>
+  <PropertyGroup>
+    <IncludeLinuxSkia>true</IncludeLinuxSkia>
+  </PropertyGroup>
   <ItemGroup>
     <Reference Include="System" />
     <Reference Include="System.Core" />
@@ -106,4 +109,4 @@
   <Import Project="..\..\Shared\RenderHelpers\RenderHelpers.projitems" Label="Shared" />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <Import Project="..\..\..\build\SkiaSharp.props" />
-</Project>
+</Project>

+ 1 - 1
src/Skia/Avalonia.Skia/BitmapImpl.cs

@@ -131,7 +131,7 @@ namespace Avalonia.Skia
             public int Width => _bmp.Width;
             public int Height => _bmp.Height;
             public int RowBytes => _bmp.RowBytes;
-            public Size Dpi { get; } = new Size(96, 96);
+            public Vector Dpi { get; } = new Vector(96, 96);
             public PixelFormat Format => _bmp.ColorType.ToPixelFormat();
         }
 

+ 3 - 9
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@@ -141,23 +141,17 @@ namespace Avalonia.Skia
             var rv = new PaintWrapper(paint);
             paint.IsStroke = false;
 
-            // TODO: SkiaSharp does not contain alpha yet!
+            
             double opacity = brush.Opacity * _currentOpacity;
-            //paint.SetAlpha(paint.GetAlpha() * opacity);
             paint.IsAntialias = true;
 
-            SKColor color = new SKColor(255, 255, 255, 255);
-
             var solid = brush as ISolidColorBrush;
-            if (solid != null)
-                color = solid.Color.ToSKColor();
-
-            paint.Color = (new SKColor(color.Red, color.Green, color.Blue, (byte)(color.Alpha * opacity)));
-
             if (solid != null)
             {
+                paint.Color = new SKColor(solid.Color.R, solid.Color.G, solid.Color.B, (byte) (solid.Color.A * opacity));
                 return rv;
             }
+            paint.Color = (new SKColor(255, 255, 255, (byte)(255 * opacity)));
 
             var gradient = brush as IGradientBrush;
             if (gradient != null)

+ 50 - 51
src/Skia/Avalonia.Skia/FormattedTextImpl.cs

@@ -42,7 +42,6 @@ namespace Avalonia.Skia
             _paint.Typeface = skiaTypeface;
             _paint.TextSize = (float)(typeface?.FontSize ?? 12);
             _paint.TextAlign = textAlignment.ToSKTextAlign();
-            _paint.BlendMode = SKBlendMode.Src;
 
             _wrapping = wrapping;
             _constraint = constraint;
@@ -200,66 +199,65 @@ namespace Avalonia.Skia
                 }
                 ctx->Canvas->restore();
             */
-            SKPaint paint = _paint;
-            IDisposable currd = null;
-            var currentWrapper = foreground;
-
-            try
+            using (var paint = _paint.Clone())
             {
-                SKPaint currFGPaint = ApplyWrapperTo(ref foreground, ref currd, paint);
-                bool hasCusomFGBrushes = _foregroundBrushes.Any();
-
-                for (int c = 0; c < _skiaLines.Count; c++)
+                IDisposable currd = null;
+                var currentWrapper = foreground;
+                SKPaint currentPaint = null;
+                try
                 {
-                    AvaloniaFormattedTextLine line = _skiaLines[c];
-
-                    float x = TransformX(origin.X, 0, paint.TextAlign);
+                    ApplyWrapperTo(ref currentPaint, foreground, ref currd, paint);
+                    bool hasCusomFGBrushes = _foregroundBrushes.Any();
 
-                    if (!hasCusomFGBrushes)
-                    {
-                        var subString = Text.Substring(line.Start, line.Length);
-                        canvas.DrawText(subString, x, origin.Y + line.Top + _lineOffset, paint);
-                    }
-                    else
+                    for (int c = 0; c < _skiaLines.Count; c++)
                     {
-                        float currX = x;
-                        string subStr;
-                        int len;
+                        AvaloniaFormattedTextLine line = _skiaLines[c];
 
-                        for (int i = line.Start; i < line.Start + line.Length;)
-                        {
-                            var fb = GetNextForegroundBrush(ref line, i, out len);
-
-                            if (fb != null)
-                            {
-                                //TODO: figure out how to get the brush size
-                                currentWrapper = context.CreatePaint(fb, new Size());
-                            }
-                            else
-                            {
-                                if (!currentWrapper.Equals(foreground)) currentWrapper.Dispose();
-                                currentWrapper = foreground;
-                            }
+                        float x = TransformX(origin.X, 0, paint.TextAlign);
 
-                            subStr = Text.Substring(i, len);
+                        if (!hasCusomFGBrushes)
+                        {
+                            var subString = Text.Substring(line.Start, line.Length);
+                            canvas.DrawText(subString, x, origin.Y + line.Top + _lineOffset, paint);
+                        }
+                        else
+                        {
+                            float currX = x;
+                            string subStr;
+                            int len;
 
-                            if (currFGPaint != currentWrapper.Paint)
+                            for (int i = line.Start; i < line.Start + line.Length;)
                             {
-                                currFGPaint = ApplyWrapperTo(ref currentWrapper, ref currd, paint);
+                                var fb = GetNextForegroundBrush(ref line, i, out len);
+
+                                if (fb != null)
+                                {
+                                    //TODO: figure out how to get the brush size
+                                    currentWrapper = context.CreatePaint(fb, new Size());
+                                }
+                                else
+                                {
+                                    if (!currentWrapper.Equals(foreground)) currentWrapper.Dispose();
+                                    currentWrapper = foreground;
+                                }
+
+                                subStr = Text.Substring(i, len);
+
+                                ApplyWrapperTo(ref currentPaint, currentWrapper, ref currd, paint);
+                                
+                                canvas.DrawText(subStr, currX, origin.Y + line.Top + _lineOffset, paint);
+
+                                i += len;
+                                currX += paint.MeasureText(subStr);
                             }
-
-                            canvas.DrawText(subStr, currX, origin.Y + line.Top + _lineOffset, paint);
-
-                            i += len;
-                            currX += paint.MeasureText(subStr);
                         }
                     }
                 }
-            }
-            finally
-            {
-                if (!currentWrapper.Equals(foreground)) currentWrapper.Dispose();
-                currd?.Dispose();
+                finally
+                {
+                    if (!currentWrapper.Equals(foreground)) currentWrapper.Dispose();
+                    currd?.Dispose();
+                }
             }
         }
 
@@ -278,12 +276,13 @@ namespace Avalonia.Skia
         private Size _size;
         private List<AvaloniaFormattedTextLine> _skiaLines;
 
-        private static SKPaint ApplyWrapperTo(ref DrawingContextImpl.PaintWrapper wrapper,
+        private static void ApplyWrapperTo(ref SKPaint current, DrawingContextImpl.PaintWrapper wrapper,
                                                 ref IDisposable curr, SKPaint paint)
         {
+            if (current == wrapper.Paint)
+                return;
             curr?.Dispose();
             curr = wrapper.ApplyTo(paint);
-            return wrapper.Paint;
         }
 
         private static bool IsBreakChar(char c)

+ 1 - 1
src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs

@@ -76,7 +76,7 @@ namespace Avalonia.Skia
             canvas.RestoreToCount(0);
             canvas.Save();
             canvas.ResetMatrix();
-            var scale = Matrix.CreateScale(fb.Dpi.Width / 96, fb.Dpi.Height / 96);
+            var scale = Matrix.CreateScale(fb.Dpi.X / 96, fb.Dpi.Y / 96);
             return new DrawingContextImpl(canvas, visualBrushRenderer, scale, canvas, surface, shim, fb);
         }
     }

+ 2 - 0
src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj

@@ -50,7 +50,9 @@
     </Compile>
     <Compile Include="Direct2D1Platform.cs" />
     <Compile Include="Disposable.cs" />
+    <Compile Include="ExternalRenderTarget.cs" />
     <Compile Include="HwndRenderTarget.cs" />
+    <Compile Include="IExternalDirect2DRenderTargetSurface.cs" />
     <Compile Include="Media\BrushImpl.cs" />
     <Compile Include="Media\BrushWrapper.cs" />
     <Compile Include="Media\DrawingContextImpl.cs" />

+ 10 - 5
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@@ -135,12 +135,17 @@ namespace Avalonia.Direct2D1
 
         public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
         {
-            var nativeWindow = surfaces?.OfType<IPlatformHandle>().FirstOrDefault();
-            if (nativeWindow != null)
+            foreach (var s in surfaces)
             {
-                if(nativeWindow.HandleDescriptor != "HWND")
-                    throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from " + nativeWindow.HandleDescriptor);
-                return new HwndRenderTarget(nativeWindow);
+                if (s is IPlatformHandle nativeWindow)
+                {
+                    if (nativeWindow.HandleDescriptor != "HWND")
+                        throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from " +
+                                                        nativeWindow.HandleDescriptor);
+                    return new HwndRenderTarget(nativeWindow);
+                }
+                if (s is IExternalDirect2DRenderTargetSurface external)
+                    return new ExternalRenderTarget(external, s_dwfactory);
             }
             throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from any of provided surfaces");
         }

+ 47 - 0
src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs

@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Direct2D1.Media;
+using Avalonia.Platform;
+using Avalonia.Rendering;
+using SharpDX;
+using DirectWriteFactory = SharpDX.DirectWrite.Factory;
+
+namespace Avalonia.Direct2D1
+{
+    class ExternalRenderTarget : IRenderTarget
+    {
+        private readonly IExternalDirect2DRenderTargetSurface _externalRenderTargetProvider;
+        private readonly DirectWriteFactory _dwFactory;
+        public ExternalRenderTarget(IExternalDirect2DRenderTargetSurface externalRenderTargetProvider,
+            DirectWriteFactory dwFactory)
+        {
+            _externalRenderTargetProvider = externalRenderTargetProvider;
+            _dwFactory = dwFactory;
+        }
+
+        public void Dispose()
+        {
+            _externalRenderTargetProvider.DestroyRenderTarget();
+        }
+
+        public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
+        {
+            var target =  _externalRenderTargetProvider.GetOrCreateRenderTarget();
+            _externalRenderTargetProvider.BeforeDrawing();
+            return new DrawingContextImpl(visualBrushRenderer, target, _dwFactory, null, () =>
+            {
+                try
+                {
+                    _externalRenderTargetProvider.AfterDrawing();
+                }
+                catch (SharpDXException ex) when ((uint) ex.HResult == 0x8899000C) // D2DERR_RECREATE_TARGET
+                {
+                    _externalRenderTargetProvider.DestroyRenderTarget();
+                }
+            });
+        }
+    }
+}

+ 16 - 0
src/Windows/Avalonia.Direct2D1/IExternalDirect2DRenderTargetSurface.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Avalonia.Direct2D1
+{
+    public interface IExternalDirect2DRenderTargetSurface
+    {
+        SharpDX.Direct2D1.RenderTarget GetOrCreateRenderTarget();
+        void DestroyRenderTarget();
+        void BeforeDrawing();
+        void AfterDrawing();
+    }
+}

+ 6 - 1
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@@ -23,6 +23,7 @@ namespace Avalonia.Direct2D1.Media
         private readonly IVisualBrushRenderer _visualBrushRenderer;
         private readonly SharpDX.Direct2D1.RenderTarget _renderTarget;
         private readonly SharpDX.DXGI.SwapChain1 _swapChain;
+        private readonly Action _finishedCallback;
         private SharpDX.DirectWrite.Factory _directWriteFactory;
 
         /// <summary>
@@ -32,15 +33,18 @@ namespace Avalonia.Direct2D1.Media
         /// <param name="renderTarget">The render target to draw to.</param>
         /// <param name="directWriteFactory">The DirectWrite factory.</param>
         /// <param name="swapChain">An optional swap chain associated with this drawing context.</param>
+        /// <param name="finishedCallback">An optional delegate to be called when context is disposed.</param>
         public DrawingContextImpl(
             IVisualBrushRenderer visualBrushRenderer,
             SharpDX.Direct2D1.RenderTarget renderTarget,
             SharpDX.DirectWrite.Factory directWriteFactory,
-            SharpDX.DXGI.SwapChain1 swapChain = null)
+            SharpDX.DXGI.SwapChain1 swapChain = null,
+            Action finishedCallback = null)
         {
             _visualBrushRenderer = visualBrushRenderer;
             _renderTarget = renderTarget;
             _swapChain = swapChain;
+            _finishedCallback = finishedCallback;
             _directWriteFactory = directWriteFactory;
             _swapChain = swapChain;
             _renderTarget.BeginDraw();
@@ -73,6 +77,7 @@ namespace Avalonia.Direct2D1.Media
                 _renderTarget.EndDraw();
 
                 _swapChain?.Present(1, SharpDX.DXGI.PresentFlags.None);
+                _finishedCallback?.Invoke();
             }
             catch (SharpDXException ex) when ((uint)ex.HResult == 0x8899000C) // D2DERR_RECREATE_TARGET
             {

+ 1 - 1
src/Windows/Avalonia.Direct2D1/Media/Imaging/WritableWicBitmapImpl.cs

@@ -37,7 +37,7 @@ namespace Avalonia.Direct2D1.Media.Imaging
             public int Width => _lock.Size.Width;
             public int Height => _lock.Size.Height;
             public int RowBytes => _lock.Stride;
-            public Size Dpi { get; } = new Size(96, 96);
+            public Vector Dpi { get; } = new Vector(96, 96);
             public PixelFormat Format => _format;
 
         }

+ 123 - 0
src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj

@@ -0,0 +1,123 @@
+<?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')" />
+  <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.5.2</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>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="PresentationCore" />
+    <Reference Include="PresentationFramework" />
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <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="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" />
+  </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="..\..\Gtk\Avalonia.Cairo\Avalonia.Cairo.csproj">
+      <Project>{fb05ac90-89ba-4f2f-a924-f37875fb547c}</Project>
+      <Name>Avalonia.Cairo</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>
+  </ItemGroup>
+  <PropertyGroup>
+    <UseDirect3D9>true</UseDirect3D9>
+  </PropertyGroup>
+  <Import Project="..\..\..\build\SharpDX.props" />
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>

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

@@ -0,0 +1,36 @@
+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")]

+ 38 - 0
src/Windows/Avalonia.Win32.Interop/Wpf/CursorShim.cs

@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Input;
+
+namespace Avalonia.Win32.Interop.Wpf
+{
+    static class CursorShim
+    {
+        public static Cursor FromHCursor(IntPtr hcursor)
+        {
+            var field = typeof(Cursor).GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
+                .FirstOrDefault(f => f.FieldType == typeof(SafeHandle));
+            if (field == null)
+                return null;
+            var rv = (Cursor) FormatterServices.GetUninitializedObject(typeof(Cursor));
+            field.SetValue(rv, new SafeHandleShim(hcursor));
+            return rv;
+        }
+
+        class SafeHandleShim : SafeHandle
+        {
+            public SafeHandleShim(IntPtr hcursor) : base(new IntPtr(-1), false)
+            {
+                this.handle = hcursor;
+            }
+
+            protected override bool ReleaseHandle() => true;
+
+            public override bool IsInvalid => false;
+        }
+    }
+}

+ 208 - 0
src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs

@@ -0,0 +1,208 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Interop;
+using Avalonia.Direct2D1;
+using SharpDX;
+using SharpDX.Direct2D1;
+using SharpDX.Direct3D11;
+using SharpDX.Direct3D9;
+using SharpDX.DXGI;
+using AlphaMode = SharpDX.Direct2D1.AlphaMode;
+using Device = SharpDX.Direct3D11.Device;
+using Format = SharpDX.DXGI.Format;
+using MapFlags = SharpDX.Direct3D11.MapFlags;
+using PresentParameters = SharpDX.DXGI.PresentParameters;
+using Query = SharpDX.Direct3D11.Query;
+using QueryType = SharpDX.Direct3D11.QueryType;
+using RenderTarget = SharpDX.Direct2D1.RenderTarget;
+using Surface = SharpDX.DXGI.Surface;
+using SwapEffect = SharpDX.DXGI.SwapEffect;
+using Usage = SharpDX.Direct3D9.Usage;
+
+namespace Avalonia.Win32.Interop.Wpf
+{
+    class Direct2DImageSurface : IExternalDirect2DRenderTargetSurface, IDisposable
+    {
+        class SwapBuffer: IDisposable
+        {
+            private readonly Query _event;
+            private readonly SharpDX.Direct3D11.Resource _resource;
+            private readonly SharpDX.Direct3D11.Resource _sharedResource;
+            public SharpDX.Direct3D9.Surface Texture { get; }
+            public RenderTarget Target { get;}
+            public IntSize Size { get; }
+
+            public SwapBuffer(IntSize size, Vector dpi)
+            {
+                int width = (int) size.Width;
+                int height = (int) size.Height;
+                _event = new Query(s_dxDevice, new QueryDescription {Type = QueryType.Event});
+                using (var texture = new Texture2D(s_dxDevice, new Texture2DDescription
+                {
+                    Width = width,
+                    Height = height,
+                    ArraySize = 1,
+                    MipLevels = 1,
+                    Format = Format.B8G8R8A8_UNorm,
+                    Usage = ResourceUsage.Default,
+                    SampleDescription = new SampleDescription(2, 0),
+                    BindFlags = BindFlags.RenderTarget,
+                }))
+                using (var surface = texture.QueryInterface<Surface>())
+                
+                {
+                    _resource = texture.QueryInterface<SharpDX.Direct3D11.Resource>();
+                    
+                    Target = new RenderTarget(AvaloniaLocator.Current.GetService<SharpDX.Direct2D1.Factory>(), surface,
+                        new RenderTargetProperties
+                        {
+                            DpiX = (float) dpi.X,
+                            DpiY = (float) dpi.Y,
+                            MinLevel = FeatureLevel.Level_10,
+                            PixelFormat = new PixelFormat(Format.B8G8R8A8_UNorm, AlphaMode.Premultiplied),
+
+                        });
+                }
+                using (var texture = new Texture2D(s_dxDevice, new Texture2DDescription
+                {
+                    Width = width,
+                    Height = height,
+                    ArraySize = 1,
+                    MipLevels = 1,
+                    Format = Format.B8G8R8A8_UNorm,
+                    Usage = ResourceUsage.Default,
+                    SampleDescription = new SampleDescription(1, 0),
+                    BindFlags = BindFlags.RenderTarget|BindFlags.ShaderResource,
+                    OptionFlags = ResourceOptionFlags.Shared,
+                }))
+                using (var resource = texture.QueryInterface<SharpDX.DXGI.Resource>())
+                {
+                    _sharedResource = texture.QueryInterface<SharpDX.Direct3D11.Resource>();
+                    var handle = resource.SharedHandle;
+                    using (var texture9 = new Texture(s_d3DDevice, texture.Description.Width,
+                        texture.Description.Height, 1,
+                        Usage.RenderTarget, SharpDX.Direct3D9.Format.A8R8G8B8, Pool.Default, ref handle))
+                        Texture = texture9.GetSurfaceLevel(0);
+                }
+                Size = size;
+            }
+
+            public void Dispose()
+            {
+                Texture?.Dispose();
+                Target?.Dispose();
+                _resource?.Dispose();
+                _sharedResource?.Dispose();
+                _event?.Dispose();
+            }
+
+            public void Flush()
+            {
+                s_dxDevice.ImmediateContext.ResolveSubresource(_resource, 0, _sharedResource, 0, Format.B8G8R8A8_UNorm);
+                s_dxDevice.ImmediateContext.Flush();
+                s_dxDevice.ImmediateContext.End(_event);
+                s_dxDevice.ImmediateContext.GetData(_event).Dispose();
+            }
+        }
+
+        private D3DImage _image;
+        private SwapBuffer _backBuffer;
+        private readonly WpfTopLevelImpl _impl;
+        private static Device s_dxDevice;
+        private static Direct3DEx s_d3DContext;
+        private static DeviceEx s_d3DDevice;
+        private Vector _oldDpi;
+
+
+        [DllImport("user32.dll", SetLastError = false)]
+        private static extern IntPtr GetDesktopWindow();
+        void EnsureDirectX()
+        {
+            if(s_d3DDevice != null)
+                return;
+            s_d3DContext = new Direct3DEx();
+
+            SharpDX.Direct3D9.PresentParameters presentparams = new SharpDX.Direct3D9.PresentParameters
+            {
+                Windowed = true,
+                SwapEffect = SharpDX.Direct3D9.SwapEffect.Discard,
+                DeviceWindowHandle = GetDesktopWindow(),
+                PresentationInterval = PresentInterval.Default
+            };
+            s_dxDevice = s_dxDevice ?? AvaloniaLocator.Current.GetService<SharpDX.DXGI.Device>()
+                             .QueryInterface<SharpDX.Direct3D11.Device>();
+            s_d3DDevice = new DeviceEx(s_d3DContext, 0, DeviceType.Hardware, IntPtr.Zero, CreateFlags.HardwareVertexProcessing | CreateFlags.Multithreaded | CreateFlags.FpuPreserve, presentparams);
+
+        }
+
+        public Direct2DImageSurface(WpfTopLevelImpl impl)
+        {
+            _impl = impl;
+        }
+
+        public RenderTarget GetOrCreateRenderTarget()
+        {
+            EnsureDirectX();
+            var scale = _impl.GetScaling();
+            var size = new IntSize(_impl.ActualWidth * scale.X, _impl.ActualHeight * scale.Y);
+            var dpi = scale * 96;
+
+            if (_backBuffer!=null && _backBuffer.Size == size)
+                return _backBuffer.Target;
+
+            if (_image == null || _oldDpi.X != dpi.X || _oldDpi.Y != dpi.Y)
+            {
+                _image = new D3DImage(dpi.X, dpi.Y);
+            }
+            _impl.ImageSource = _image;
+            
+            RemoveAndDispose(ref _backBuffer);
+            if (size == default(IntSize))
+            {
+                _image.Lock();
+                _image.SetBackBuffer(D3DResourceType.IDirect3DSurface9, IntPtr.Zero);
+                _image.Unlock();
+                return null;
+            }
+            _backBuffer = new SwapBuffer(size, dpi);
+
+            return _backBuffer.Target;
+        }
+
+        void RemoveAndDispose<T>(ref T d) where T : IDisposable
+        {
+            d?.Dispose();
+            d = default(T);
+        }
+
+        void Swap()
+        {
+            _backBuffer.Flush();
+            _image.Lock();
+            _image.SetBackBuffer(D3DResourceType.IDirect3DSurface9, _backBuffer?.Texture?.NativePointer ?? IntPtr.Zero, true);
+            _image.AddDirtyRect(new Int32Rect(0, 0, _image.PixelWidth, _image.PixelHeight));
+            _image.Unlock();
+        }
+
+        public void DestroyRenderTarget()
+        {
+            RemoveAndDispose(ref _backBuffer);
+        }
+
+        public void BeforeDrawing()
+        {
+            
+        }
+
+        public void AfterDrawing() => Swap();
+        public void Dispose()
+        {
+            RemoveAndDispose(ref _backBuffer);
+        }
+    }
+}

+ 59 - 0
src/Windows/Avalonia.Win32.Interop/Wpf/IntSize.cs

@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Avalonia.Win32.Interop.Wpf
+{
+    struct IntSize : IEquatable<IntSize>
+    {
+        public bool Equals(IntSize other)
+        {
+            return Width == other.Width && Height == other.Height;
+        }
+
+        public IntSize(int width, int height)
+        {
+            Width = width;
+            Height = height;
+        }
+
+        public IntSize(double width, double height) : this((int) width, (int) height)
+        {
+            
+        }
+
+        public static implicit  operator IntSize(System.Windows.Size size)
+        {
+            return new IntSize {Width = (int) size.Width, Height = (int) size.Height};
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (ReferenceEquals(null, obj)) return false;
+            return obj is IntSize && Equals((IntSize) obj);
+        }
+
+        public override int GetHashCode()
+        {
+            unchecked
+            {
+                return (Width * 397) ^ Height;
+            }
+        }
+
+        public static bool operator ==(IntSize left, IntSize right)
+        {
+            return left.Equals(right);
+        }
+
+        public static bool operator !=(IntSize left, IntSize right)
+        {
+            return !left.Equals(right);
+        }
+
+        public int Width { get; set; }
+        public int Height { get; set; }
+    }
+}

+ 121 - 0
src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs

@@ -0,0 +1,121 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Markup;
+using System.Windows.Media;
+using Avalonia.Markup.Xaml.Styling;
+using Avalonia.Platform;
+using Avalonia.Styling;
+
+namespace Avalonia.Win32.Interop.Wpf
+{
+    [ContentProperty("Content")]
+    public class WpfAvaloniaHost : FrameworkElement, IDisposable, IAddChild
+    {
+        private WpfTopLevelImpl _impl;
+        private readonly SynchronizationContext _sync;
+        private bool _hasChildren;
+        public WpfAvaloniaHost()
+        {
+            _sync = SynchronizationContext.Current;
+            _impl = new WpfTopLevelImpl();
+            _impl.ControlRoot.Prepare();
+            _impl.Visibility = Visibility.Visible;
+            SnapsToDevicePixels = true;
+            UseLayoutRounding = true;
+            PresentationSource.AddSourceChangedHandler(this, OnSourceChanged);
+        }
+
+        private void OnSourceChanged(object sender, SourceChangedEventArgs e)
+        {
+            if (e.NewSource != null && !_hasChildren)
+            {
+                AddLogicalChild(_impl);
+                AddVisualChild(_impl);
+                _hasChildren = true;
+            }
+            else
+            {
+                RemoveVisualChild(_impl);
+                RemoveLogicalChild(_impl);
+                _hasChildren = false;
+            }
+        }
+
+        public object Content
+        {
+            get => _impl.ControlRoot.Content;
+            set => _impl.ControlRoot.Content = value;
+        }
+
+        //Separate class is needed to prevent accidential resurrection
+        class Disposer
+        {
+            private readonly WpfTopLevelImpl _impl;
+
+            public Disposer(WpfTopLevelImpl impl)
+            {
+                _impl = impl;
+            }
+
+            public void Callback(object state)
+            {
+                _impl.Dispose();
+            }
+        }
+
+        protected override System.Windows.Size MeasureOverride(System.Windows.Size constraint)
+        {
+            _impl.InvalidateMeasure();
+            _impl.Measure(constraint);
+            return _impl.DesiredSize;
+        }
+
+        protected override System.Windows.Size ArrangeOverride(System.Windows.Size arrangeSize)
+        {
+            _impl.Arrange(new System.Windows.Rect(arrangeSize));
+            return arrangeSize;
+        }
+        
+        protected override int VisualChildrenCount => 1;
+        protected override System.Windows.Media.Visual GetVisualChild(int index) => _impl;
+
+        ~WpfAvaloniaHost()
+        {
+            if (_impl != null)
+                _sync.Post(new Disposer(_impl).Callback, null);
+        }
+
+        public void Dispose()
+        {
+            if (_impl != null)
+            {
+                RemoveVisualChild(_impl);
+                RemoveLogicalChild(_impl);
+                _impl.Dispose();
+                _impl = null;
+                GC.SuppressFinalize(this);
+            }
+        }
+
+        void IAddChild.AddChild(object value)
+        {
+            if (Content == null)
+                Content = value;
+            else
+                throw new InvalidOperationException();
+        }
+
+        void IAddChild.AddText(string text)
+        {
+            //
+        }
+    }
+}

+ 16 - 0
src/Windows/Avalonia.Win32.Interop/Wpf/WpfInteropExtensions.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Avalonia.Win32.Interop.Wpf
+{
+    static class WpfInteropExtensions
+    {
+        public static System.Windows.Point ToWpfPoint(this Point pt) => new System.Windows.Point(pt.X, pt.Y);
+        public static Point ToAvaloniaPoint(this System.Windows.Point pt) => new Point(pt.X, pt.Y);
+        public static System.Windows.Size ToWpfSize(this Size pt) => new System.Windows.Size(pt.Width, pt.Height);
+        public static Size ToAvaloniaSize(this System.Windows.Size pt) => new Size(pt.Width, pt.Height);
+    }
+}

+ 30 - 0
src/Windows/Avalonia.Win32.Interop/Wpf/WpfMouseDevice.cs

@@ -0,0 +1,30 @@
+using System;
+using Avalonia.Controls.Embedding;
+using Avalonia.Input;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Win32.Interop.Wpf
+{
+    class WpfMouseDevice : MouseDevice
+    {
+        private readonly WpfTopLevelImpl _impl;
+
+        public WpfMouseDevice(WpfTopLevelImpl impl)
+        {
+            _impl = impl;
+        }
+
+        public override void Capture(IInputElement control)
+        {
+            if (control == null)
+            {
+                System.Windows.Input.Mouse.Capture(null);
+            }
+            else if ((control.GetVisualRoot() as EmbeddableControlRoot)?.PlatformImpl != _impl)
+                throw new ArgumentException("Visual belongs to unknown toplevel");
+            else
+                System.Windows.Input.Mouse.Capture(_impl);
+            base.Capture(control);
+        }
+    }
+}

+ 241 - 0
src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs

@@ -0,0 +1,241 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Interop;
+using System.Windows.Media;
+using Avalonia.Controls.Embedding;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Layout;
+using Avalonia.Platform;
+using Key = Avalonia.Input.Key;
+using KeyEventArgs = System.Windows.Input.KeyEventArgs;
+using MouseButton = System.Windows.Input.MouseButton;
+
+namespace Avalonia.Win32.Interop.Wpf
+{
+    class WpfTopLevelImpl : FrameworkElement, IEmbeddableWindowImpl
+    {
+        private HwndSource _currentHwndSource;
+        private readonly HwndSourceHook _hook;
+        private readonly IEmbeddableWindowImpl _ttl;
+        private IInputRoot _inputRoot;
+        private readonly IEnumerable<object> _surfaces;
+        private readonly IMouseDevice _mouse;
+        private readonly IKeyboardDevice _keyboard;
+        private Size _finalSize;
+
+        public EmbeddableControlRoot ControlRoot { get; }
+        internal ImageSource ImageSource { get; set; }
+
+        public class CustomControlRoot : EmbeddableControlRoot, IEmbeddedLayoutRoot
+        {
+            public CustomControlRoot(WpfTopLevelImpl impl) : base(impl)
+            {
+                EnforceClientSize = false;
+            }
+
+            protected override void OnMeasureInvalidated()
+            {
+                ((FrameworkElement)PlatformImpl)?.InvalidateMeasure();
+            }
+
+            protected override void HandleResized(Size clientSize)
+            {
+                ClientSize = clientSize;
+                LayoutManager.Instance.ExecuteLayoutPass();
+                Renderer?.Resized(clientSize);
+            }
+
+            public Size AllocatedSize => ClientSize;
+        }
+
+        public WpfTopLevelImpl()
+        {
+            PresentationSource.AddSourceChangedHandler(this, OnSourceChanged);
+            _hook = WndProc;
+            _ttl = this;
+            _surfaces = new object[] {new WritableBitmapSurface(this), new Direct2DImageSurface(this)};
+            _mouse = new WpfMouseDevice(this);
+            _keyboard = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
+
+            ControlRoot = new CustomControlRoot(this);
+            SnapsToDevicePixels = true;
+            Focusable = true;
+            DataContextChanged += delegate
+            {
+                ControlRoot.DataContext = DataContext;
+            };
+        }
+
+        private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled)
+        {
+            if (msg == (int)UnmanagedMethods.WindowsMessage.WM_DPICHANGED)
+                _ttl.ScalingChanged?.Invoke(_ttl.Scaling);
+            return IntPtr.Zero;
+        }
+
+        private void OnSourceChanged(object sender, SourceChangedEventArgs e)
+        {
+            _currentHwndSource?.RemoveHook(_hook);
+            _currentHwndSource = e.NewSource as HwndSource;
+            _currentHwndSource?.AddHook(_hook);
+            _ttl.ScalingChanged?.Invoke(_ttl.Scaling);
+        }
+
+        public void Dispose()
+        {
+            _ttl.Closed?.Invoke();
+            foreach(var d in _surfaces.OfType<IDisposable>())
+                d.Dispose();
+        }
+
+        Size ITopLevelImpl.ClientSize => _finalSize;
+        IMouseDevice ITopLevelImpl.MouseDevice => _mouse;
+
+        double ITopLevelImpl.Scaling => PresentationSource.FromVisual(this)?.CompositionTarget?.TransformToDevice.M11 ?? 1;
+
+        IEnumerable<object> ITopLevelImpl.Surfaces => _surfaces;
+
+        private Size _previousSize;
+        protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize)
+        {
+            _finalSize = finalSize.ToAvaloniaSize();
+            if (_finalSize == _previousSize)
+                return finalSize;
+            _previousSize = _finalSize;
+            _ttl.Resized?.Invoke(finalSize.ToAvaloniaSize());
+            return base.ArrangeOverride(finalSize);
+        }
+
+        protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize)
+        {
+            ControlRoot.Measure(availableSize.ToAvaloniaSize());
+            return ControlRoot.DesiredSize.ToWpfSize();
+        }
+
+        protected override void OnRender(DrawingContext drawingContext)
+        {
+            if(ActualHeight == 0 || ActualWidth == 0)
+                return;
+            _ttl.Paint?.Invoke(new Rect(0, 0, ActualWidth, ActualHeight));
+            if (ImageSource != null)
+                drawingContext.DrawImage(ImageSource, new System.Windows.Rect(0, 0, ActualWidth, ActualHeight));
+        }
+
+        void ITopLevelImpl.Invalidate(Rect rect) => InvalidateVisual();
+
+        void ITopLevelImpl.SetInputRoot(IInputRoot inputRoot) => _inputRoot = inputRoot;
+
+        Point ITopLevelImpl.PointToClient(Point point) => PointFromScreen(point.ToWpfPoint()).ToAvaloniaPoint();
+
+        Point ITopLevelImpl.PointToScreen(Point point) => PointToScreen(point.ToWpfPoint()).ToAvaloniaPoint();
+
+        protected override void OnLostFocus(RoutedEventArgs e) => LostFocus?.Invoke();
+
+
+        InputModifiers GetModifiers()
+        {
+            var state = Keyboard.Modifiers;
+            var rv = default(InputModifiers);
+            if (state.HasFlag(ModifierKeys.Windows))
+                rv |= InputModifiers.Windows;
+            if (state.HasFlag(ModifierKeys.Alt))
+                rv |= InputModifiers.Alt;
+            if (state.HasFlag(ModifierKeys.Control))
+                rv |= InputModifiers.Control;
+            if (state.HasFlag(ModifierKeys.Shift))
+                rv |= InputModifiers.Shift;
+            //TODO: mouse modifiers
+
+
+            return rv;
+        }
+
+        void MouseEvent(RawMouseEventType type, MouseEventArgs e)
+            => _ttl.Input?.Invoke(new RawMouseEventArgs(_mouse, (uint)e.Timestamp, _inputRoot, type,
+            e.GetPosition(this).ToAvaloniaPoint(), GetModifiers()));
+
+        protected override void OnMouseDown(MouseButtonEventArgs e)
+        {
+            RawMouseEventType type;
+            if(e.ChangedButton == MouseButton.Left)
+                type = RawMouseEventType.LeftButtonDown;
+            else if (e.ChangedButton == MouseButton.Middle)
+                type = RawMouseEventType.MiddleButtonDown;
+            else if (e.ChangedButton == MouseButton.Right)
+                type = RawMouseEventType.RightButtonDown;
+            else
+                return;
+            MouseEvent(type, e);
+            Focus();
+        }
+
+        protected override void OnMouseUp(MouseButtonEventArgs e)
+        {
+            RawMouseEventType type;
+            if (e.ChangedButton == MouseButton.Left)
+                type = RawMouseEventType.LeftButtonUp;
+            else if (e.ChangedButton == MouseButton.Middle)
+                type = RawMouseEventType.MiddleButtonUp;
+            else if (e.ChangedButton == MouseButton.Right)
+                type = RawMouseEventType.RightButtonUp;
+            else
+                return;
+            MouseEvent(type, e);
+            Focus();
+        }
+
+        protected override void OnMouseMove(MouseEventArgs e)
+        {
+            MouseEvent(RawMouseEventType.Move, e);
+        }
+
+        protected override void OnMouseWheel(MouseWheelEventArgs e) =>
+            _ttl.Input?.Invoke(new RawMouseWheelEventArgs(_mouse, (uint) e.Timestamp, _inputRoot,
+                e.GetPosition(this).ToAvaloniaPoint(), new Vector(0, e.Delta), GetModifiers()));
+
+        protected override void OnMouseLeave(MouseEventArgs e) => MouseEvent(RawMouseEventType.LeaveWindow, e);
+
+        protected override void OnKeyDown(KeyEventArgs e)
+            => _ttl.Input?.Invoke(new RawKeyEventArgs(_keyboard, (uint) e.Timestamp, RawKeyEventType.KeyDown,
+                (Key) e.Key,
+                GetModifiers()));
+
+        protected override void OnKeyUp(KeyEventArgs e)
+            => _ttl.Input?.Invoke(new RawKeyEventArgs(_keyboard, (uint)e.Timestamp, RawKeyEventType.KeyUp,
+                (Key)e.Key,
+                GetModifiers()));
+
+        protected override void OnTextInput(TextCompositionEventArgs e) 
+            => _ttl.Input?.Invoke(new RawTextInputEventArgs(_keyboard, (uint) e.Timestamp, e.Text));
+
+        void ITopLevelImpl.SetCursor(IPlatformHandle cursor)
+        {
+            if (cursor == null)
+                Cursor = Cursors.Arrow;
+            else if (cursor.HandleDescriptor == "HCURSOR")
+                Cursor = CursorShim.FromHCursor(cursor.Handle);
+        }
+
+        Action<RawInputEventArgs> ITopLevelImpl.Input { get; set; } //TODO
+        Action<Rect> ITopLevelImpl.Paint { get; set; }
+        Action<Size> ITopLevelImpl.Resized { get; set; }
+        Action<double> ITopLevelImpl.ScalingChanged { get; set; }
+        Action ITopLevelImpl.Closed { get; set; }
+        public new event Action LostFocus;
+
+        internal Vector GetScaling()
+        {
+            var src = PresentationSource.FromVisual(this)?.CompositionTarget;
+            if (src == null)
+                return new Vector(1, 1);
+            return new Vector(src.TransformToDevice.M11, src.TransformToDevice.M22);
+        }
+    }
+}

+ 73 - 0
src/Windows/Avalonia.Win32.Interop/Wpf/WritableBitmapSurface.cs

@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using Avalonia.Controls.Platform.Surfaces;
+using Avalonia.Platform;
+using PixelFormat = Avalonia.Platform.PixelFormat;
+
+namespace Avalonia.Win32.Interop.Wpf
+{
+    class WritableBitmapSurface : IFramebufferPlatformSurface
+    {
+        private readonly WpfTopLevelImpl _impl;
+        private WriteableBitmap _bitmap;
+        public WritableBitmapSurface(WpfTopLevelImpl impl)
+        {
+            _impl = impl;
+        }
+
+        public ILockedFramebuffer Lock()
+        {
+            var scale = _impl.GetScaling();
+            var size = new Size(_impl.ActualWidth * scale.X, _impl.ActualHeight * scale.Y);
+            var dpi = scale * 96;
+            if (_bitmap == null || _bitmap.PixelWidth != (int) size.Width || _bitmap.PixelHeight != (int) size.Height)
+            {
+                _bitmap = new WriteableBitmap((int) size.Width, (int) size.Height, dpi.X, dpi.Y,
+                    PixelFormats.Bgra32, null);
+            }
+            return new LockedFramebuffer(_impl, _bitmap, dpi);
+        }
+
+        internal class LockedFramebuffer : ILockedFramebuffer
+        {
+            private readonly WpfTopLevelImpl _impl;
+            private readonly WriteableBitmap _bitmap;
+
+            public LockedFramebuffer(WpfTopLevelImpl impl, WriteableBitmap bitmap, Vector dpi)
+            {
+                _impl = impl;
+                _bitmap = bitmap;
+                Dpi = dpi;
+                _bitmap.Lock();
+            }
+
+            public void Dispose()
+            {
+                _bitmap.AddDirtyRect(new Int32Rect(0, 0, _bitmap.PixelWidth, _bitmap.PixelHeight));
+                _bitmap.Unlock();
+                /*
+                using (var fileStream = new FileStream("c:\\tools\\wat.png", FileMode.Create))
+                {
+                    BitmapEncoder encoder = new PngBitmapEncoder();
+                    encoder.Frames.Add(BitmapFrame.Create(_bitmap));
+                    encoder.Save(fileStream);
+                }*/
+                _impl.ImageSource = _bitmap;
+            }
+
+            public IntPtr Address => _bitmap.BackBuffer;
+            public int Width => _bitmap.PixelWidth;
+            public int Height => _bitmap.PixelHeight;
+            public int RowBytes => _bitmap.BackBufferStride;
+            public Vector Dpi { get; }
+            public PixelFormat Format => PixelFormat.Bgra8888;
+        }
+    }
+}

+ 0 - 1
src/Windows/Avalonia.Win32/Avalonia.Win32.csproj

@@ -57,7 +57,6 @@
     <Compile Include="Embedding\WinFormsAvaloniaControlHost.cs">
       <SubType>Component</SubType>
     </Compile>
-    <Compile Include="Embedding\WpfAvaloniaControlHost.cs" />
     <Compile Include="IconImpl.cs" />
     <Compile Include="WinFormsWin32Platform.cs" />
   </ItemGroup>

+ 0 - 52
src/Windows/Avalonia.Win32/Embedding/WpfAvaloniaControlHost.cs

@@ -1,52 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Runtime.InteropServices;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Forms.Integration;
-using System.Windows.Interop;
-using Avalonia.Controls;
-using Avalonia.Win32.Interop;
-
-namespace Avalonia.Win32.Embedding
-{
-    public class WpfAvaloniaControlHost : HwndHost
-    {
-        private WinFormsAvaloniaControlHost _host;
-        private Avalonia.Controls.Control _content;
-
-        public Avalonia.Controls.Control Content
-        {
-            get { return _content; }
-            set
-            {
-                if (_host != null)
-                    _host.Content = value;
-                _content = value;
-                
-            }
-        }
-
-        void DestroyHost()
-        {
-            _host?.Dispose();
-            _host = null;
-        }
-
-        protected override HandleRef BuildWindowCore(HandleRef hwndParent)
-        {
-            DestroyHost();
-            _host = new WinFormsAvaloniaControlHost {Content = _content};
-            UnmanagedMethods.SetParent(_host.Handle, hwndParent.Handle);
-            return new HandleRef(this, _host.Handle);
-        }
-
-        protected override void DestroyWindowCore(HandleRef hwnd)
-        {
-            DestroyHost();
-        }
-    }
-}

Some files were not shown because too many files changed in this diff