Sfoglia il codice sorgente

Merge branch 'master' into portablexaml

danwalmsley 8 anni fa
parent
commit
e0dc2ceff7
100 ha cambiato i file con 1060 aggiunte e 569 eliminazioni
  1. 2 1
      .gitignore
  2. 43 0
      Avalonia.sln
  3. 1 0
      Avalonia.sln.DotSettings
  4. 1 0
      appveyor.yml
  5. 53 15
      build.cake
  6. 1 1
      build/Base.props
  7. 1 1
      build/Moq.props
  8. 2 1
      build/SkiaSharp.props
  9. 4 2
      build/XUnit.props
  10. 1 1
      docs/index.md
  11. 1 1
      docs/tutorial/from-wpf.md
  12. 1 1
      docs/tutorial/gettingstarted.md
  13. 35 10
      packages.cake
  14. 11 3
      parameters.cake
  15. 1 1
      readme.md
  16. 2 1
      samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj
  17. 23 0
      samples/ControlCatalog/Pages/MenuPage.xaml
  18. 1 0
      samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
  19. 1 1
      samples/interop/Direct3DInteropSample/MainWindow.cs
  20. 1 0
      samples/interop/GtkInteropDemo/GtkInteropDemo.csproj
  21. 13 1
      samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml
  22. 14 1
      samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs
  23. 7 1
      samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
  24. 5 0
      scripts/ReplaceNugetCache.ps1
  25. 7 0
      scripts/ReplaceNugetCache.sh
  26. 0 1
      src/Android/Avalonia.Android/AndroidPlatform.cs
  27. 2 0
      src/Android/Avalonia.Android/Platform/Input/AndroidMouseDevice.cs
  28. 1 1
      src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
  29. 3 0
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  30. 1 1
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
  31. 1 0
      src/Avalonia.Base/Avalonia.Base.csproj
  32. 3 8
      src/Avalonia.Base/AvaloniaObject.cs
  33. 9 16
      src/Avalonia.Base/Collections/AvaloniaDictionary.cs
  34. 33 90
      src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
  35. 8 30
      src/Avalonia.Base/Data/BindingNotification.cs
  36. 1 1
      src/Avalonia.Base/PriorityValue.cs
  37. 20 4
      src/Avalonia.Controls/AppBuilderBase.cs
  38. 24 14
      src/Avalonia.Controls/Button.cs
  39. 1 1
      src/Avalonia.Controls/ContextMenu.cs
  40. 11 4
      src/Avalonia.Controls/Control.cs
  41. 9 7
      src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
  42. 1 1
      src/Avalonia.Controls/ItemsControl.cs
  43. 1 1
      src/Avalonia.Controls/Menu.cs
  44. 10 1
      src/Avalonia.Controls/MenuItem.cs
  45. 7 0
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  46. 24 14
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  47. 1 2
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  48. 2 2
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  49. 2 2
      src/Avalonia.Controls/Primitives/Popup.cs
  50. 3 4
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  51. 11 0
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  52. 2 2
      src/Avalonia.Controls/Templates/TemplateExtensions.cs
  53. 5 2
      src/Avalonia.Controls/TextBox.cs
  54. 26 15
      src/Avalonia.Controls/ToolTip.cs
  55. 23 17
      src/Avalonia.Controls/TopLevel.cs
  56. 21 1
      src/Avalonia.Controls/TreeView.cs
  57. 30 13
      src/Avalonia.Controls/Window.cs
  58. 28 15
      src/Avalonia.Controls/WindowBase.cs
  59. 3 1
      src/Avalonia.DesignerSupport/DesignerAssist.cs
  60. 0 1
      src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj
  61. 14 16
      src/Avalonia.Diagnostics/DevTools.xaml.cs
  62. 1 2
      src/Avalonia.Diagnostics/ViewModels/ControlDetailsViewModel.cs
  63. 43 31
      src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs
  64. 2 2
      src/Avalonia.Diagnostics/ViewModels/LogicalTreeNode.cs
  65. 4 7
      src/Avalonia.Diagnostics/ViewModels/PropertyDetails.cs
  66. 5 5
      src/Avalonia.Diagnostics/ViewModels/TreeNode.cs
  67. 14 10
      src/Avalonia.Diagnostics/ViewModels/TreePageViewModel.cs
  68. 32 0
      src/Avalonia.Diagnostics/ViewModels/ViewModelBase.cs
  69. 3 4
      src/Avalonia.Diagnostics/ViewModels/VisualTreeNode.cs
  70. 10 10
      src/Avalonia.Diagnostics/Views/ControlDetailsView.cs
  71. 30 0
      src/Avalonia.Diagnostics/Views/PropertyChangedExtenions.cs
  72. 7 1
      src/Avalonia.DotNetFrameworkRuntime/AppBuilder.cs
  73. 4 1
      src/Avalonia.HtmlRenderer/Adapters/ControlAdapter.cs
  74. 2 1
      src/Avalonia.HtmlRenderer/HtmlControl.cs
  75. 3 2
      src/Avalonia.Input/FocusManager.cs
  76. 15 0
      src/Avalonia.Input/ICustomKeyboardNavigation.cs
  77. 7 0
      src/Avalonia.Input/IInputDevice.cs
  78. 8 0
      src/Avalonia.Input/IInputRoot.cs
  79. 2 1
      src/Avalonia.Input/IKeyboardDevice.cs
  80. 1 0
      src/Avalonia.Input/InputManager.cs
  81. 3 9
      src/Avalonia.Input/KeyboardDevice.cs
  82. 27 0
      src/Avalonia.Input/KeyboardNavigationHandler.cs
  83. 8 23
      src/Avalonia.Input/MouseDevice.cs
  84. 37 25
      src/Avalonia.Input/Navigation/DirectionalNavigation.cs
  85. 3 3
      src/Avalonia.Input/Navigation/FocusExtensions.cs
  86. 72 35
      src/Avalonia.Input/Navigation/TabNavigation.cs
  87. 10 0
      src/Avalonia.Layout/IEmbeddedLayoutRoot.cs
  88. 66 33
      src/Avalonia.Layout/LayoutManager.cs
  89. 25 9
      src/Avalonia.Layout/Layoutable.cs
  90. 1 1
      src/Avalonia.Styling/Controls/NameScope.cs
  91. 10 0
      src/Avalonia.Styling/LogicalTree/ILogical.cs
  92. 3 3
      src/Avalonia.Styling/LogicalTree/LogicalExtensions.cs
  93. 7 7
      src/Avalonia.Styling/Styling/DescendentSelector.cs
  94. 3 3
      src/Avalonia.Styling/Styling/Selectors.cs
  95. 2 2
      src/Avalonia.Styling/Styling/Style.cs
  96. 1 1
      src/Avalonia.Visuals/Platform/ILockedFramebuffer.cs
  97. 1 1
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  98. 1 1
      src/Avalonia.Visuals/Rendering/ZIndexComparer.cs
  99. 11 2
      src/Avalonia.Visuals/Vector.cs
  100. 3 3
      src/Avalonia.Visuals/Visual.cs

+ 2 - 1
.gitignore

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

+ 43 - 0
Avalonia.sln

@@ -193,6 +193,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
@@ -3191,6 +3193,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
@@ -3252,5 +3294,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

+ 1 - 0
Avalonia.sln.DotSettings

@@ -1,4 +1,5 @@
 <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=3E53A01A_002DB331_002D47F3_002DB828_002D4A5717E77A24_002Fd_003Aglass/@EntryIndexedValue">ExplicitlyExcluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=6417B24E_002D49C2_002D4985_002D8DB2_002D3AB9D898EC91/@EntryIndexedValue">ExplicitlyExcluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=E3A1060B_002D50D0_002D44E8_002D88B6_002DF44EF2E5BD72_002Ff_003Ahtml_002Ehtm/@EntryIndexedValue">ExplicitlyExcluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantUsingDirective/@EntryIndexedValue">HINT</s:String>

+ 1 - 0
appveyor.yml

@@ -35,6 +35,7 @@ test: off
 artifacts:
   - path: artifacts\nuget\*.nupkg
   - path: artifacts\zip\*.zip
+  - path: artifacts\inspectcode.xml
 cache:
   - gtk-sharp-2.12.26.msi
   - dotnet-1.0.1.exe

+ 53 - 15
build.cake

@@ -5,12 +5,13 @@
 #addin "nuget:?package=Polly&version=4.2.0"
 #addin "nuget:?package=NuGet.Core&version=2.12.0"
 #tool "nuget:https://dotnet.myget.org/F/nuget-build/?package=NuGet.CommandLine&version=4.3.0-preview1-3980&prerelease"
-#tool "nuget:?package=JetBrains.dotMemoryUnit&version=2.1.20150828.125449"
+#tool "nuget:?package=JetBrains.dotMemoryUnit&version=2.3.20160517.113140"
+#tool "JetBrains.ReSharper.CommandLineTools"
 ///////////////////////////////////////////////////////////////////////////////
 // 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"
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -97,14 +98,13 @@ Task("Clean")
     CleanDirectory(parameters.TestsRoot);
 });
 
-
 Task("Restore-NuGet-Packages")
     .IsDependentOn("Clean")
     .WithCriteria(parameters.IsRunningOnWindows)
     .Does(() =>
 {
     var maxRetryCount = 5;
-    var toolTimeout = 1d;
+    var toolTimeout = 2d;
     Policy
         .Handle<Exception>()
         .Retry(maxRetryCount, (exception, retryCount, context) => {
@@ -170,23 +170,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")
@@ -279,11 +281,15 @@ Task("Zip-Files")
     Zip(parameters.ZipSourceControlCatalogDesktopDirs, 
         parameters.ZipTargetControlCatalogDesktopDirs, 
         GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dll") + 
+        GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.config") + 
+        GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.so") + 
+        GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dylib") + 
         GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.exe"));
 });
 
 Task("Create-NuGet-Packages")
     .IsDependentOn("Run-Unit-Tests")
+    .IsDependentOn("Inspect")
     .Does(() =>
 {
     foreach(var nuspec in packages.NuspecNuGetSettings)
@@ -331,7 +337,6 @@ Task("Publish-NuGet")
     .WithCriteria(() => !parameters.IsLocalBuild)
     .WithCriteria(() => !parameters.IsPullRequest)
     .WithCriteria(() => parameters.IsMainRepo)
-    .WithCriteria(() => parameters.IsMasterBranch)
     .WithCriteria(() => parameters.IsNuGetRelease)
     .Does(() =>
 {
@@ -360,6 +365,39 @@ Task("Publish-NuGet")
     Information("Publish-NuGet Task failed, but continuing with next Task...");
 });
 
+Task("Inspect")
+    .WithCriteria(parameters.IsRunningOnWindows)
+    .IsDependentOn("Restore-NuGet-Packages")
+    .Does(() =>
+    {
+        var badIssues = new []{"PossibleNullReferenceException"};
+        var whitelist = new []{"tests", "src\\android", "src\\ios",
+            "src\\windows\\avalonia.designer", "src\\avalonia.htmlrenderer\\external"};
+        Information("Running code inspections");
+        
+        
+        StartProcess("tools\\JetBrains.ReSharper.CommandLineTools\\tools\\inspectcode.exe",
+            new ProcessSettings{ Arguments = "--output=artifacts\\inspectcode.xml --profile=Avalonia.sln.DotSettings Avalonia.sln" });
+        Information("Analyzing report");
+        var doc = XDocument.Parse(System.IO.File.ReadAllText("artifacts\\inspectcode.xml"));
+        var failBuild = false;
+        foreach(var xml in doc.Descendants("Issue"))
+        {
+            var typeId = xml.Attribute("TypeId").Value.ToString();
+            if(badIssues.Contains(typeId))
+            {
+                var file = xml.Attribute("File").Value.ToString().ToLower();
+                if(whitelist.Any(wh => file.StartsWith(wh)))
+                    continue;
+                var line = xml.Attribute("Line").Value.ToString();
+                Error(typeId + " - " + file + " on line " + line);
+                failBuild = true;
+            }
+        }
+        if(failBuild)
+            throw new Exception("Issues found");
+    });
+
 ///////////////////////////////////////////////////////////////////////////////
 // TARGETS
 ///////////////////////////////////////////////////////////////////////////////

+ 1 - 1
build/SkiaSharp.Desktop.props → build/Base.props

@@ -1,5 +1,5 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <PackageReference Include="Avalonia.Skia.Linux.Natives" Version="1.56.1.3" />
+    <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>

+ 2 - 1
build/SkiaSharp.props

@@ -1,5 +1,6 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <PackageReference Include="SkiaSharp" Version="1.56.1-beta" />
+    <PackageReference Include="SkiaSharp" Version="1.57.1" />
+    <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/index.md

@@ -10,7 +10,7 @@ What does alpha mean? Well, it means that it's now at a stage where you can have
 
 ## How do I try it out
 
-The easiest way to try out Avalonia is to install the [Visual Studio Extension](https://visualstudiogallery.msdn.microsoft.com/a4542e8a-b56c-4295-8df1-7e220178b873).
+The easiest way to try out Avalonia is to install the [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio).
 
 This will add a Avalonia project template and a Window template to the standard Visual Studo "Add" dialog (yes, icons still to come :) ):
 

+ 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 -->

+ 1 - 1
docs/tutorial/gettingstarted.md

@@ -4,7 +4,7 @@
 
 ![](images/add-dialogs.png)
 
-The easiest way to try out Avalonia is to install the [Visual Studio Extension](https://visualstudiogallery.msdn.microsoft.com/e1c6ae1f-6fd9-467d-8f62-1e28b4225213).
+The easiest way to try out Avalonia is to install the [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio).
 
 This will add a Avalonia project template and a Window template to the standard Visual Studo “Add”
 dialog (yes, icons still to come :) ):

+ 35 - 10
packages.cake

@@ -7,6 +7,7 @@ public class Packages
     public FilePath[] BinFiles { get; private set; }
     public string NugetPackagesDir {get; private set;}
     public string SkiaSharpVersion {get; private set; }
+    public string SkiaSharpLinuxVersion {get; private set; }
     public Packages(ICakeContext context, Parameters parameters)
     {
         // NUGET NUSPECS
@@ -74,7 +75,9 @@ 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;
@@ -84,7 +87,9 @@ public class Packages
         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);
@@ -194,6 +199,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" },
@@ -201,7 +207,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)
@@ -425,10 +432,7 @@ public class Packages
                 {
                     new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version },
                     new NuSpecDependency() { Id = "SkiaSharp", Version = SkiaSharpVersion },
-                    //netstandard1.3
-                    new NuSpecDependency() { Id = "Avalonia", TargetFramework = "netstandard1.3", Version = parameters.Version },
-                    new NuSpecDependency() { Id = "SkiaSharp", TargetFramework = "netstandard1.3", Version = SkiaSharpVersion },
-                    new NuSpecDependency() { Id = "NETStandard.Library", TargetFramework = "netstandard1.3", Version = "1.6.0" }
+                    new NuSpecDependency() { Id = "Avalonia.Skia.Linux.Natives", Version = SkiaSharpLinuxVersion }
                 },
                 Files = new []
                 {
@@ -446,11 +450,17 @@ public class Packages
                 Id = "Avalonia.Desktop",
                 Dependencies = new []
                 {
-                    new NuSpecDependency() { Id = "Avalonia.Win32", Version = parameters.Version },
-                    new NuSpecDependency() { Id = "Avalonia.Direct2D1", Version = parameters.Version },
-                    new NuSpecDependency() { Id = "Avalonia.Gtk", Version = parameters.Version },
-                    new NuSpecDependency() { Id = "Avalonia.Cairo", Version = parameters.Version },
-                    new NuSpecDependency() { Id = "Avalonia.Skia.Desktop", Version = parameters.Version }
+                    //Full .NET
+                    new NuSpecDependency() { Id = "Avalonia.Direct2D1", TargetFramework="net45", Version = parameters.Version },
+                    new NuSpecDependency() { Id = "Avalonia.Gtk", TargetFramework="net45", Version = parameters.Version },
+                    new NuSpecDependency() { Id = "Avalonia.Cairo", TargetFramework="net45", Version = parameters.Version },
+                    new NuSpecDependency() { Id = "Avalonia.Win32", TargetFramework="net45", Version = parameters.Version },
+                    new NuSpecDependency() { Id = "Avalonia.Skia.Desktop", TargetFramework="net45", Version = parameters.Version },
+                    new NuSpecDependency() { Id = "Avalonia.Gtk3", TargetFramework="net45", Version = parameters.Version },
+                    //.NET Core
+                    new NuSpecDependency() { Id = "Avalonia.Win32", TargetFramework="netcoreapp1.1", Version = parameters.Version },
+                    new NuSpecDependency() { Id = "Avalonia.Skia.Desktop", TargetFramework="netcoreapp1.1", Version = parameters.Version },
+                    new NuSpecDependency() { Id = "Avalonia.Gtk3", TargetFramework="netcoreapp1.1", Version = parameters.Version }
                 },
                 Files = new NuSpecContent[]
                 {
@@ -459,6 +469,21 @@ 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 },
+                },
+                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
             ///////////////////////////////////////////////////////////////////////////////

+ 11 - 3
parameters.cake

@@ -75,17 +75,25 @@ public class Parameters
         IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleasePlatform, Platform) 
                     && StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration);
         IsMyGetRelease = !IsTagged && IsReleasable;
-        IsNuGetRelease = IsTagged && IsReleasable;
+        
 
         // VERSION
         Version = context.Argument("force-nuget-version", context.ParseAssemblyInfo(AssemblyInfoPath).AssemblyVersion);
 
         if (IsRunningOnAppVeyor)
         {
+            string tagVersion = null;
             if (IsTagged)
             {
-                // Use Tag Name as version
-                Version = buildSystem.AppVeyor.Environment.Repository.Tag.Name;
+                var tag = buildSystem.AppVeyor.Environment.Repository.Tag.Name;
+                var nugetReleasePrefix = "nuget-release-";
+                IsNuGetRelease = IsTagged && IsReleasable && tag.StartsWith(nugetReleasePrefix);
+                if(IsNuGetRelease)
+                    tagVersion = tag.Substring(nugetReleasePrefix.Length);
+            }
+            if(tagVersion != null)
+            {
+                Version = tagVersion;
             }
             else
             {

+ 1 - 1
readme.md

@@ -42,7 +42,7 @@ using Direct2D and other operating systems using Gtk & Cairo.
 
 Avalonia is now in alpha. What does "alpha" mean? Well, it means that it's now at a stage where you
 can have a play and hopefully create simple applications. There's now a [Visual
-Studio Extension](https://visualstudiogallery.msdn.microsoft.com/e1c6ae1f-6fd9-467d-8f62-1e28b4225213)
+Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio)
 containing project and item templates that will help you get started, and
 there's an initial complement of controls. There's still a lot missing, and you
 *will* find bugs, and the API *will* change, but this represents the first time

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

@@ -26,7 +26,7 @@
     <WarningLevel>4</WarningLevel>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <PlatformTarget>AnyCPU</PlatformTarget>
+    <PlatformTarget>x86</PlatformTarget>
     <DebugType>pdbonly</DebugType>
     <Optimize>true</Optimize>
     <OutputPath>bin\Release\</OutputPath>
@@ -142,6 +142,7 @@
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <Import Project="..\..\build\Serilog.props" />
+  <Import Project="..\..\build\SkiaSharp.props" />
   <Import Project="..\..\build\Serilog.Sinks.Trace.props" />
   <Import Project="$(MSBuildThisFileDirectory)..\..\src\Shared\nuget.workaround.targets" />
 </Project>

+ 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>

+ 1 - 0
samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj

@@ -22,6 +22,7 @@
     <ItemGroup>
         <ProjectReference Include="..\..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" />
         <ProjectReference Include="..\..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj" />
+        <ProjectReference Include="..\..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
         <ProjectReference Include="..\..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
         <ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" />
         <ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" />

+ 1 - 1
samples/interop/Direct3DInteropSample/MainWindow.cs

@@ -58,7 +58,7 @@ namespace Direct3DInteropSample
                    new ModeDescription((int)ClientSize.Width, (int)ClientSize.Height,
                             new Rational(60, 1), Format.R8G8B8A8_UNorm),
                 IsWindowed = true,
-                OutputHandle = PlatformImpl.Handle.Handle,
+                OutputHandle = PlatformImpl?.Handle.Handle ?? IntPtr.Zero,
                 SampleDescription = new SampleDescription(1, 0),
                 SwapEffect = SwapEffect.Discard,
                 Usage = Usage.RenderTargetOutput

+ 1 - 0
samples/interop/GtkInteropDemo/GtkInteropDemo.csproj

@@ -149,6 +149,7 @@
       <Name>ControlCatalog</Name>
     </ProjectReference>
   </ItemGroup>
+  <Import Project="..\..\..\build\Rx.props" />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <Import Project="$(MSBuildThisFileDirectory)..\..\..\src\Shared\nuget.workaround.targets" />
 </Project>

+ 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 += "!";
+            };
+
         }
     }
 }

+ 7 - 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>
@@ -179,6 +183,8 @@
       <Generator>MSBuild:Compile</Generator>
     </Page>
   </ItemGroup>
+  <Import Project="..\..\..\build\Rx.props" />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Import Project="..\..\..\build\SkiaSharp.props" />
   <Import Project="$(MSBuildThisFileDirectory)..\..\..\src\Shared\nuget.workaround.targets" />
 </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>

+ 3 - 8
src/Avalonia.Base/AvaloniaObject.cs

@@ -622,14 +622,9 @@ namespace Avalonia
         /// <returns>The default value.</returns>
         private object GetDefaultValue(AvaloniaProperty property)
         {
-            if (property.Inherits && _inheritanceParent != null)
-            {
-                return (_inheritanceParent as AvaloniaObject).GetValueInternal(property);
-            }
-            else
-            {
-                return ((IStyledPropertyAccessor)property).GetDefaultValue(GetType());
-            }
+            if (property.Inherits && _inheritanceParent is AvaloniaObject aobj)
+                return aobj.GetValueInternal(property);
+            return ((IStyledPropertyAccessor) property).GetDefaultValue(GetType());
         }
 
         /// <summary>

+ 9 - 16
src/Avalonia.Base/Collections/AvaloniaDictionary.cs

@@ -103,11 +103,9 @@ namespace Avalonia.Collections
 
             _inner = new Dictionary<TKey, TValue>();
 
-            if (PropertyChanged != null)
-            {
-                PropertyChanged(this, new PropertyChangedEventArgs("Count"));
-                PropertyChanged(this, new PropertyChangedEventArgs($"Item[]"));
-            }
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[]"));
+            
 
             if (CollectionChanged != null)
             {
@@ -144,12 +142,9 @@ namespace Avalonia.Collections
 
             if (_inner.TryGetValue(key, out value))
             {
-                if (PropertyChanged != null)
-                {
-                    PropertyChanged(this, new PropertyChangedEventArgs("Count"));
-                    PropertyChanged(this, new PropertyChangedEventArgs($"Item[{key}]"));
-                }
-
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]"));
+                
                 if (CollectionChanged != null)
                 {
                     var e = new NotifyCollectionChangedEventArgs(
@@ -199,11 +194,9 @@ namespace Avalonia.Collections
 
         private void NotifyAdd(TKey key, TValue value)
         {
-            if (PropertyChanged != null)
-            {
-                PropertyChanged(this, new PropertyChangedEventArgs("Count"));
-                PropertyChanged(this, new PropertyChangedEventArgs($"Item[{key}]"));
-            }
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]"));
+            
 
             if (CollectionChanged != null)
             {

+ 33 - 90
src/Avalonia.Base/Collections/AvaloniaListExtensions.cs

@@ -2,6 +2,7 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.ComponentModel;
@@ -59,7 +60,8 @@ namespace Avalonia.Collections
         /// the index in the collection and the item.
         /// </param>
         /// <param name="reset">
-        /// An action called when the collection is reset.
+        /// An action called when the collection is reset. This will be followed by calls to 
+        /// <paramref name="added"/> for each item present in the collection after the reset.
         /// </param>
         /// <returns>A disposable used to terminate the subscription.</returns>
         public static IDisposable ForEachItem<T>(
@@ -68,112 +70,38 @@ namespace Avalonia.Collections
             Action<int, T> removed,
             Action reset)
         {
-            int index;
-
-            NotifyCollectionChangedEventHandler handler = (_, e) =>
+            void Add(int index, IList items)
             {
-                switch (e.Action)
+                foreach (T item in items)
                 {
-                    case NotifyCollectionChangedAction.Add:
-                        index = e.NewStartingIndex;
-
-                        foreach (T item in e.NewItems)
-                        {
-                            added(index++, item);
-                        }
-
-                        break;
-
-                    case NotifyCollectionChangedAction.Replace:
-                        index = e.OldStartingIndex;
-
-                        foreach (T item in e.OldItems)
-                        {
-                            removed(index++, item);
-                        }
-
-                        index = e.NewStartingIndex;
-
-                        foreach (T item in e.NewItems)
-                        {
-                            added(index++, item);
-                        }
-
-                        break;
-
-                    case NotifyCollectionChangedAction.Remove:
-                        index = e.OldStartingIndex;
-
-                        foreach (T item in e.OldItems)
-                        {
-                            removed(index++, item);
-                        }
-
-                        break;
-
-                    case NotifyCollectionChangedAction.Reset:
-                        if (reset == null)
-                        {
-                            throw new InvalidOperationException(
-                                "Reset called on collection without reset handler.");
-                        }
-
-                        reset();
-                        break;
+                    added(index++, item);
                 }
-            };
+            }
 
-            index = 0;
-            foreach (T i in collection)
+            void Remove(int index, IList items)
             {
-                added(index++, i);
+                for (var i = items.Count - 1; i >= 0; --i)
+                {
+                    removed(index + i, (T)items[i]);
+                }
             }
 
-            collection.CollectionChanged += handler;
-
-            return Disposable.Create(() => collection.CollectionChanged -= handler);
-        }
-
-        /// <summary>
-        /// Invokes an action for each item in a collection and subsequently each item added or
-        /// removed from the collection.
-        /// </summary>
-        /// <typeparam name="T">The type of the collection items.</typeparam>
-        /// <param name="collection">The collection.</param>
-        /// <param name="added">
-        /// An action called initially with all items in the collection and subsequently with a
-        /// list of items added to the collection. The parameters passed are the index of the
-        /// first item added to the collection and the items added.
-        /// </param>
-        /// <param name="removed">
-        /// An action called with all items removed from the collection. The parameters passed 
-        /// are the index of the first item removed from the collection and the items removed.
-        /// </param>
-        /// <param name="reset">
-        /// An action called when the collection is reset.
-        /// </param>
-        /// <returns>A disposable used to terminate the subscription.</returns>
-        public static IDisposable ForEachItem<T>(
-            this IAvaloniaReadOnlyList<T> collection,
-            Action<int, IEnumerable<T>> added,
-            Action<int, IEnumerable<T>> removed,
-            Action reset)
-        {
             NotifyCollectionChangedEventHandler handler = (_, e) =>
             {
                 switch (e.Action)
                 {
                     case NotifyCollectionChangedAction.Add:
-                        added(e.NewStartingIndex, e.NewItems.Cast<T>());
+                        Add(e.NewStartingIndex, e.NewItems);
                         break;
 
+                    case NotifyCollectionChangedAction.Move:
                     case NotifyCollectionChangedAction.Replace:
-                        removed(e.OldStartingIndex, e.OldItems.Cast<T>());
-                        added(e.NewStartingIndex, e.NewItems.Cast<T>());
+                        Remove(e.OldStartingIndex, e.OldItems);
+                        Add(e.NewStartingIndex, e.NewItems);
                         break;
 
                     case NotifyCollectionChangedAction.Remove:
-                        removed(e.OldStartingIndex, e.OldItems.Cast<T>());
+                        Remove(e.OldStartingIndex, e.OldItems);
                         break;
 
                     case NotifyCollectionChangedAction.Reset:
@@ -184,16 +112,31 @@ namespace Avalonia.Collections
                         }
 
                         reset();
+                        Add(0, (IList)collection);
                         break;
                 }
             };
 
-            added(0, collection);
+            Add(0, (IList)collection);
             collection.CollectionChanged += handler;
 
             return Disposable.Create(() => collection.CollectionChanged -= handler);
         }
 
+        public static IAvaloniaReadOnlyList<TDerived> CreateDerivedList<TSource, TDerived>(
+            this IAvaloniaReadOnlyList<TSource> collection,
+            Func<TSource, TDerived> select)
+        {
+            var result = new AvaloniaList<TDerived>();
+
+            collection.ForEachItem(
+                (i, item) => result.Insert(i, select(item)),
+                (i, item) => result.RemoveAt(i),
+                () => result.Clear());
+
+            return result;
+        }
+
         /// <summary>
         /// Listens for property changed events from all items in a collection.
         /// </summary>

+ 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/>

+ 1 - 1
src/Avalonia.Base/PriorityValue.cs

@@ -285,7 +285,7 @@ namespace Avalonia
                     Property.Name, 
                     _valueType, 
                     value,
-                    value.GetType());
+                    value?.GetType());
             }
         }
     }

+ 20 - 4
src/Avalonia.Controls/AppBuilderBase.cs

@@ -55,8 +55,7 @@ namespace Avalonia.Controls
         public Action<TAppBuilder> AfterSetupCallback { get; private set; } = builder => { };
 
         /// <summary>
-        /// Gets or sets a method to call before <see cref="Start{TMainWindow}"/> is called on the
-        /// <see cref="Application"/>.
+        /// Gets or sets a method to call before Startis called on the <see cref="Application"/>.
         /// </summary>
         public Action<TAppBuilder> BeforeStartCallback { get; private set; } = builder => { };
 
@@ -94,8 +93,7 @@ namespace Avalonia.Controls
         protected TAppBuilder Self => (TAppBuilder) this;
 
         /// <summary>
-        /// Registers a callback to call before <see cref="Start{TMainWindow}"/> is called on the
-        /// <see cref="Application"/>.
+        /// Registers a callback to call before Start is called on the <see cref="Application"/>.
         /// </summary>
         /// <param name="callback">The callback.</param>
         /// <returns>An <typeparamref name="TAppBuilder"/> instance.</returns>
@@ -129,6 +127,24 @@ namespace Avalonia.Controls
             Instance.Run(window);
         }
 
+        /// <summary>
+        /// Starts the application with the provided instance of <typeparamref name="TMainWindow"/>.
+        /// </summary>
+        /// <typeparam name="TMainWindow">The window type.</typeparam>
+        /// <param name="mainWindow">Instance of type TMainWindow to use when starting the app</param>
+        /// <param name="dataContextProvider">A delegate that will be called to create a data context for the window (optional).</param>
+        public void Start<TMainWindow>(TMainWindow mainWindow, Func<object> dataContextProvider = null)
+            where TMainWindow : Window
+        {
+            Setup();
+            BeforeStartCallback(Self);
+
+            if (dataContextProvider != null)
+                mainWindow.DataContext = dataContextProvider();
+            mainWindow.Show();
+            Instance.Run(mainWindow);
+        }
+
         /// <summary>
         /// Sets up the platform-specific services for the application, but does not run it.
         /// </summary>

+ 24 - 14
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();
+                }
             }
         }
 
@@ -275,7 +285,7 @@ namespace Avalonia.Controls
         {
             var button = e.Sender as Button;
             var isDefault = (bool)e.NewValue;
-            var inputRoot = button.VisualRoot as IInputElement;
+            var inputRoot = button?.VisualRoot as IInputElement;
 
             if (inputRoot != null)
             {

+ 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>

+ 11 - 4
src/Avalonia.Controls/Control.cs

@@ -118,6 +118,7 @@ namespace Avalonia.Controls
         public Control()
         {
             _nameScope = this as INameScope;
+            _isAttachedToLogicalTree = this is IStyleRoot;
         }
 
         /// <summary>
@@ -379,6 +380,12 @@ namespace Avalonia.Controls
             }
         }
 
+        /// <inheritdoc/>
+        void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
+        {
+            this.OnAttachedToLogicalTreeCore(e);
+        }
+
         /// <inheritdoc/>
         void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
         {
@@ -428,7 +435,7 @@ namespace Avalonia.Controls
 
                 if (_isAttachedToLogicalTree)
                 {
-                    var oldRoot = FindStyleRoot(old);
+                    var oldRoot = FindStyleRoot(old) ?? this as IStyleRoot;
 
                     if (oldRoot == null)
                     {
@@ -446,7 +453,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);
 
@@ -479,7 +486,7 @@ namespace Avalonia.Controls
             {
                 if (!IsInitialized)
                 {
-                    foreach (var i in this.GetSelfAndVisualDescendents())
+                    foreach (var i in this.GetSelfAndVisualDescendants())
                     {
                         var c = i as IControl;
 
@@ -651,7 +658,7 @@ namespace Avalonia.Controls
 
             if (_focusAdorner != null)
             {
-                var adornerLayer = _focusAdorner.Parent as Panel;
+                var adornerLayer = (IPanel)_focusAdorner.Parent;
                 adornerLayer.Children.Remove(_focusAdorner);
                 _focusAdorner = null;
             }

+ 9 - 7
src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs

@@ -4,6 +4,7 @@ using Avalonia.Input;
 using Avalonia.Layout;
 using Avalonia.Platform;
 using Avalonia.Styling;
+using JetBrains.Annotations;
 
 namespace Avalonia.Controls.Embedding
 {
@@ -18,8 +19,11 @@ namespace Avalonia.Controls.Embedding
         {
         }
 
+        [CanBeNull]
         public new IEmbeddableWindowImpl PlatformImpl => (IEmbeddableWindowImpl) base.PlatformImpl;
 
+        protected bool EnforceClientSize { get; set; } = true;
+
         public void Prepare()
         {
             EnsureInitialized();
@@ -36,11 +40,12 @@ namespace Avalonia.Controls.Embedding
                 init.EndInit();
             }
         }
-
+        
         protected override Size MeasureOverride(Size availableSize)
         {
-            base.MeasureOverride(PlatformImpl.ClientSize);
-            return PlatformImpl.ClientSize;
+            if (EnforceClientSize)
+                availableSize = PlatformImpl?.ClientSize ?? default(Size);
+            return base.MeasureOverride(availableSize);
         }
 
         private readonly NameScope _nameScope = new NameScope();
@@ -63,9 +68,6 @@ namespace Avalonia.Controls.Embedding
         public void Unregister(string name) => _nameScope.Unregister(name);
 
         Type IStyleable.StyleKey => typeof(EmbeddableControlRoot);
-        public void Dispose()
-        {
-            PlatformImpl.Dispose();
-        }
+        public void Dispose() => PlatformImpl?.Dispose();
     }
 }

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

@@ -354,7 +354,7 @@ namespace Avalonia.Controls
             }
 
             var collection = sender as ICollection;
-            PseudoClasses.Set(":empty", collection.Count == 0);
+            PseudoClasses.Set(":empty", collection == null || collection.Count == 0);
         }
 
         /// <summary>

+ 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();
         }
 

+ 1 - 2
src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs

@@ -155,8 +155,7 @@ namespace Avalonia.Controls.Presenters
                     case NotifyCollectionChangedAction.Add:
                         CreateAndRemoveContainers();
 
-                        if (e.NewStartingIndex >= FirstIndex &&
-                            e.NewStartingIndex < NextIndex)
+                        if (e.NewStartingIndex < NextIndex)
                         {
                             RecycleContainers();
                         }

+ 2 - 2
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@@ -111,7 +111,7 @@ namespace Avalonia.Controls.Presenters
         /// <param name="target">The target visual.</param>
         /// <param name="targetRect">The portion of the target visual to bring into view.</param>
         /// <returns>True if the scroll offset was changed; otherwise false.</returns>
-        public bool BringDescendentIntoView(IVisual target, Rect targetRect)
+        public bool BringDescendantIntoView(IVisual target, Rect targetRect)
         {
             if (Child == null)
             {
@@ -262,7 +262,7 @@ namespace Avalonia.Controls.Presenters
 
         private void BringIntoViewRequested(object sender, RequestBringIntoViewEventArgs e)
         {
-            e.Handled = BringDescendentIntoView(e.TargetObject, e.TargetRect);
+            e.Handled = BringDescendantIntoView(e.TargetObject, e.TargetRect);
         }
 
         private void ChildChanged(AvaloniaPropertyChangedEventArgs e)

+ 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);

+ 3 - 4
src/Avalonia.Controls/Primitives/PopupRoot.cs

@@ -9,6 +9,7 @@ using Avalonia.Layout;
 using Avalonia.Media;
 using Avalonia.Platform;
 using Avalonia.VisualTree;
+using JetBrains.Annotations;
 
 namespace Avalonia.Controls.Primitives
 {
@@ -49,6 +50,7 @@ namespace Avalonia.Controls.Primitives
         /// <summary>
         /// Gets the platform-specific window implementation.
         /// </summary>
+        [CanBeNull]
         public new IPopupImpl PlatformImpl => (IPopupImpl)base.PlatformImpl;
 
         /// <summary>
@@ -65,10 +67,7 @@ namespace Avalonia.Controls.Primitives
         IVisual IHostedVisualTreeRoot.Host => Parent;
 
         /// <inheritdoc/>
-        public void Dispose()
-        {
-            this.PlatformImpl.Dispose();
-        }
+        public void Dispose() => PlatformImpl?.Dispose();
 
         /// <inheritdoc/>
         protected override void OnTemplateApplied(TemplateAppliedEventArgs e)

+ 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)
         {

+ 2 - 2
src/Avalonia.Controls/Templates/TemplateExtensions.cs

@@ -31,9 +31,9 @@ namespace Avalonia.Controls.Templates
 
                 if (child.TemplatedParent != null)
                 {
-                    foreach (var descendent in GetTemplateChildren(child, templatedParent))
+                    foreach (var descendant in GetTemplateChildren(child, templatedParent))
                     {
-                        yield return descendent;
+                        yield return descendant;
                     }
                 }
             }

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

@@ -720,7 +720,7 @@ namespace Avalonia.Controls
                         if (pos < text.Length)
                         {
                             --pos;
-                            if (pos > 0 && Text[pos - 1] == '\r' && Text[pos] == '\n')
+                            if (pos > 0 && text[pos - 1] == '\r' && text[pos] == '\n')
                             {
                                 --pos;
                             }
@@ -771,6 +771,9 @@ namespace Avalonia.Controls
 
         private string GetSelection()
         {
+            var text = Text;
+            if (string.IsNullOrEmpty(text))
+                return "";
             var selectionStart = SelectionStart;
             var selectionEnd = SelectionEnd;
             var start = Math.Min(selectionStart, selectionEnd);
@@ -779,7 +782,7 @@ namespace Avalonia.Controls
             {
                 return "";
             }
-            return Text.Substring(start, end - start);
+            return text.Substring(start, end - start);
         }
 
         private int GetLine(int caretIndex, IList<FormattedTextLine> lines)

+ 26 - 15
src/Avalonia.Controls/ToolTip.cs

@@ -105,17 +105,21 @@ namespace Avalonia.Controls
         {
             if (control != null && control.IsVisible && control.GetVisualRoot() != null)
             {
-                if (s_popup != null)
+                var cp = (control.GetVisualRoot() as IInputRoot)?.MouseDevice?.GetPosition(control);
+                var position = control.PointToScreen(cp ?? new Point(0, 0)) + new Vector(0, 22);
+
+                if (s_popup == null)
                 {
-                    throw new AvaloniaInternalException("Previous ToolTip not disposed.");
+                    s_popup = new PopupRoot();
+                    s_popup.Content = new ToolTip();
+                }
+                else
+                {
+                    ((ISetLogicalParent)s_popup).SetParent(null);
                 }
 
-                var cp = MouseDevice.Instance?.GetPosition(control);
-                var position = control.PointToScreen(cp ?? new Point(0, 0)) + new Vector(0, 22);
-
-                s_popup = new PopupRoot();
                 ((ISetLogicalParent)s_popup).SetParent(control);
-                s_popup.Content = new ToolTip { Content = GetTip(control) };
+                ((ToolTip)s_popup.Content).Content = GetTip(control);
                 s_popup.Position = position;
                 s_popup.Show();
 
@@ -147,16 +151,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;
             }
         }
     }

+ 23 - 17
src/Avalonia.Controls/TopLevel.cs

@@ -14,6 +14,7 @@ using Avalonia.Platform;
 using Avalonia.Rendering;
 using Avalonia.Styling;
 using Avalonia.VisualTree;
+using JetBrains.Annotations;
 
 namespace Avalonia.Controls
 {
@@ -92,25 +93,25 @@ namespace Avalonia.Controls
             var rendererFactory = TryGetService<IRendererFactory>(dependencyResolver);
             Renderer = rendererFactory?.CreateRenderer(this, renderLoop);
 
-            PlatformImpl.SetInputRoot(this);
+            impl.SetInputRoot(this);
 
-            PlatformImpl.Closed = HandleClosed;
-            PlatformImpl.Input = HandleInput;
-            PlatformImpl.Paint = HandlePaint;
-            PlatformImpl.Resized = HandleResized;
-            PlatformImpl.ScalingChanged = HandleScalingChanged;
+            impl.Closed = HandleClosed;
+            impl.Input = HandleInput;
+            impl.Paint = HandlePaint;
+            impl.Resized = HandleResized;
+            impl.ScalingChanged = HandleScalingChanged;
 
 
             _keyboardNavigationHandler?.SetOwner(this);
             _accessKeyHandler?.SetOwner(this);
             styler?.ApplyStyles(this);
 
-            ClientSize = PlatformImpl.ClientSize;
+            ClientSize = impl.ClientSize;
             
             this.GetObservable(PointerOverElementProperty)
                 .Select(
                     x => (x as InputElement)?.GetObservable(CursorProperty) ?? Observable.Empty<Cursor>())
-                .Switch().Subscribe(cursor => PlatformImpl.SetCursor(cursor?.PlatformCursor));
+                .Switch().Subscribe(cursor => PlatformImpl?.SetCursor(cursor?.PlatformCursor));
 
             if (_applicationLifecycle != null)
             {
@@ -135,10 +136,8 @@ namespace Avalonia.Controls
         /// <summary>
         /// Gets the platform-specific window implementation.
         /// </summary>
-        public ITopLevelImpl PlatformImpl
-        {
-            get;
-        }
+        [CanBeNull]
+        public ITopLevelImpl PlatformImpl { get; private set; }
         
         /// <summary>
         /// Gets the renderer for the window.
@@ -164,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>
@@ -177,7 +179,7 @@ namespace Avalonia.Controls
         Size ILayoutRoot.MaxClientSize => Size.Infinity;
 
         /// <inheritdoc/>
-        double ILayoutRoot.LayoutScaling => PlatformImpl.Scaling;
+        double ILayoutRoot.LayoutScaling => PlatformImpl?.Scaling ?? 1;
 
         IStyleHost IStyleHost.StylingParent
         {
@@ -189,25 +191,27 @@ namespace Avalonia.Controls
         /// <inheritdoc/>
         protected virtual IRenderTarget CreateRenderTarget()
         {
+            if(PlatformImpl == null)
+                throw new InvalidOperationException("Cann't create render target, PlatformImpl is null (might be already disposed)");
             return _renderInterface.CreateRenderTarget(PlatformImpl.Surfaces);
         }
 
         /// <inheritdoc/>
         void IRenderRoot.Invalidate(Rect rect)
         {
-            PlatformImpl.Invalidate(rect);
+            PlatformImpl?.Invalidate(rect);
         }
 
         /// <inheritdoc/>
         Point IRenderRoot.PointToClient(Point p)
         {
-            return PlatformImpl.PointToClient(p);
+            return PlatformImpl?.PointToClient(p) ?? default(Point);
         }
 
         /// <inheritdoc/>
         Point IRenderRoot.PointToScreen(Point p)
         {
-            return PlatformImpl.PointToScreen(p);
+            return PlatformImpl?.PointToScreen(p) ?? default(Point);
         }
 
         /// <summary>
@@ -224,6 +228,8 @@ namespace Avalonia.Controls
         /// </summary>
         protected virtual void HandleClosed()
         {
+            PlatformImpl = null;
+
             Closed?.Invoke(this, EventArgs.Empty);
             Renderer?.Dispose();
             Renderer = null;
@@ -250,7 +256,7 @@ namespace Avalonia.Controls
         /// <param name="scaling">The window scaling.</param>
         protected virtual void HandleScalingChanged(double scaling)
         {
-            foreach (ILayoutable control in this.GetSelfAndVisualDescendents())
+            foreach (ILayoutable control in this.GetSelfAndVisualDescendants())
             {
                 control.InvalidateMeasure();
             }

+ 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()
         {

+ 30 - 13
src/Avalonia.Controls/Window.cs

@@ -12,6 +12,7 @@ using Avalonia.Platform;
 using Avalonia.Styling;
 using System.Collections.Generic;
 using System.Linq;
+using JetBrains.Annotations;
 
 namespace Avalonia.Controls
 {
@@ -46,12 +47,12 @@ namespace Avalonia.Controls
     /// </summary>
     public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot, INameScope
     {
-        private static IList<Window> s_windows = new List<Window>();
+        private static List<Window> s_windows = new List<Window>();
 
         /// <summary>
         /// Retrieves an enumeration of all Windows in the currently running application.
         /// </summary>
-        public static IList<Window> OpenWindows => s_windows;
+        public static IReadOnlyList<Window> OpenWindows => s_windows;
 
         /// <summary>
         /// Defines the <see cref="SizeToContent"/> property.
@@ -87,11 +88,11 @@ namespace Avalonia.Controls
         static Window()
         {
             BackgroundProperty.OverrideDefaultValue(typeof(Window), Brushes.White);
-            TitleProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl.SetTitle((string)e.NewValue));
+            TitleProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl?.SetTitle((string)e.NewValue));
             HasSystemDecorationsProperty.Changed.AddClassHandler<Window>(
-                (s, e) => s.PlatformImpl.SetSystemDecorations((bool) e.NewValue));
+                (s, e) => s.PlatformImpl?.SetSystemDecorations((bool) e.NewValue));
 
-            IconProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl.SetIcon(((WindowIcon)e.NewValue).PlatformImpl));
+            IconProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl?.SetIcon(((WindowIcon)e.NewValue).PlatformImpl));
         }
 
         /// <summary>
@@ -109,7 +110,7 @@ namespace Avalonia.Controls
         public Window(IWindowImpl impl)
             : base(impl)
         {
-            _maxPlatformClientSize = this.PlatformImpl.MaxClientSize;
+            _maxPlatformClientSize = PlatformImpl?.MaxClientSize ?? default(Size);
         }
 
         /// <inheritdoc/>
@@ -129,6 +130,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// Gets the platform-specific window implementation.
         /// </summary>
+        [CanBeNull]
         public new IWindowImpl PlatformImpl => (IWindowImpl)base.PlatformImpl;
 
         /// <summary>
@@ -164,8 +166,12 @@ namespace Avalonia.Controls
         /// </summary>
         public WindowState WindowState
         {
-            get { return this.PlatformImpl.WindowState; }
-            set { this.PlatformImpl.WindowState = value; }
+            get { return PlatformImpl?.WindowState ?? WindowState.Normal; }
+            set
+            {
+                if (PlatformImpl != null)
+                    PlatformImpl.WindowState = value;
+            }
         }
 
         /// <summary>
@@ -189,7 +195,7 @@ namespace Avalonia.Controls
         public void Close()
         {
             s_windows.Remove(this);
-            PlatformImpl.Dispose();
+            PlatformImpl?.Dispose();
             IsVisible = false;
         }
 
@@ -221,7 +227,7 @@ namespace Avalonia.Controls
         {
             using (BeginAutoSizing())
             {
-                PlatformImpl.Hide();
+                PlatformImpl?.Hide();
             }
 
             IsVisible = false;
@@ -232,6 +238,11 @@ namespace Avalonia.Controls
         /// </summary>
         public override void Show()
         {
+            if (IsVisible)
+            {
+                return;
+            }
+
             s_windows.Add(this);
 
             EnsureInitialized();
@@ -240,7 +251,7 @@ namespace Avalonia.Controls
 
             using (BeginAutoSizing())
             {
-                PlatformImpl.Show();
+                PlatformImpl?.Show();
             }
         }
 
@@ -266,6 +277,11 @@ namespace Avalonia.Controls
         /// </returns>
         public Task<TResult> ShowDialog<TResult>()
         {
+            if (IsVisible)
+            {
+                throw new InvalidOperationException("The window is already being shown.");
+            }
+
             s_windows.Add(this);
 
             EnsureInitialized();
@@ -278,7 +294,7 @@ namespace Avalonia.Controls
                 var activated = affectedWindows.Where(w => w.IsActive).FirstOrDefault();
                 SetIsEnabled(affectedWindows, false);
 
-                var modal = PlatformImpl.ShowDialog();
+                var modal = PlatformImpl?.ShowDialog();
                 var result = new TaskCompletionSource<TResult>();
 
                 Observable.FromEventPattern<EventHandler, EventArgs>(
@@ -287,7 +303,7 @@ namespace Avalonia.Controls
                     .Take(1)
                     .Subscribe(_ =>
                     {
-                        modal.Dispose();
+                        modal?.Dispose();
                         SetIsEnabled(affectedWindows, true);
                         activated?.Activate();
                         result.SetResult((TResult)_dialogResult);
@@ -354,6 +370,7 @@ namespace Avalonia.Controls
         protected override void HandleClosed()
         {
             IsVisible = false;
+            s_windows.Remove(this);
             base.HandleClosed();
         }
 

+ 28 - 15
src/Avalonia.Controls/WindowBase.cs

@@ -9,6 +9,7 @@ using Avalonia.Controls.Primitives;
 using Avalonia.Input;
 using Avalonia.Layout;
 using Avalonia.Platform;
+using JetBrains.Annotations;
 
 namespace Avalonia.Controls
 {
@@ -28,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;
 
@@ -43,10 +45,10 @@ namespace Avalonia.Controls
 
         public WindowBase(IWindowBaseImpl impl, IAvaloniaDependencyResolver dependencyResolver) : base(impl, dependencyResolver)
         {
-            PlatformImpl.Activated = HandleActivated;
-            PlatformImpl.Deactivated = HandleDeactivated;
-            PlatformImpl.PositionChanged = HandlePositionChanged;
-            this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl.Resize(x));
+            impl.Activated = HandleActivated;
+            impl.Deactivated = HandleDeactivated;
+            impl.PositionChanged = HandlePositionChanged;
+            this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x));
         }
 
         /// <summary>
@@ -64,6 +66,7 @@ namespace Avalonia.Controls
         /// </summary>
         public event EventHandler<PointEventArgs> PositionChanged;
 
+        [CanBeNull]
         public new IWindowBaseImpl PlatformImpl => (IWindowBaseImpl) base.PlatformImpl;
 
         /// <summary>
@@ -80,8 +83,12 @@ namespace Avalonia.Controls
         /// </summary>
         public Point Position
         {
-            get { return PlatformImpl.Position; }
-            set { PlatformImpl.Position = value; }
+            get { return PlatformImpl?.Position ?? default(Point); }
+            set
+            {
+                if (PlatformImpl is IWindowBaseImpl impl)
+                    impl.Position = value;
+            }
         }
 
         /// <summary>
@@ -98,7 +105,7 @@ namespace Avalonia.Controls
         /// </summary>
         public void Activate()
         {
-            PlatformImpl.Activate();
+            PlatformImpl?.Activate();
         }
 
         /// <summary>
@@ -110,7 +117,7 @@ namespace Avalonia.Controls
 
             try
             {
-                PlatformImpl.Hide();
+                PlatformImpl?.Hide();
                 IsVisible = false;
             }
             finally
@@ -130,8 +137,14 @@ namespace Avalonia.Controls
             {
                 EnsureInitialized();
                 IsVisible = true;
-                LayoutManager.Instance.ExecuteInitialLayoutPass(this);
-                PlatformImpl.Show();
+
+                if (!_hasExecutedInitialLayoutPass)
+                {
+                    LayoutManager.Instance.ExecuteInitialLayoutPass(this);
+                    _hasExecutedInitialLayoutPass = true;
+                }
+
+                PlatformImpl?.Show();
             }
             finally
             {
@@ -163,10 +176,10 @@ namespace Avalonia.Controls
         {
             using (BeginAutoSizing())
             {
-                PlatformImpl.Resize(finalSize);
+                PlatformImpl?.Resize(finalSize);
             }
 
-            return base.ArrangeOverride(PlatformImpl.ClientSize);
+            return base.ArrangeOverride(PlatformImpl?.ClientSize ?? default(Size));
         }
 
         /// <summary>
@@ -174,7 +187,7 @@ namespace Avalonia.Controls
         /// </summary>
         protected void EnsureInitialized()
         {
-            if (!this.IsInitialized)
+            if (!IsInitialized)
             {
                 var init = (ISupportInitialize)this;
                 init.BeginInit();
@@ -268,12 +281,12 @@ namespace Avalonia.Controls
         /// <summary>
         /// Starts moving a window with left button being held. Should be called from left mouse button press event handler
         /// </summary>
-        public void BeginMoveDrag() => PlatformImpl.BeginMoveDrag();
+        public void BeginMoveDrag() => PlatformImpl?.BeginMoveDrag();
 
         /// <summary>
         /// Starts resizing a window. This function is used if an application has window resizing controls. 
         /// Should be called from left mouse button press event handler
         /// </summary>
-        public void BeginResizeDrag(WindowEdge edge) => PlatformImpl.BeginResizeDrag(edge);
+        public void BeginResizeDrag(WindowEdge edge) => PlatformImpl?.BeginResizeDrag(edge);
     }
 }

+ 3 - 1
src/Avalonia.DesignerSupport/DesignerAssist.cs

@@ -75,7 +75,7 @@ namespace Avalonia.DesignerSupport
         private static void SetScalingFactor(double factor)
         {
             PlatformManager.SetDesignerScalingFactor(factor);
-            s_currentWindow?.PlatformImpl.Resize(s_currentWindow.ClientSize);
+            s_currentWindow?.PlatformImpl?.Resize(s_currentWindow.ClientSize);
         }
 
         static Window s_currentWindow;
@@ -149,6 +149,8 @@ namespace Avalonia.DesignerSupport
             s_currentWindow = window;
             window.Show();
             Design.ApplyDesignerProperties(window, control);
+            // ReSharper disable once PossibleNullReferenceException
+            // Always not null at this point
             Api.OnWindowCreated?.Invoke(window.PlatformImpl.Handle.Handle);
             Api.OnResize?.Invoke();
         }

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

@@ -34,7 +34,6 @@
     <ProjectReference Include="..\Avalonia.Input\Avalonia.Input.csproj" />
     <ProjectReference Include="..\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
     <ProjectReference Include="..\Avalonia.Layout\Avalonia.Layout.csproj" />
-    <ProjectReference Include="..\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
     <ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
     <ProjectReference Include="..\Avalonia.Styling\Avalonia.Styling.csproj" />
     <ProjectReference Include="..\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />

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

@@ -11,15 +11,14 @@ using Avalonia.Input.Raw;
 using Avalonia.Interactivity;
 using Avalonia.Markup.Xaml;
 using Avalonia.VisualTree;
-using ReactiveUI;
 
 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);
 		}
 	}
 }
@@ -28,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)
@@ -44,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);
@@ -56,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
                     {
@@ -74,12 +73,12 @@ namespace Avalonia.Diagnostics
                         Content = devTools,
                         DataTemplates = new DataTemplates
                         {
-                            new ViewLocator<ReactiveObject>(),
+                            new ViewLocator<ViewModelBase>(),
                         }
                     };
 
                     devToolsWindow.Closed += devTools.DevToolsClosed;
-                    s_open.Add((Window)sender, devToolsWindow);
+                    s_open.Add(control, devToolsWindow);
                     devToolsWindow.Show();
                 }
             }
@@ -89,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;
         }
@@ -107,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();
 

+ 1 - 2
src/Avalonia.Diagnostics/ViewModels/ControlDetailsViewModel.cs

@@ -4,11 +4,10 @@
 using System.Collections.Generic;
 using System.Linq;
 using Avalonia.VisualTree;
-using ReactiveUI;
 
 namespace Avalonia.Diagnostics.ViewModels
 {
-    internal class ControlDetailsViewModel : ReactiveObject
+    internal class ControlDetailsViewModel : ViewModelBase
     {
         public ControlDetailsViewModel(IVisual control)
         {

+ 43 - 31
src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs

@@ -5,32 +5,50 @@ using System;
 using System.Reactive.Linq;
 using Avalonia.Controls;
 using Avalonia.Input;
-using ReactiveUI;
 
 namespace Avalonia.Diagnostics.ViewModels
 {
-    internal class DevToolsViewModel : ReactiveObject
+    internal class DevToolsViewModel : ViewModelBase
     {
-        private ReactiveObject _content;
-
+        private ViewModelBase _content;
         private int _selectedTab;
-
         private TreePageViewModel _logicalTree;
-
         private TreePageViewModel _visualTree;
-
-        private readonly ObservableAsPropertyHelper<string> _focusedControl;
-
-        private readonly ObservableAsPropertyHelper<string> _pointerOverElement;
+        private string _focusedControl;
+        private string _pointerOverElement;
 
         public DevToolsViewModel(IControl root)
         {
             _logicalTree = new TreePageViewModel(LogicalTreeNode.Create(root));
             _visualTree = new TreePageViewModel(VisualTreeNode.Create(root));
 
-            this.WhenAnyValue(x => x.SelectedTab).Subscribe(index =>
+            UpdateFocusedControl();
+            KeyboardDevice.Instance.PropertyChanged += (s, e) =>
             {
-                switch (index)
+                if (e.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement))
+                {
+                    UpdateFocusedControl();
+                }
+            };
+
+            root.GetObservable(TopLevel.PointerOverElementProperty)
+                .Subscribe(x => PointerOverElement = x?.GetType().Name);
+        }
+
+        public ViewModelBase Content
+        {
+            get { return _content; }
+            private set { RaiseAndSetIfChanged(ref _content, value); }
+        }
+
+        public int SelectedTab
+        {
+            get { return _selectedTab; }
+            set
+            {
+                _selectedTab = value;
+
+                switch (value)
                 {
                     case 0:
                         Content = _logicalTree;
@@ -39,34 +57,23 @@ namespace Avalonia.Diagnostics.ViewModels
                         Content = _visualTree;
                         break;
                 }
-            });
 
-            _focusedControl = KeyboardDevice.Instance
-                .WhenAnyValue(x => x.FocusedElement)
-                .Select(x => x?.GetType().Name)
-                .ToProperty(this, x => x.FocusedControl);
-
-            _pointerOverElement = root.GetObservable(TopLevel.PointerOverElementProperty)
-                .Select(x => x?.GetType().Name)
-                .ToProperty(this, x => x.PointerOverElement);
+                RaisePropertyChanged();
+            }
         }
 
-        public ReactiveObject Content
+        public string FocusedControl
         {
-            get { return _content; }
-            private set { this.RaiseAndSetIfChanged(ref _content, value); }
+            get { return _focusedControl; }
+            private set { RaiseAndSetIfChanged(ref _focusedControl, value); }
         }
 
-        public int SelectedTab
+        public string PointerOverElement
         {
-            get { return _selectedTab; }
-            set { this.RaiseAndSetIfChanged(ref _selectedTab, value); }
+            get { return _pointerOverElement; }
+            private set { RaiseAndSetIfChanged(ref _pointerOverElement, value); }
         }
 
-        public string FocusedControl => _focusedControl.Value;
-
-        public string PointerOverElement => _pointerOverElement.Value;
-
         public void SelectControl(IControl control)
         {
             var tree = Content as TreePageViewModel;
@@ -76,5 +83,10 @@ namespace Avalonia.Diagnostics.ViewModels
                 tree.SelectControl(control);
             }
         }
+
+        private void UpdateFocusedControl()
+        {
+            _focusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name;
+        }
     }
 }

+ 2 - 2
src/Avalonia.Diagnostics/ViewModels/LogicalTreeNode.cs

@@ -2,9 +2,9 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using Avalonia.Collections;
 using Avalonia.Controls;
 using Avalonia.LogicalTree;
-using ReactiveUI;
 
 namespace Avalonia.Diagnostics.ViewModels
 {
@@ -13,7 +13,7 @@ namespace Avalonia.Diagnostics.ViewModels
         public LogicalTreeNode(ILogical logical, TreeNode parent)
             : base((Control)logical, parent)
         {
-            Children = logical.LogicalChildren.CreateDerivedCollection(x => new LogicalTreeNode(x, this));
+            Children = logical.LogicalChildren.CreateDerivedList(x => new LogicalTreeNode(x, this));
         }
 
         public static LogicalTreeNode[] Create(object control)

+ 4 - 7
src/Avalonia.Diagnostics/ViewModels/PropertyDetails.cs

@@ -3,16 +3,13 @@
 
 using System;
 using Avalonia.Data;
-using ReactiveUI;
 
 namespace Avalonia.Diagnostics.ViewModels
 {
-    internal class PropertyDetails : ReactiveObject
+    internal class PropertyDetails : ViewModelBase
     {
         private object _value;
-
         private string _priority;
-
         private string _diagnostic;
 
         public PropertyDetails(AvaloniaObject o, AvaloniaProperty property)
@@ -41,19 +38,19 @@ namespace Avalonia.Diagnostics.ViewModels
         public string Priority
         {
             get { return _priority; }
-            private set { this.RaiseAndSetIfChanged(ref _priority, value); }
+            private set { RaiseAndSetIfChanged(ref _priority, value); }
         }
 
         public string Diagnostic
         {
             get { return _diagnostic; }
-            private set { this.RaiseAndSetIfChanged(ref _diagnostic, value); }
+            private set { RaiseAndSetIfChanged(ref _diagnostic, value); }
         }
 
         public object Value
         {
             get { return _value; }
-            private set { this.RaiseAndSetIfChanged(ref _value, value); }
+            private set { RaiseAndSetIfChanged(ref _value, value); }
         }
     }
 }

+ 5 - 5
src/Avalonia.Diagnostics/ViewModels/TreeNode.cs

@@ -5,13 +5,13 @@ using System;
 using System.Collections.Specialized;
 using System.Reactive;
 using System.Reactive.Linq;
+using Avalonia.Collections;
 using Avalonia.Styling;
 using Avalonia.VisualTree;
-using ReactiveUI;
 
 namespace Avalonia.Diagnostics.ViewModels
 {
-    internal class TreeNode : ReactiveObject
+    internal class TreeNode : ViewModelBase
     {
         private string _classes;
         private bool _isExpanded;
@@ -47,7 +47,7 @@ namespace Avalonia.Diagnostics.ViewModels
             }
         }
 
-        public IReadOnlyReactiveList<TreeNode> Children
+        public IAvaloniaReadOnlyList<TreeNode> Children
         {
             get;
             protected set;
@@ -56,7 +56,7 @@ namespace Avalonia.Diagnostics.ViewModels
         public string Classes
         {
             get { return _classes; }
-            private set { this.RaiseAndSetIfChanged(ref _classes, value); }
+            private set { RaiseAndSetIfChanged(ref _classes, value); }
         }
 
         public IVisual Visual
@@ -67,7 +67,7 @@ namespace Avalonia.Diagnostics.ViewModels
         public bool IsExpanded
         {
             get { return _isExpanded; }
-            set { this.RaiseAndSetIfChanged(ref _isExpanded, value); }
+            set { RaiseAndSetIfChanged(ref _isExpanded, value); }
         }
 
         public TreeNode Parent

+ 14 - 10
src/Avalonia.Diagnostics/ViewModels/TreePageViewModel.cs

@@ -1,25 +1,19 @@
 // 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.Reactive.Linq;
 using Avalonia.Controls;
 using Avalonia.VisualTree;
-using ReactiveUI;
 
 namespace Avalonia.Diagnostics.ViewModels
 {
-    internal class TreePageViewModel : ReactiveObject
+    internal class TreePageViewModel : ViewModelBase
     {
         private TreeNode _selected;
-
-        private readonly ObservableAsPropertyHelper<ControlDetailsViewModel> _details;
+        private ControlDetailsViewModel _details;
 
         public TreePageViewModel(TreeNode[] nodes)
         {
             Nodes = nodes;
-            _details = this.WhenAnyValue(x => x.SelectedNode)
-                .Select(x => x != null ? new ControlDetailsViewModel(x.Visual) : null)
-                .ToProperty(this, x => x.Details);
         }
 
         public TreeNode[] Nodes { get; protected set; }
@@ -27,10 +21,20 @@ namespace Avalonia.Diagnostics.ViewModels
         public TreeNode SelectedNode
         {
             get { return _selected; }
-            set { this.RaiseAndSetIfChanged(ref _selected, value); }
+            set
+            {
+                if (RaiseAndSetIfChanged(ref _selected, value))
+                {
+                    Details = value != null ? new ControlDetailsViewModel(value.Visual) : null;
+                }
+            }
         }
 
-        public ControlDetailsViewModel Details => _details.Value;
+        public ControlDetailsViewModel Details
+        {
+            get { return _details; }
+            private set { RaiseAndSetIfChanged(ref _details, value); }
+        }
 
         public TreeNode FindNode(IControl control)
         {

+ 32 - 0
src/Avalonia.Diagnostics/ViewModels/ViewModelBase.cs

@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using JetBrains.Annotations;
+
+namespace Avalonia.Diagnostics.ViewModels
+{
+    public class ViewModelBase : INotifyPropertyChanged
+    {
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        [NotifyPropertyChangedInvocator]
+        protected bool RaiseAndSetIfChanged<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
+        {
+            if (!EqualityComparer<T>.Default.Equals(field, value))
+            {
+                field = value;
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+                return true;
+            }
+
+            return false;
+        }
+
+        [NotifyPropertyChangedInvocator]
+        protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+        }
+    }
+}

+ 3 - 4
src/Avalonia.Diagnostics/ViewModels/VisualTreeNode.cs

@@ -1,10 +1,9 @@
 // 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.Controls;
+using Avalonia.Collections;
 using Avalonia.Styling;
 using Avalonia.VisualTree;
-using ReactiveUI;
 
 namespace Avalonia.Diagnostics.ViewModels
 {
@@ -17,11 +16,11 @@ namespace Avalonia.Diagnostics.ViewModels
 
             if (host?.Root == null)
             {
-                Children = visual.VisualChildren.CreateDerivedCollection(x => new VisualTreeNode(x, this));
+                Children = visual.VisualChildren.CreateDerivedList(x => new VisualTreeNode(x, this));
             }
             else
             {
-                Children = new ReactiveList<VisualTreeNode>(new[] { new VisualTreeNode(host.Root, this) });
+                Children = new AvaloniaList<VisualTreeNode>(new[] { new VisualTreeNode(host.Root, this) });
             }
 
             if ((Visual is IStyleable styleable))

+ 10 - 10
src/Avalonia.Diagnostics/Views/ControlDetailsView.cs

@@ -8,7 +8,6 @@ using Avalonia.Controls;
 using Avalonia.Diagnostics.ViewModels;
 using Avalonia.Media;
 using Avalonia.Styling;
-using ReactiveUI;
 
 namespace Avalonia.Diagnostics.Views
 {
@@ -16,6 +15,7 @@ namespace Avalonia.Diagnostics.Views
     {
         private static readonly StyledProperty<ControlDetailsViewModel> ViewModelProperty =
             AvaloniaProperty.Register<ControlDetailsView, ControlDetailsViewModel>("ViewModel");
+        private SimpleGrid _grid;
 
         public ControlDetailsView()
         {
@@ -27,7 +27,11 @@ namespace Avalonia.Diagnostics.Views
         public ControlDetailsViewModel ViewModel
         {
             get { return GetValue(ViewModelProperty); }
-            private set { SetValue(ViewModelProperty, value); }
+            private set
+            {
+                SetValue(ViewModelProperty, value);
+                _grid[GridRepeater.ItemsProperty] = value?.Properties;
+            }
         }
 
         private void InitializeComponent()
@@ -36,7 +40,7 @@ namespace Avalonia.Diagnostics.Views
 
             Content = new ScrollViewer
             {
-                Content = new SimpleGrid
+                Content = _grid = new SimpleGrid
                 {
                     Styles = new Styles
                     {
@@ -49,7 +53,6 @@ namespace Avalonia.Diagnostics.Views
                         },
                     },
                     [GridRepeater.TemplateProperty] = pt,
-                    [!GridRepeater.ItemsProperty] = this.WhenAnyValue(x => x.ViewModel.Properties).ToBinding(),
                 }
             };
         }
@@ -62,16 +65,13 @@ namespace Avalonia.Diagnostics.Views
             {
                 Text = property.Name,
                 TextWrapping = TextWrapping.NoWrap,
-                [!ToolTip.TipProperty] = property
-                    .WhenAnyValue(x => x.Diagnostic)
-                    .ToBinding(),
+                [!ToolTip.TipProperty] = property.GetObservable<string>(nameof(property.Diagnostic)).ToBinding(),
             };
 
             yield return new TextBlock
             {
                 TextWrapping = TextWrapping.NoWrap,
-                [!TextBlock.TextProperty] = property
-                    .WhenAnyValue(v => v.Value)
+                [!TextBlock.TextProperty] = property.GetObservable<object>(nameof(property.Value))
                     .Select(v => v?.ToString())
                     .ToBinding(),
             };
@@ -79,7 +79,7 @@ namespace Avalonia.Diagnostics.Views
             yield return new TextBlock
             {
                 TextWrapping = TextWrapping.NoWrap,
-                [!TextBlock.TextProperty] = property.WhenAnyValue(x => x.Priority).ToBinding(),
+                [!TextBlock.TextProperty] = property.GetObservable<string>((nameof(property.Priority))).ToBinding(),
             };
         }
     }

+ 30 - 0
src/Avalonia.Diagnostics/Views/PropertyChangedExtenions.cs

@@ -0,0 +1,30 @@
+using System;
+using System.ComponentModel;
+using System.Reactive.Linq;
+using System.Reflection;
+
+namespace Avalonia.Diagnostics.Views
+{
+    internal static class PropertyChangedExtenions
+    {
+        public static IObservable<T> GetObservable<T>(this INotifyPropertyChanged source, string propertyName)
+        {
+            Contract.Requires<ArgumentNullException>(source != null);
+            Contract.Requires<ArgumentNullException>(propertyName != null);
+
+            var property = source.GetType().GetTypeInfo().GetDeclaredProperty(propertyName);
+
+            if (property == null)
+            {
+                throw new ArgumentException($"Property '{propertyName}' not found on '{source}.");
+            }
+
+            return Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(
+                e => source.PropertyChanged += e,
+                e => source.PropertyChanged -= e)
+                    .Where(e => e.EventArgs.PropertyName == propertyName)
+                    .Select(_ => (T)property.GetValue(source))
+                    .StartWith((T)property.GetValue(source));
+        }
+    }
+}

+ 7 - 1
src/Avalonia.DotNetFrameworkRuntime/AppBuilder.cs

@@ -82,7 +82,13 @@ namespace Avalonia
 
         private void LoadAssembliesInDirectory()
         {
-            foreach (var file in new FileInfo(Assembly.GetEntryAssembly().Location).Directory.EnumerateFiles("*.dll"))
+            var location = Assembly.GetEntryAssembly().Location;
+            if (string.IsNullOrWhiteSpace(location))
+                return;
+            var dir = new FileInfo(location).Directory;
+            if (dir == null)
+                return;
+            foreach (var file in dir.EnumerateFiles("*.dll"))
             {
                 try
                 {

+ 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; }
     }
 }

+ 2 - 1
src/Avalonia.Input/IKeyboardDevice.cs

@@ -2,6 +2,7 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using System.ComponentModel;
 
 namespace Avalonia.Input
 {
@@ -26,7 +27,7 @@ namespace Avalonia.Input
         Toggled = 2,
     }
 
-    public interface IKeyboardDevice : IInputDevice
+    public interface IKeyboardDevice : IInputDevice, INotifyPropertyChanged
     {
         IInputElement FocusedElement { 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);

+ 37 - 25
src/Avalonia.Input/Navigation/DirectionalNavigation.cs

@@ -41,10 +41,10 @@ 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) ??
-                               GetFocusableDescendent(container, direction);
+                               GetFocusableDescendant(container, direction);
                     case KeyboardNavigationMode.Contained:
                         return GetNextInContainer(element, container, direction);
                     default:
@@ -53,7 +53,7 @@ namespace Avalonia.Input.Navigation
             }
             else
             {
-                return GetFocusableDescendents(element).FirstOrDefault();
+                return GetFocusableDescendants(element).FirstOrDefault();
             }
         }
 
@@ -71,24 +71,24 @@ namespace Avalonia.Input.Navigation
         }
 
         /// <summary>
-        /// Gets the first or last focusable descendent of the specified element.
+        /// Gets the first or last focusable descendant of the specified element.
         /// </summary>
         /// <param name="container">The element.</param>
         /// <param name="direction">The direction to search.</param>
         /// <returns>The element or null if not found.##</returns>
-        private static IInputElement GetFocusableDescendent(IInputElement container, NavigationDirection direction)
+        private static IInputElement GetFocusableDescendant(IInputElement container, NavigationDirection direction)
         {
             return IsForward(direction) ?
-                GetFocusableDescendents(container).FirstOrDefault() :
-                GetFocusableDescendents(container).LastOrDefault();
+                GetFocusableDescendants(container).FirstOrDefault() :
+                GetFocusableDescendants(container).LastOrDefault();
         }
 
         /// <summary>
-        /// Gets the focusable descendents of the specified element.
+        /// Gets the focusable descendants of the specified element.
         /// </summary>
         /// <param name="element">The element.</param>
-        /// <returns>The element's focusable descendents.</returns>
-        private static IEnumerable<IInputElement> GetFocusableDescendents(IInputElement element)
+        /// <returns>The element's focusable descendants.</returns>
+        private static IEnumerable<IInputElement> GetFocusableDescendants(IInputElement element)
         {
             var children = element.GetVisualChildren().OfType<IInputElement>();
 
@@ -99,11 +99,11 @@ namespace Avalonia.Input.Navigation
                     yield return child;
                 }
 
-                if (child.CanFocusDescendents())
+                if (child.CanFocusDescendants())
                 {
-                    foreach (var descendent in GetFocusableDescendents(child))
+                    foreach (var descendant in GetFocusableDescendants(child))
                     {
-                        yield return descendent;
+                        yield return descendant;
                     }
                 }
             }
@@ -123,11 +123,11 @@ namespace Avalonia.Input.Navigation
         {
             if (direction == NavigationDirection.Down)
             {
-                var descendent = GetFocusableDescendents(element).FirstOrDefault();
+                var descendant = GetFocusableDescendants(element).FirstOrDefault();
 
-                if (descendent != null)
+                if (descendant != null)
                 {
-                    return descendent;
+                    return descendant;
                 }
             }
 
@@ -156,11 +156,11 @@ namespace Avalonia.Input.Navigation
 
                 if (element != null && direction == NavigationDirection.Up)
                 {
-                    var descendent = GetFocusableDescendents(element).LastOrDefault();
+                    var descendant = GetFocusableDescendants(element).LastOrDefault();
 
-                    if (descendent != null)
+                    if (descendant != null)
                     {
-                        return descendent;
+                        return descendant;
                     }
                 }
 
@@ -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)
         {
@@ -193,13 +195,23 @@ namespace Avalonia.Input.Navigation
 
                 var siblings = parent.GetVisualChildren()
                     .OfType<IInputElement>()
-                    .Where(FocusExtensions.CanFocusDescendents);
+                    .Where(FocusExtensions.CanFocusDescendants);
                 var sibling = isForward ? 
                     siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault() : 
                     siblings.TakeWhile(x => x != container).LastOrDefault();
 
                 if (sibling != null)
                 {
+                    if (sibling is ICustomKeyboardNavigation custom)
+                    {
+                        var (handled, customNext) = custom.GetNext(element, direction);
+
+                        if (handled)
+                        {
+                            return customNext;
+                        }
+                    }
+
                     if (sibling.CanFocus())
                     {
                         next = sibling;
@@ -207,21 +219,21 @@ namespace Avalonia.Input.Navigation
                     else
                     {
                         next = isForward ?
-                            GetFocusableDescendents(sibling).FirstOrDefault() :
-                            GetFocusableDescendents(sibling).LastOrDefault();
+                            GetFocusableDescendants(sibling).FirstOrDefault() :
+                            GetFocusableDescendants(sibling).LastOrDefault();
                     }
                 }
 
                 if (next == null)
                 {
-                    next = GetFirstInNextContainer(parent, direction);
+                    next = GetFirstInNextContainer(element, parent, direction);
                 }
             }
             else
             {
                 next = isForward ?
-                    GetFocusableDescendents(container).FirstOrDefault() :
-                    GetFocusableDescendents(container).LastOrDefault();
+                    GetFocusableDescendants(container).FirstOrDefault() :
+                    GetFocusableDescendants(container).LastOrDefault();
             }
 
             return next;

+ 3 - 3
src/Avalonia.Input/Navigation/FocusExtensions.cs

@@ -16,10 +16,10 @@ namespace Avalonia.Input.Navigation
         public static bool CanFocus(this IInputElement e) => e.Focusable && e.IsEnabledCore && e.IsVisible;
 
         /// <summary>
-        /// Checks if descendents of the specified element can be focused.
+        /// Checks if descendants of the specified element can be focused.
         /// </summary>
         /// <param name="e">The element.</param>
-        /// <returns>True if descendents of the element can be focused.</returns>
-        public static bool CanFocusDescendents(this IInputElement e) => e.IsEnabledCore && e.IsVisible;
+        /// <returns>True if descendants of the element can be focused.</returns>
+        public static bool CanFocusDescendants(this IInputElement e) => e.IsEnabledCore && e.IsVisible;
     }
 }

+ 72 - 35
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,42 +44,43 @@ 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) ??
-                               GetFocusableDescendent(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 GetFocusableDescendents(element).FirstOrDefault();
+                return GetFocusableDescendants(element, direction).FirstOrDefault();
             }
         }
 
         /// <summary>
-        /// Gets the first or last focusable descendent of the specified element.
+        /// Gets the first or last focusable descendant of the specified element.
         /// </summary>
         /// <param name="container">The element.</param>
         /// <param name="direction">The direction to search.</param>
         /// <returns>The element or null if not found.##</returns>
-        private static IInputElement GetFocusableDescendent(IInputElement container, NavigationDirection direction)
+        private static IInputElement GetFocusableDescendant(IInputElement container, NavigationDirection direction)
         {
             return direction == NavigationDirection.Next ?
-                GetFocusableDescendents(container).FirstOrDefault() :
-                GetFocusableDescendents(container).LastOrDefault();
+                GetFocusableDescendants(container, direction).FirstOrDefault() :
+                GetFocusableDescendants(container, direction).LastOrDefault();
         }
 
         /// <summary>
-        /// Gets the focusable descendents of the specified element.
+        /// Gets the focusable descendants of the specified element.
         /// </summary>
         /// <param name="element">The element.</param>
-        /// <returns>The element's focusable descendents.</returns>
-        private static IEnumerable<IInputElement> GetFocusableDescendents(IInputElement element)
+        /// <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, 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.CanFocusDescendents())
+                else
                 {
-                    foreach (var descendent in GetFocusableDescendents(child))
+                    if (child.CanFocus())
                     {
-                        yield return descendent;
+                        yield return child;
+                    }
+
+                    if (child.CanFocusDescendants())
+                    {
+                        foreach (var descendant in GetFocusableDescendants(child, direction))
+                        {
+                            yield return descendant;
+                        }
                     }
                 }
             }
@@ -124,19 +138,23 @@ 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 descendent = GetFocusableDescendents(element).FirstOrDefault();
+                var descendant = GetFocusableDescendants(element, direction).FirstOrDefault();
 
-                if (descendent != null)
+                if (descendant != null)
                 {
-                    return descendent;
+                    return descendant;
                 }
             }
 
@@ -167,11 +185,11 @@ namespace Avalonia.Input.Navigation
 
                 if (element != null && direction == NavigationDirection.Previous)
                 {
-                    var descendent = GetFocusableDescendents(element).LastOrDefault();
+                    var descendant = GetFocusableDescendants(element, direction).LastOrDefault();
 
-                    if (descendent != null)
+                    if (descendant != null)
                     {
-                        return descendent;
+                        return descendant;
                     }
                 }
 
@@ -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)
         {
@@ -203,13 +223,20 @@ namespace Avalonia.Input.Navigation
 
                 var siblings = parent.GetVisualChildren()
                     .OfType<IInputElement>()
-                    .Where(FocusExtensions.CanFocusDescendents);
+                    .Where(FocusExtensions.CanFocusDescendants);
                 var sibling = direction == NavigationDirection.Next ? 
                     siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault() : 
                     siblings.TakeWhile(x => x != container).LastOrDefault();
 
                 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 ?
-                            GetFocusableDescendents(sibling).FirstOrDefault() :
-                            GetFocusableDescendents(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 ?
-                    GetFocusableDescendents(container).FirstOrDefault() :
-                    GetFocusableDescendents(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()

+ 25 - 9
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();
+                }
             }
         }
 
@@ -456,10 +473,9 @@ namespace Avalonia.Layout
 
                 ApplyTemplate();
 
-                var constrained = LayoutHelper
-                    .ApplyLayoutConstraints(this, availableSize)
-                    .Deflate(margin);
-
+                var constrained = LayoutHelper.ApplyLayoutConstraints(
+                    this,
+                    availableSize.Deflate(margin));
                 var measured = MeasureOverride(constrained);
 
                 var width = measured.Width;
@@ -613,7 +629,7 @@ namespace Avalonia.Layout
         /// <inheritdoc/>
         protected override sealed void OnVisualParentChanged(IVisual oldParent, IVisual newParent)
         {
-            foreach (ILayoutable i in this.GetSelfAndVisualDescendents())
+            foreach (ILayoutable i in this.GetSelfAndVisualDescendants())
             {
                 i.InvalidateMeasure();
             }

+ 1 - 1
src/Avalonia.Styling/Controls/NameScope.cs

@@ -50,7 +50,7 @@ namespace Avalonia.Controls
                     return result;
                 }
 
-                visual = (visual as ILogical).LogicalParent as Visual;
+                visual = (visual as ILogical)?.LogicalParent as Visual;
             }
 
             return null;

+ 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>

+ 3 - 3
src/Avalonia.Styling/LogicalTree/LogicalExtensions.cs

@@ -37,15 +37,15 @@ namespace Avalonia.LogicalTree
             return logical.LogicalChildren;
         }
 
-        public static IEnumerable<ILogical> GetLogicalDescendents(this ILogical logical)
+        public static IEnumerable<ILogical> GetLogicalDescendants(this ILogical logical)
         {
             foreach (ILogical child in logical.LogicalChildren)
             {
                 yield return child;
 
-                foreach (ILogical descendent in child.GetLogicalDescendents())
+                foreach (ILogical descendant in child.GetLogicalDescendants())
                 {
-                    yield return descendent;
+                    yield return descendant;
                 }
             }
         }

+ 7 - 7
src/Avalonia.Styling/Styling/DescendentSelector.cs

@@ -7,16 +7,16 @@ using Avalonia.LogicalTree;
 
 namespace Avalonia.Styling
 {
-    internal class DescendentSelector : Selector
+    internal class DescendantSelector : Selector
     {
         private readonly Selector _parent;
         private string _selectorString;
 
-        public DescendentSelector(Selector parent)
+        public DescendantSelector(Selector parent)
         {
             if (parent == null)
             {
-                throw new InvalidOperationException("Descendent selector must be preceeded by a selector.");
+                throw new InvalidOperationException("Descendant selector must be preceeded by a selector.");
             }
 
             _parent = parent;
@@ -41,7 +41,7 @@ namespace Avalonia.Styling
         protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
         {
             ILogical c = (ILogical)control;
-            List<IObservable<bool>> descendentMatches = new List<IObservable<bool>>();
+            List<IObservable<bool>> descendantMatches = new List<IObservable<bool>>();
 
             while (c != null)
             {
@@ -60,14 +60,14 @@ namespace Avalonia.Styling
                     }
                     else
                     {
-                        descendentMatches.Add(match.ObservableResult);
+                        descendantMatches.Add(match.ObservableResult);
                     }
                 }
             }
 
-            if (descendentMatches.Count > 0)
+            if (descendantMatches.Count > 0)
             {
-                return new SelectorMatch(StyleActivator.Or(descendentMatches));
+                return new SelectorMatch(StyleActivator.Or(descendantMatches));
             }
             else
             {

+ 3 - 3
src/Avalonia.Styling/Styling/Selectors.cs

@@ -42,13 +42,13 @@ namespace Avalonia.Styling
         }
 
         /// <summary>
-        /// Returns a selector which matches a descendent of a previous selector.
+        /// Returns a selector which matches a descendant of a previous selector.
         /// </summary>
         /// <param name="previous">The previous selector.</param>
         /// <returns>The selector.</returns>
-        public static Selector Descendent(this Selector previous)
+        public static Selector Descendant(this Selector previous)
         {
-            return new DescendentSelector(previous);
+            return new DescendantSelector(previous);
         }
 
         /// <summary>

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

@@ -66,12 +66,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 IList<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

+ 1 - 1
src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs

@@ -164,7 +164,7 @@ namespace Avalonia.Rendering
 
         private static void ClearTransformedBounds(IVisual visual)
         {
-            foreach (var e in visual.GetSelfAndVisualDescendents())
+            foreach (var e in visual.GetSelfAndVisualDescendants())
             {
                 BoundsTracker.SetTransformedBounds((Visual)visual, null);
             }

+ 1 - 1
src/Avalonia.Visuals/Rendering/ZIndexComparer.cs

@@ -8,6 +8,6 @@ namespace Avalonia.Rendering
     {
         public static readonly ZIndexComparer Instance = new ZIndexComparer();
 
-        public int Compare(IVisual x, IVisual y) => x.ZIndex.CompareTo(y.ZIndex);
+        public int Compare(IVisual x, IVisual y) => (x?.ZIndex ?? 0).CompareTo(y?.ZIndex ?? 0);
     }
 }

+ 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>

+ 3 - 3
src/Avalonia.Visuals/Visual.cs

@@ -314,7 +314,7 @@ namespace Avalonia
 
         /// <summary>
         /// Calls the <see cref="OnAttachedToVisualTree(VisualTreeAttachmentEventArgs)"/> method 
-        /// for this control and all of its visual descendents.
+        /// for this control and all of its visual descendants.
         /// </summary>
         /// <param name="e">The event args.</param>
         protected virtual void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
@@ -342,7 +342,7 @@ namespace Avalonia
 
         /// <summary>
         /// Calls the <see cref="OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs)"/> method 
-        /// for this control and all of its visual descendents.
+        /// for this control and all of its visual descendants.
         /// </summary>
         /// <param name="e">The event args.</param>
         protected virtual void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
@@ -422,7 +422,7 @@ namespace Avalonia
 
                 if (visual == null)
                 {
-                    throw new ArgumentException("'visual' is not a descendent of 'ancestor'.");
+                    throw new ArgumentException("'visual' is not a descendant of 'ancestor'.");
                 }
             }
 

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